Plugins
Creación de plugins de proveedor
Esta guía explica cómo crear un plugin de proveedor que añade un proveedor de modelos (LLM) a OpenClaw. Al final tendrás un proveedor con un catálogo de modelos, autenticación con clave de API y resolución dinámica de modelos.
Guía paso a paso
Package and manifest
Paso 1: Paquete y manifiesto
{
"name": "@myorg/openclaw-acme-ai",
"version": "1.0.0",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"providers": ["acme-ai"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
},
"build": {
"openclawVersion": "2026.3.24-beta.2",
"pluginSdkVersion": "2026.3.24-beta.2"
}
}
}
{
"id": "acme-ai",
"name": "Acme AI",
"description": "Acme AI model provider",
"providers": ["acme-ai"],
"modelSupport": {
"modelPrefixes": ["acme-"]
},
"providerAuthEnvVars": {
"acme-ai": ["ACME_AI_API_KEY"]
},
"providerAuthAliases": {
"acme-ai-coding": "acme-ai"
},
"providerAuthChoices": [
{
"provider": "acme-ai",
"method": "api-key",
"choiceId": "acme-ai-api-key",
"choiceLabel": "Acme AI API key",
"groupId": "acme-ai",
"groupLabel": "Acme AI",
"cliFlag": "--acme-ai-api-key",
"cliOption": "--acme-ai-api-key <key>",
"cliDescription": "Acme AI API key"
}
],
"configSchema": {
"type": "object",
"additionalProperties": false
}
}
El manifiesto declara providerAuthEnvVars para que OpenClaw pueda detectar
credenciales sin cargar el runtime de tu plugin. Añade providerAuthAliases
cuando una variante de proveedor deba reutilizar la autenticación del identificador de otro proveedor. modelSupport
es opcional y permite a OpenClaw cargar automáticamente tu plugin de proveedor a partir de
identificadores de modelo abreviados como acme-large antes de que existan los hooks de runtime. Si publicas el
proveedor en ClawHub, esos campos openclaw.compat y openclaw.build
son obligatorios en package.json.
Register the provider
Un proveedor mínimo necesita un id, label, auth y catalog:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
export default definePluginEntry({
id: "acme-ai",
name: "Acme AI",
description: "Acme AI model provider",
register(api) {
api.registerProvider({
id: "acme-ai",
label: "Acme AI",
docsPath: "/providers/acme-ai",
envVars: ["ACME_AI_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: "acme-ai",
methodId: "api-key",
label: "Acme AI API key",
hint: "API key from your Acme AI dashboard",
optionKey: "acmeAiApiKey",
flagName: "--acme-ai-api-key",
envVar: "ACME_AI_API_KEY",
promptMessage: "Enter your Acme AI API key",
defaultModel: "acme-ai/acme-large",
}),
],
catalog: {
order: "simple",
run: async (ctx) => {
const apiKey =
ctx.resolveProviderApiKey("acme-ai").apiKey;
if (!apiKey) return null;
return {
provider: {
baseUrl: "https://api.acme-ai.com/v1",
apiKey,
api: "openai-completions",
models: [
{
id: "acme-large",
name: "Acme Large",
reasoning: true,
input: ["text", "image"],
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
contextWindow: 200000,
maxTokens: 32768,
},
{
id: "acme-small",
name: "Acme Small",
reasoning: false,
input: ["text"],
cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
contextWindow: 128000,
maxTokens: 8192,
},
],
},
};
},
},
});
},
});
Ese es un proveedor funcional. Ahora los usuarios pueden ejecutar
openclaw onboard --acme-ai-api-key <key> y seleccionar
acme-ai/acme-large como su modelo.
Si el proveedor upstream usa tokens de control distintos a los de OpenClaw, añade una pequeña transformación de texto bidireccional en lugar de reemplazar la ruta de streaming:
api.registerTextTransforms({
input: [
{ from: /red basket/g, to: "blue basket" },
{ from: /paper ticket/g, to: "digital ticket" },
{ from: /left shelf/g, to: "right shelf" },
],
output: [
{ from: /blue basket/g, to: "red basket" },
{ from: /digital ticket/g, to: "paper ticket" },
{ from: /right shelf/g, to: "left shelf" },
],
});
input reescribe el prompt de sistema final y el contenido de los mensajes de texto antes
del transporte. output reescribe los deltas de texto del asistente y el texto final antes de que
OpenClaw analice sus propios marcadores de control o la entrega del canal.
Para proveedores incluidos que solo registran un proveedor de texto con autenticación mediante clave de API
y un único runtime respaldado por catálogo, prefiere el helper más específico
defineSingleProviderPluginEntry(...):
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
export default defineSingleProviderPluginEntry({
id: "acme-ai",
name: "Acme AI",
description: "Acme AI model provider",
provider: {
label: "Acme AI",
docsPath: "/providers/acme-ai",
auth: [
{
methodId: "api-key",
label: "Acme AI API key",
hint: "API key from your Acme AI dashboard",
optionKey: "acmeAiApiKey",
flagName: "--acme-ai-api-key",
envVar: "ACME_AI_API_KEY",
promptMessage: "Enter your Acme AI API key",
defaultModel: "acme-ai/acme-large",
},
],
catalog: {
buildProvider: () => ({
api: "openai-completions",
baseUrl: "https://api.acme-ai.com/v1",
models: [{ id: "acme-large", name: "Acme Large" }],
}),
buildStaticProvider: () => ({
api: "openai-completions",
baseUrl: "https://api.acme-ai.com/v1",
models: [{ id: "acme-large", name: "Acme Large" }],
}),
},
},
});
buildProvider es la ruta de catálogo en vivo que se usa cuando OpenClaw puede resolver la autenticación real
del proveedor. Puede realizar descubrimiento específico del proveedor. Usa
buildStaticProvider solo para filas offline que sean seguras de mostrar antes de configurar la autenticación;
no debe requerir credenciales ni realizar solicitudes de red.
La visualización actual de models list --all de OpenClaw ejecuta catálogos estáticos
solo para plugins de proveedor incluidos, con una configuración vacía, entorno vacío y sin
rutas de agente o workspace.
Si tu flujo de autenticación también necesita parchear models.providers.*, alias y
el modelo predeterminado del agente durante el onboarding, usa los helpers de preset de
openclaw/plugin-sdk/provider-onboard. Los helpers más específicos son
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) y
createModelCatalogPresetAppliers(...).
Cuando el endpoint nativo de un proveedor admite bloques de uso en streaming en el
transporte normal openai-completions, prefiere los helpers de catálogo compartidos en
openclaw/plugin-sdk/provider-catalog-shared en lugar de codificar comprobaciones de
identificador de proveedor. supportsNativeStreamingUsageCompat(...) y
applyProviderNativeStreamingUsageCompat(...) detectan el soporte a partir del
mapa de capacidades del endpoint, por lo que los endpoints nativos de estilo Moonshot/DashScope
siguen optando por esta compatibilidad incluso cuando un plugin usa un identificador de proveedor personalizado.
Add dynamic model resolution
Si tu proveedor acepta identificadores de modelo arbitrarios (como un proxy o router),
añade resolveDynamicModel:
api.registerProvider({
// ... id, label, auth, catalog from above
resolveDynamicModel: (ctx) => ({
id: ctx.modelId,
name: ctx.modelId,
provider: "acme-ai",
api: "openai-completions",
baseUrl: "https://api.acme-ai.com/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192,
}),
});
Si la resolución requiere una llamada de red, usa prepareDynamicModel para el calentamiento
asíncrono: resolveDynamicModel se ejecuta de nuevo cuando termina.
Add runtime hooks (as needed)
La mayoría de los proveedores solo necesitan catalog + resolveDynamicModel. Añade hooks
incrementalmente a medida que tu proveedor los requiera.
Los builders de helpers compartidos ahora cubren las familias más comunes de compatibilidad de replay/herramientas, por lo que los plugins normalmente no necesitan cablear cada hook manualmente uno por uno:
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream";
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
const GOOGLE_FAMILY_HOOKS = {
...buildProviderReplayFamilyHooks({ family: "google-gemini" }),
...buildProviderStreamFamilyHooks("google-thinking"),
...buildProviderToolCompatFamilyHooks("gemini"),
};
api.registerProvider({
id: "acme-gemini-compatible",
// ...
...GOOGLE_FAMILY_HOOKS,
});
Familias de replay disponibles actualmente:
| Familia | Qué cablea | Ejemplos incluidos |
|---|---|---|
openai-compatible |
Política de replay compartida de estilo OpenAI para transportes compatibles con OpenAI, incluida la limpieza de identificadores de llamadas a herramientas, correcciones de orden con asistente primero y validación genérica de turnos Gemini cuando el transporte la necesita | moonshot, ollama, xai, zai |
anthropic-by-model |
Política de replay compatible con Claude elegida por modelId, de modo que los transportes de mensajes Anthropic solo reciben limpieza de bloques de pensamiento específica de Claude cuando el modelo resuelto es realmente un identificador de Claude |
amazon-bedrock, anthropic-vertex |
google-gemini |
Política de replay nativa de Gemini más limpieza de replay de bootstrap y modo de salida de razonamiento etiquetada | google, google-gemini-cli |
passthrough-gemini |
Limpieza de firmas de pensamiento Gemini para modelos Gemini que se ejecutan mediante transportes proxy compatibles con OpenAI; no habilita la validación de replay nativa de Gemini ni las reescrituras de bootstrap | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Política híbrida para proveedores que mezclan superficies de modelos de mensajes Anthropic y compatibles con OpenAI en un solo plugin; la eliminación opcional de bloques de pensamiento solo de Claude permanece limitada al lado Anthropic | minimax |
Familias de stream disponibles hoy:
| Familia | Qué conecta | Ejemplos incluidos |
|---|---|---|
google-thinking |
Normalización de cargas de pensamiento de Gemini en la ruta de stream compartida | google, google-gemini-cli |
kilocode-thinking |
Contenedor de razonamiento de Kilo en la ruta de stream de proxy compartida, con kilo/auto e ids de razonamiento de proxy no compatibles omitiendo el pensamiento inyectado |
kilocode |
moonshot-thinking |
Mapeo de cargas nativas de pensamiento binario de Moonshot desde la configuración + nivel /think |
moonshot |
minimax-fast-mode |
Reescritura de modelos de modo rápido de MiniMax en la ruta de stream compartida | minimax, minimax-portal |
openai-responses-defaults |
Contenedores compartidos nativos de OpenAI/Codex Responses: encabezados de atribución, /fast/serviceTier, verbosidad de texto, búsqueda web nativa de Codex, conformación de cargas compatible con razonamiento y gestión de contexto de Responses |
openai, openai-codex |
openrouter-thinking |
Contenedor de razonamiento de OpenRouter para rutas de proxy, con omisiones de modelos no compatibles/auto gestionadas de forma centralizada |
openrouter |
tool-stream-default-on |
Contenedor tool_stream activado de forma predeterminada para proveedores como Z.AI que quieren streaming de herramientas salvo que se desactive explícitamente |
zai |
Seams del SDK que impulsan los constructores de familias
Cada constructor de familia se compone a partir de helpers públicos de nivel inferior exportados desde el mismo paquete, a los que puedes recurrir cuando un proveedor necesita apartarse del patrón común:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)y los constructores de reproducción sin procesar (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). También exporta helpers de reproducción de Gemini (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) y helpers de endpoint/modelo (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId,normalizeNativeXaiModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...), además de los contenedores compartidos de OpenAI/Codex (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), el contenedor compatible con OpenAI de DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), la limpieza de precarga de pensamiento de Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper) y contenedores compartidos de proxy/proveedor (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini"), helpers de esquema de Gemini subyacentes (normalizeGeminiToolSchemas,inspectGeminiToolSchemas) y helpers de compatibilidad de xAI (resolveXaiModelCompatPatch(),applyXaiModelCompat(model)). El plugin xAI incluido usanormalizeResolvedModel+contributeResolvedModelCompatcon estos para mantener las reglas de xAI propiedad del proveedor.
Algunos helpers de stream permanecen locales al proveedor de forma intencionada. @openclaw/anthropic-provider mantiene wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier y los constructores de contenedores de Anthropic de nivel inferior en su propio seam público api.ts / contract-api.ts porque codifican la gestión beta de Claude OAuth y la activación de context1m. El plugin xAI conserva de forma similar la conformación nativa de xAI Responses en su propio wrapStreamFn (alias de /fast, tool_stream predeterminado, limpieza de herramientas estrictas no compatibles, eliminación de cargas de razonamiento específica de xAI).
El mismo patrón de raíz de paquete también respalda a @openclaw/openai-provider (constructores de proveedor, helpers de modelo predeterminado, constructores de proveedor en tiempo real) y @openclaw/openrouter-provider (constructor de proveedor más helpers de onboarding/configuración).
Intercambio de tokens
Para proveedores que necesitan un intercambio de tokens antes de cada llamada de inferencia:
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},
Encabezados personalizados
Para proveedores que necesitan encabezados de solicitud personalizados o modificaciones del cuerpo:
// wrapStreamFn returns a StreamFn derived from ctx.streamFn
wrapStreamFn: (ctx) => {
if (!ctx.streamFn) return undefined;
const inner = ctx.streamFn;
return async (params) => {
params.headers = {
...params.headers,
"X-Acme-Version": "2",
};
return inner(params);
};
},
Identidad de transporte nativa
Para proveedores que necesitan encabezados de solicitud/sesión nativos o metadatos en transportes HTTP o WebSocket genéricos:
resolveTransportTurnState: (ctx) => ({
headers: {
"x-request-id": ctx.turnId,
},
metadata: {
session_id: ctx.sessionId ?? "",
turn_id: ctx.turnId,
},
}),
resolveWebSocketSessionPolicy: (ctx) => ({
headers: {
"x-session-id": ctx.sessionId ?? "",
},
degradeCooldownMs: 60_000,
}),
Uso y facturación
Para proveedores que exponen datos de uso/facturación:
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);
},
Todos los hooks de proveedor disponibles
OpenClaw llama a los hooks en este orden. La mayoría de los proveedores solo usan 2-3:
Los campos de proveedor solo de compatibilidad que OpenClaw ya no llama, como
ProviderPlugin.capabilities y suppressBuiltInModel, no se enumeran
aquí.
| # | Hook | Cuándo usarlo |
|---|---|---|
| 1 | catalog |
Catálogo de modelos o valores predeterminados de URL base |
| 2 | applyConfigDefaults |
Valores predeterminados globales propiedad del proveedor durante la materialización de configuración |
| 3 | normalizeModelId |
Limpieza de alias de id de modelo heredado/vista previa antes de la búsqueda |
| 4 | normalizeTransport |
Limpieza de api / baseUrl de familia de proveedor antes del ensamblaje genérico del modelo |
| 5 | normalizeConfig |
Normalizar configuración models.providers.<id> |
| 6 | applyNativeStreamingUsageCompat |
Reescrituras de compatibilidad de uso de streaming nativo para proveedores de configuración |
| 7 | resolveConfigApiKey |
Resolución de auth de marcador de entorno propiedad del proveedor |
| 8 | resolveSyntheticAuth |
Auth sintética local/autohospedada o respaldada por configuración |
| 9 | shouldDeferSyntheticProfileAuth |
Rebajar marcadores de posición de perfil almacenado sintético por debajo de auth de entorno/configuración |
| 10 | resolveDynamicModel |
Aceptar ids arbitrarios de modelos upstream |
| 11 | prepareDynamicModel |
Obtención asíncrona de metadatos antes de resolver |
| 12 | normalizeResolvedModel |
Reescrituras de transporte antes del runner |
| 13 | contributeResolvedModelCompat |
Indicadores de compatibilidad para modelos de proveedor detrás de otro transporte compatible |
| 14 | normalizeToolSchemas |
Limpieza de esquemas de herramientas propiedad del proveedor antes del registro |
| 15 | inspectToolSchemas |
Diagnósticos de esquemas de herramientas propiedad del proveedor |
| 16 | resolveReasoningOutputMode |
Contrato de salida de razonamiento etiquetada frente a nativa |
| 17 | prepareExtraParams |
Parámetros de solicitud predeterminados |
| 18 | createStreamFn |
Transporte StreamFn completamente personalizado |
| 19 | wrapStreamFn |
Contenedores personalizados de encabezados/cuerpo en la ruta de stream normal |
| 20 | resolveTransportTurnState |
Encabezados/metadatos nativos por turno |
| 21 | resolveWebSocketSessionPolicy |
Encabezados/cool-down de sesión WS nativos |
| 22 | formatApiKey |
Forma personalizada de token en tiempo de ejecución |
| 23 | refreshOAuth |
Actualización OAuth personalizada |
| 24 | buildAuthDoctorHint |
Guía de reparación de auth |
| 25 | matchesContextOverflowError |
Detección de desbordamiento propiedad del proveedor |
| 26 | classifyFailoverReason |
Clasificación de límite de tasa/sobrecarga propiedad del proveedor |
| 27 | isCacheTtlEligible |
Activación de TTL de caché de prompt |
| 28 | buildMissingAuthMessage |
Sugerencia personalizada de auth faltante |
| 29 | augmentModelCatalog |
Filas sintéticas de compatibilidad futura |
| 30 | resolveThinkingProfile |
Conjunto de opciones /think específico del modelo |
| 31 | isBinaryThinking |
Compatibilidad de pensamiento binario activado/desactivado |
| 32 | supportsXHighThinking |
Compatibilidad con razonamiento xhigh |
| 33 | resolveDefaultThinkingLevel |
Compatibilidad de política /think predeterminada |
| 34 | isModernModelRef |
Coincidencia de modelo live/smoke |
| 35 | prepareRuntimeAuth |
Intercambio de tokens antes de la inferencia |
| 36 | resolveUsageAuth |
Análisis personalizado de credenciales de uso |
| 37 | fetchUsageSnapshot |
Endpoint de uso personalizado |
| 38 | createEmbeddingProvider |
Adaptador de embeddings propiedad del proveedor para memoria/búsqueda |
| 39 | buildReplayPolicy |
Política personalizada de reproducción/Compaction de transcripción |
| 40 | sanitizeReplayHistory |
Reescrituras de reproducción específicas del proveedor después de la limpieza genérica |
| 41 | validateReplayTurns |
Validación estricta de turnos de reproducción antes del runner embebido |
| 42 | onModelSelected |
Callback posterior a la selección (p. ej., telemetría) |
Notas de fallback en tiempo de ejecución:
normalizeConfigcomprueba primero el proveedor coincidente y luego otros plugins de proveedor con capacidad de hooks hasta que uno realmente cambia la configuración. Si ningún hook de proveedor reescribe una entrada de configuración compatible con la familia Google, el normalizador de configuración de Google incluido aún se aplica.resolveConfigApiKeyusa el hook de proveedor cuando está expuesto. La rutaamazon-bedrockincluida también tiene aquí un resolutor integrado de marcador de entorno AWS, aunque la auth en tiempo de ejecución de Bedrock sigue usando la cadena predeterminada del AWS SDK.resolveSystemPromptContributionpermite que un proveedor inyecte guía de prompt del sistema consciente de la caché para una familia de modelos. Prefiérelo abefore_prompt_buildcuando el comportamiento pertenece a un proveedor/familia de modelos y debe preservar la división estable/dinámica de caché.
Para descripciones detalladas y ejemplos reales, consulta Internos: Hooks de tiempo de ejecución de proveedor.
Añadir capacidades adicionales (opcional)
Paso 5: Añadir capacidades adicionales
Un plugin de proveedor puede registrar voz, transcripción en tiempo real, voz en tiempo real, comprensión de medios, generación de imágenes, generación de video, fetch web, y búsqueda web junto con la inferencia de texto. OpenClaw clasifica esto como un plugin de capacidad híbrida: el patrón recomendado para plugins de empresa (un plugin por proveedor). Consulta Internos: Propiedad de capacidades.
Registra cada capacidad dentro de register(api) junto con tu llamada existente a
api.registerProvider(...). Elige solo las pestañas que necesites:
Speech (TTS)
import {
assertOkOrThrowProviderError,
postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";
api.registerSpeechProvider({
id: "acme-ai",
label: "Acme Speech",
isConfigured: ({ config }) => Boolean(config.messages?.tts),
synthesize: async (req) => {
const { response, release } = await postJsonRequest({
url: "https://api.example.com/v1/speech",
headers: new Headers({ "Content-Type": "application/json" }),
body: { text: req.text },
timeoutMs: req.timeoutMs,
fetchFn: fetch,
auditContext: "acme speech",
});
try {
await assertOkOrThrowProviderError(response, "Acme Speech API error");
return {
audioBuffer: Buffer.from(await response.arrayBuffer()),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
};
} finally {
await release();
}
},
});
Usa assertOkOrThrowProviderError(...) para errores HTTP del proveedor, de modo que
los plugins compartan lecturas limitadas del cuerpo de error, análisis de errores JSON y
sufijos de ID de solicitud.
Realtime transcription
Prefiere createRealtimeTranscriptionWebSocketSession(...): el helper
compartido gestiona la captura del proxy, el retroceso de reconexión, el vaciado al cerrar, los
handshakes de preparación, la puesta en cola de audio y los diagnósticos de eventos de cierre. Tu plugin
solo asigna eventos upstream.
api.registerRealtimeTranscriptionProvider({
id: "acme-ai",
label: "Acme Realtime Transcription",
isConfigured: () => true,
createSession: (req) => {
const apiKey = String(req.providerConfig.apiKey ?? "");
return createRealtimeTranscriptionWebSocketSession({
providerId: "acme-ai",
callbacks: req,
url: "wss://api.example.com/v1/realtime-transcription",
headers: { Authorization: `Bearer ${apiKey}` },
onMessage: (event, transport) => {
if (event.type === "session.created") {
transport.sendJson({ type: "session.update" });
transport.markReady();
return;
}
if (event.type === "transcript.final") {
req.onTranscript?.(event.text);
}
},
sendAudio: (audio, transport) => {
transport.sendJson({
type: "audio.append",
audio: audio.toString("base64"),
});
},
onClose: (transport) => {
transport.sendJson({ type: "audio.end" });
},
});
},
});
Los proveedores de STT por lotes que envían audio multipart mediante POST deben usar
buildAudioTranscriptionFormData(...) desde
openclaw/plugin-sdk/provider-http. El helper normaliza los nombres de archivo de carga,
incluidas las cargas AAC que necesitan un nombre de archivo estilo M4A para
APIs de transcripción compatibles.
Realtime voice
api.registerRealtimeVoiceProvider({
id: "acme-ai",
label: "Acme Realtime Voice",
capabilities: {
transports: ["gateway-relay"],
inputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
outputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
supportsBargeIn: true,
supportsToolCalls: true,
},
isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),
createBridge: (req) => ({
// Set this only if the provider accepts multiple tool responses for
// one call, for example an immediate "working" response followed by
// the final result.
supportsToolResultContinuation: false,
connect: async () => {},
sendAudio: () => {},
setMediaTimestamp: () => {},
handleBargeIn: () => {},
submitToolResult: () => {},
acknowledgeMark: () => {},
close: () => {},
isConnected: () => true,
}),
});
Declara capabilities para que talk.catalog pueda exponer modos válidos,
transportes, formatos de audio y banderas de funcionalidad a clientes Talk de navegador
y nativos. Implementa handleBargeIn cuando un transporte pueda detectar que un
humano está interrumpiendo la reproducción del asistente y el proveedor admita
truncar o borrar la respuesta de audio activa.
Media understanding
api.registerMediaUnderstandingProvider({
id: "acme-ai",
capabilities: ["image", "audio"],
describeImage: async (req) => ({ text: "A photo of..." }),
transcribeAudio: async (req) => ({ text: "Transcript..." }),
});
Image and video generation
Las capacidades de video usan una forma consciente del modo: generate,
imageToVideo y videoToVideo. Los campos agregados planos como
maxInputImages / maxInputVideos / maxDurationSeconds no son
suficientes para anunciar compatibilidad con el modo de transformación o modos deshabilitados de forma clara.
La generación de música sigue el mismo patrón con bloques explícitos generate /
edit.
api.registerImageGenerationProvider({
id: "acme-ai",
label: "Acme Images",
generate: async (req) => ({ /* image result */ }),
});
api.registerVideoGenerationProvider({
id: "acme-ai",
label: "Acme Video",
capabilities: {
generate: { maxVideos: 1, maxDurationSeconds: 10, supportsResolution: true },
imageToVideo: {
enabled: true,
maxVideos: 1,
maxInputImages: 1,
maxInputImagesByModel: { "acme/reference-to-video": 9 },
maxDurationSeconds: 5,
},
videoToVideo: { enabled: false },
},
generateVideo: async (req) => ({ videos: [] }),
});
Web fetch and search
api.registerWebFetchProvider({
id: "acme-ai-fetch",
label: "Acme Fetch",
hint: "Fetch pages through Acme's rendering backend.",
envVars: ["ACME_FETCH_API_KEY"],
placeholder: "acme-...",
signupUrl: "https://acme.example.com/fetch",
credentialPath: "plugins.entries.acme.config.webFetch.apiKey",
getCredentialValue: (fetchConfig) => fetchConfig?.acme?.apiKey,
setCredentialValue: (fetchConfigTarget, value) => {
const acme = (fetchConfigTarget.acme ??= {});
acme.apiKey = value;
},
createTool: () => ({
description: "Fetch a page through Acme Fetch.",
parameters: {},
execute: async (args) => ({ content: [] }),
}),
});
api.registerWebSearchProvider({
id: "acme-ai-search",
label: "Acme Search",
search: async (req) => ({ content: [] }),
});
Test
Paso 6: Prueba
import { describe, it, expect } from "vitest";
// Export your provider config object from index.ts or a dedicated file
import { acmeProvider } from "./provider.js";
describe("acme-ai provider", () => {
it("resolves dynamic models", () => {
const model = acmeProvider.resolveDynamicModel!({
modelId: "acme-beta-v3",
} as any);
expect(model.id).toBe("acme-beta-v3");
expect(model.provider).toBe("acme-ai");
});
it("returns catalog when key is available", async () => {
const result = await acmeProvider.catalog!.run({
resolveProviderApiKey: () => ({ apiKey: "test-key" }),
} as any);
expect(result?.provider?.models).toHaveLength(2);
});
it("returns null catalog when no key", async () => {
const result = await acmeProvider.catalog!.run({
resolveProviderApiKey: () => ({ apiKey: undefined }),
} as any);
expect(result).toBeNull();
});
});
Publicar en ClawHub
Los plugins de proveedor se publican de la misma forma que cualquier otro plugin de código externo:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
No uses aquí el alias de publicación heredado exclusivo para skill; los paquetes de plugin deben usar
clawhub package publish.
Estructura de archivos
<bundled-plugin-root>/acme-ai/
├── package.json # openclaw.providers metadata
├── openclaw.plugin.json # Manifest with provider auth metadata
├── index.ts # definePluginEntry + registerProvider
└── src/
├── provider.test.ts # Tests
└── usage.ts # Usage endpoint (optional)
Referencia de orden del catálogo
catalog.order controla cuándo se fusiona tu catálogo en relación con los proveedores
integrados:
| Orden | Cuándo | Caso de uso |
|---|---|---|
simple |
Primera pasada | Proveedores sencillos con clave de API |
profile |
Después de simple | Proveedores controlados por perfiles de autenticación |
paired |
Después de profile | Sintetizar varias entradas relacionadas |
late |
Última pasada | Sobrescribir proveedores existentes (gana en colisiones) |
Siguientes pasos
- Plugins de canal: si tu plugin también proporciona un canal
- Runtime del SDK: helpers de
api.runtime(TTS, búsqueda, subagente) - Resumen del SDK: referencia completa de importación de subrutas
- Internos del plugin: detalles de hooks y ejemplos integrados