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 usa normalizeResolvedModel + contributeResolvedModelCompat con 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:

    • normalizeConfig comprueba 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.
    • resolveConfigApiKey usa el hook de proveedor cuando está expuesto. La ruta amazon-bedrock incluida 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.
    • resolveSystemPromptContribution permite que un proveedor inyecte guía de prompt del sistema consciente de la caché para una familia de modelos. Prefiérelo a before_prompt_build cuando 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)
    

    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

    Relacionado