Ir al contenido

Crear complementos de proveedor

Esta guía explica cómo crear un complemento 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 de clave de API y resolución dinámica de modelos.

  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

    ”, “cliDescription”: “Acme AI API key” } ], “configSchema”: { “type”: “object”, “additionalProperties”: false } } ```

    El manifiesto declara providerAuthEnvVars para que OpenClaw pueda detectar las credenciales sin cargar el tiempo de ejecución de tu plugin. Añade providerAuthAliases cuando una variante de proveedor deba reutilizar la autenticación de otro ID de proveedor. modelSupport es opcional y permite a OpenClaw cargar automáticamente tu plugin de proveedor desde IDs de modelo abreviados como acme-large antes de que existan los ganchos de tiempo de ejecución. Si publicas el proveedor en ClawHub, esos campos openclaw.compat y openclaw.build son obligatorios en package.json.

  2. Registrar el proveedor

    Un proveedor de texto mínimo necesita un id, un label, un auth y un catalog. El catalog es el enlace de tiempo de ejecución/configuración propiedad del proveedor; puede llamar a APIs de proveedores en vivo y devuelve entradas models.providers.

    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,
    },
    ],
    },
    };
    },
    },
    });
    api.registerModelCatalogProvider({
    provider: "acme-ai",
    kinds: ["text"],
    liveCatalog: async (ctx) => {
    const apiKey = ctx.resolveProviderApiKey("acme-ai").apiKey;
    if (!apiKey) return null;
    return [
    {
    kind: "text",
    provider: "acme-ai",
    model: "acme-large",
    label: "Acme Large",
    source: "live",
    },
    ];
    },
    });
    },
    });

    El registerModelCatalogProvider es la superficie del catálogo del plano de control más reciente para la interfaz de usuario de lista/ayuda/selector. Úselo para filas de texto, generación de imágenes, generación de video y generación de música. Mantenga las llamadas a endpoints de proveedores y el mapeo de respuestas en el complemento; OpenClaw posee la forma de fila compartida, las etiquetas de origen y el renderizado de ayuda.

    Ese es un proveedor funcional. Los usuarios ahora pueden `openclaw onboard —acme-ai-api-key

    y seleccionar acme-ai/acme-large` como su modelo.

    Si el proveedor upstream utiliza diferentes tokens de control que OpenClaw, agregue una
    pequeña transformación de texto bidireccional en lugar de reemplazar la ruta de flujo (stream):
    ```typescript
    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" },
    ],
    });
    ```
    El `input` reescribe el contenido final del mensaje del sistema y de texto antes
    del transporte. El `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 integrados que solo registran un proveedor de texto con autenticación
    de clave de API más un tiempo de ejecución respaldado por un solo catálogo, prefiera el auxiliar
    más estrecho `defineSingleProviderPluginEntry(...)`:
    ```typescript
    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" }],
    }),
    },
    },
    });
    ```
    El `buildProvider` es la ruta del catálogo en vivo utilizada cuando OpenClaw puede resolver la
    autenticación real del proveedor. Puede realizar descubrimientos específicos del proveedor. Use
    `buildStaticProvider` solo para filas sin conexión que sean seguras de mostrar antes de que la
    autenticación esté configurada; no debe requerir credenciales ni realizar solicitudes de red.
    La visualización del `models list --all` de OpenClaw actualmente ejecuta catálogos estáticos
    solo para complementos de proveedor integrados, con una configuración vacía, un entorno vacío y sin
    rutas de agente/espacio de trabajo.
    Si su flujo de autenticación también necesita parchear `models.providers.*`, alias y
    el modelo predeterminado del agente durante la incorporación, use los auxiliares preestablecidos de
    `openclaw/plugin-sdk/provider-onboard`. Los auxiliares más estrechos son
    `createDefaultModelPresetAppliers(...)`,
    `createDefaultModelsPresetAppliers(...)` y
    `createModelCatalogPresetAppliers(...)`.
    Cuando el endpoint nativo de un proveedor admite bloques de uso en flujo (streamed) en el
    transporte normal `openai-completions`, prefiera los auxiliares de catálogo compartidos en
    `openclaw/plugin-sdk/provider-catalog-shared` en lugar de codificar
    comprobaciones de ID de proveedor. El `supportsNativeStreamingUsageCompat(...)` y
    el `applyProviderNativeStreamingUsageCompat(...)` detectan compatibilidad desde el
    mapa de capacidades del endpoint, por lo que los endpoints nativos de estilo Moonshot/DashScope aún
    se activan incluso cuando un complemento utiliza un ID de proveedor personalizado.
  3. Añadir resolución dinámica de modelos

    Si su proveedor acepta IDs de modelo arbitrarios (como un proxy o enrutador), añada 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, use prepareDynamicModel para el calentamiento asíncrono: resolveDynamicModel se ejecuta de nuevo después de que se complete.

  4. Añadir ganchos de ejecución (según sea necesario)

    La mayoría de los proveedores solo necesitan catalog + resolveDynamicModel. Añada ganchos de manera incremental a medida que su proveedor los requiera.

    Los constructores de ayuda compartidos ahora cubren las familias de repetición/compatibilidad de herramientas más comunes, por lo que los complementos generalmente no necesitan conectar cada gancho uno por uno manualmente:

    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 repetición disponibles hoy:

    FamiliaLo que conectaEjemplos incluidos
    openai-compatiblePolítica de repetición estilo OpenAI compartida para transportes compatibles con OpenAI, que incluye saneamiento de ID de llamada de herramienta, correcciones de ordenamiento asistente-primero y validación genérica de turnos Gemini donde el transporte lo necesitamoonshot, ollama, xai, zai
    anthropic-by-modelPolítica de repetición con conocimiento de Claude elegida por modelId, por lo que los transportes de mensajes Anthropic solo obtienen la limpieza de bloques de pensamiento específicos de Claude cuando el modelo resuelto es realmente una ID de Claudeamazon-bedrock, anthropic-vertex
    google-geminiPolítica de repetición nativa de Gemini más saneamiento de repetición de arranque y modo de salida de razonamiento etiquetadogoogle, google-gemini-cli
    passthrough-geminiSaneamiento de firma de pensamiento de Gemini para modelos de Gemini que se ejecutan a través de transportes de proxy compatibles con OpenAI; no habilita la validación de repetición nativa de Gemini ni reescrituras de arranqueopenrouter, kilocode, opencode, opencode-go
    hybrid-anthropic-openaiPolítica híbrida para proveedores que mezclan superficies de modelo de mensajes Anthropic y compatibles con OpenAI en un solo complemento; la eliminación opcional de bloques de pensamiento solo para Claude permanece limitada al lado Anthropicminimax

    Familias de transmisión disponibles hoy:

    FamiliaLo que conectaEjemplos incluidos
    google-thinkingNormalización de carga útil de pensamiento de Gemini en la ruta de transmisión compartidagoogle, google-gemini-cli
    kilocode-thinkingEnvoltorio de razonamiento Kilo en la ruta de transmisión de proxy compartida, con kilo/auto e IDs de razonamiento de proxy no compatibles que omiten el pensamiento inyectadokilocode
    moonshot-thinkingMapeo de carga útil de pensamiento nativo binario de Moonshot desde la configuración + nivel /thinkmoonshot
    minimax-fast-modeReescritura de modelo en modo rápido MiniMax en la ruta de transmisión compartidaminimax, minimax-portal
    openai-responses-defaultsEnvoltorios nativos compartidos de Respuestas OpenAI/Codex: encabezados de atribución, /fast/serviceTier, verbosidad de texto, búsqueda web nativa de Codex, formación de carga útil de compatibilidad de razonamiento y gestión de contexto de Respuestasopenai, openai-codex
    openrouter-thinkingEnvoltorio de razonamiento de OpenRouter para rutas de proxy, con omisiones de modelo no compatible/auto manejadas centralmenteopenrouter
    tool-stream-default-onEnvoltorio tool_stream habilitado por defecto para proveedores como Z.AI que desean transmisión de herramientas a menos que se deshabilite explícitamentezai
    Costuras del SDK que alimentan a los constructores de familias

    Cada constructor de familia se compone a partir de ayudantes públicos de nivel inferior exportados desde el mismo paquete, a los que puede recurrir cuando un proveedor necesita salirse del patrón común:

    • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...) y los constructores de repetición sin procesar (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). También exporta ayudantes de repetición de Gemini (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) y ayudantes de punto de final/modelo (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId).
    • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), además de los envoltorios compartidos OpenAI/Codex (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), el envoltorio compatible con OpenAI de DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), la limpieza de relleno previo de pensamiento de Mensajes de Anthropic (createAnthropicThinkingPrefillPayloadWrapper) y los envoltorios de proxy/proveedor compartidos (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
    • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai") y los ayudantes del esquema de proveedor subyacente.

    Algunos ayudantes de transmisión se mantienen locales al proveedor a propósito. @openclaw/anthropic-provider mantiene wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier y los constructores de envoltorios de Anthropic de nivel inferior en su propia costura pública api.ts / contract-api.ts porque codifican el manejo beta de OAuth de Claude y la limitación context1m. El complemento xAI mantiene de manera similar la formación nativa de Respuestas xAI en su propio wrapStreamFn (alias /fast, tool_stream predeterminado, limpieza estricta de herramientas no compatibles, eliminación de carga útil de razonamiento específica de xAI).

    El mismo patrón de raíz de paquete también respalda @openclaw/openai-provider (constructores de proveedor, ayudantes de modelo predeterminado, constructores de proveedor en tiempo real) y @openclaw/openrouter-provider (constructor de proveedor más ayudantes de integración/configuración).

    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,
    };
    },
    Todos los ganchos de proveedor disponibles

    OpenClaw llama a los ganchos 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 están enumerados aquí.

    #GanchoCuándo usar
    1catalogCatálogo de modelos o URL base predeterminadas
    2applyConfigDefaultsValores globales predeterminados propiedad del proveedor durante la materialización de la configuración
    3normalizeModelIdLimpieza de alias de ID de modelo heredados/vista previa antes de la búsqueda
    4normalizeTransportLimpieza de api / baseUrl de familia de proveedores antes del ensamblaje de modelo genérico
    5normalizeConfigNormalizar configuración `models.providers.

    | | 6 |applyNativeStreamingUsageCompat| Reescrituras de compatibilidad de uso de transmisión nativa para proveedores de configuración | | 7 |resolveConfigApiKey| Resolución de autenticación de marcador de entorno propiedad del proveedor | | 8 |resolveSyntheticAuth| Autenticación sintética local/autohospedada o respaldada por configuración | | 9 |shouldDeferSyntheticProfileAuth| Bajar marcadores de posición de perfil almacenado sintéticos detrás de la autenticación de entorno/configuración | | 10 |resolveDynamicModel| Aceptar ID de modelos ascendentes arbitrarios | | 11 |prepareDynamicModel| Obtención asincrónica de metadatos antes de resolver | | 12 |normalizeResolvedModel| Reescrituras de transporte antes del ejecutor | | 13 |contributeResolvedModelCompat| Indicadores de compatibilidad para modelos de proveedores detrás de otro transporte compatible | | 14 |normalizeToolSchemas| Limpieza de esquema de herramienta propiedad del proveedor antes del registro | | 15 |inspectToolSchemas| Diagnósticos de esquema de herramienta propiedad del proveedor | | 16 |resolveReasoningOutputMode| Contrato de salida de razonamiento etiquetado frente a nativo | | 17 |prepareExtraParams| Parámetros de solicitud predeterminados | | 18 |createStreamFn| Transporte StreamFn completamente personalizado | | 19 |wrapStreamFn| Envoltorios de encabezados/cuerpo personalizados en la ruta de transmisión normal | | 20 |resolveTransportTurnState| Encabezados/metadatos nativos por turno | | 21 |resolveWebSocketSessionPolicy| Encabezados de sesión WS nativos/período de enfriamiento | | 22 |formatApiKey| Forma de token de tiempo de ejecución personalizada | | 23 |refreshOAuth| Actualización de OAuth personalizada | | 24 |buildAuthDoctorHint| Guía de reparación de autenticación | | 25 |matchesContextOverflowError| Detección de desbordamiento propiedad del proveedor | | 26 |classifyFailoverReason| Clasificación de límite de tasa/sobrecarga propiedad del proveedor | | 27 |isCacheTtlEligible| Limitación de TTL de caché de prompt | | 28 |buildMissingAuthMessage| Sugerencia personalizada de falta de autenticación | | 29 |augmentModelCatalog| Filas sintéticas de compatibilidad futura | | 30 |resolveThinkingProfile| Conjunto de opciones/thinkespecíficas del modelo | | 31 |isBinaryThinking| Compatibilidad de activación/desactivación de pensamiento binario | | 32 |supportsXHighThinking| Compatibilidad de soporte de razonamientoxhigh| | 33 |resolveDefaultThinkingLevel| Compatibilidad de política/thinkpredeterminada | | 34 |isModernModelRef| Coincidencia de modelos en vivo/prueba | | 35 |prepareRuntimeAuth| Intercambio de tokens antes de la inferencia | | 36 |resolveUsageAuth| Análisis personalizado de credenciales de uso | | 37 |fetchUsageSnapshot| Punto final de uso personalizado | | 38 |createEmbeddingProvider| Adaptador de incrustación propiedad del proveedor para memoria/búsqueda | | 39 |buildReplayPolicy| Política personalizada de repetición/compactación de transcripciones | | 40 |sanitizeReplayHistory| Reescrituras de repetición específicas del proveedor después de la limpieza genérica | | 41 |validateReplayTurns| Validación estricta de turno de repetición antes del ejecutor incrustado | | 42 |onModelSelected` | Devolución de llamada posterior a la selección (por ejemplo, telemetría) |

    Notas sobre reserva en tiempo de ejecución:
    - `normalizeConfig` verifica el proveedor coincidente primero, luego otros complementos de proveedor con capacidad de gancho hasta que uno realmente cambie la configuración. Si ningún gancho de proveedor reescribe una entrada de configuración de familia Google compatible, el normalizador de configuración de Google incluido aún se aplica.
    - `resolveConfigApiKey` utiliza el gancho del proveedor cuando se expone. La ruta `amazon-bedrock` incluida también tiene un resolutor de marcador de entorno de AWS integrado aquí, aunque la autenticación en tiempo de ejecución de Bedrock aún utiliza la cadena predeterminada del SDK de AWS.
    - `resolveSystemPromptContribution` permite que un proveedor inyecte orientación de prompt del sistema consciente de la caché para una familia de modelos. Prefiérala sobre `before_prompt_build` cuando el comportamiento pertenece a una familia de proveedor/modelo y debe preservar la división de caché estable/dinámica.
    Para descripciones detalladas y ejemplos del mundo real, consulte [Internalidades: Ganchos de tiempo de ejecución del proveedor](/es/plugins/architecture-internals#provider-runtime-hooks).
  5. Añadir capacidades adicionales (opcional)

    Un proveedor de complementos 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, obtención web y búsqueda web junto con la inferencia de texto. OpenClaw clasifica esto como un complemento de capacidad híbrida: el patrón recomendado para complementos de empresas (un complemento por proveedor). Consulte Internalidades: Propiedad de capacidades.

    Registre cada capacidad dentro de register(api) junto con su llamada existente api.registerProvider(...). Elija solo las pestañas que necesite:

    ```typescript
    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();
    }
    },
    });
    ```
    Use `assertOkOrThrowProviderError(...)` para fallos HTTP del proveedor para que

    los complementos compartan lecturas limitadas del cuerpo del error, análisis de errores JSON y sufijos de ID de solicitud.

  6. Test

    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();
    });
    });

Los plugins de proveedores se publican de la misma manera que cualquier otro plugin de código externo:

Ventana de terminal
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin

No use el alias de publicación heredado solo de habilidades aquí; los paquetes de complementos deben usar clawhub package publish.

<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 su catálogo en relación con los proveedores integrados:

OrdenCuándoCaso de uso
simplePrimera pasadaProveedores de clave API simple
profileDespués de simpleProveedores restringidos por perfiles de autenticación
pairedDespués del perfilSintetizar múltiples entradas relacionadas
lateÚltima pasadaAnular proveedores existentes (gana en colisión)