Construir complementos de proveedor
Crear complementos de proveedores
Sección titulada «Crear complementos de proveedores»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.
Tutorial
Sección titulada «Tutorial»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"],"providerAuthEnvVars": {"acme-ai": ["ACME_AI_API_KEY"]},"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
providerAuthEnvVarspara que OpenClaw pueda detectar las credenciales sin cargar el tiempo de ejecución de tu complemento. Si publicas el proveedor en ClawHub, esos camposopenclaw.compatyopenclaw.buildson obligatorios enpackage.json.Registrar el proveedor
Un proveedor mínimo necesita un
id, unlabel, unauthy uncatalog: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. Los usuarios ahora pueden `openclaw onboard —acme-ai-api-key
y seleccionaracme-ai/acme-large` como su modelo.Para proveedores empaquetados que solo registren un proveedor de texto con autenticaciónde clave de API más un tiempo de ejecución con un solo catálogo, prefiere el asistente másespecífico `defineSingleProviderPluginEntry(...)`:```typescriptimport { 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" }],}),},},});```Si tu flujo de autenticación también necesita modificar `models.providers.*`, los alias yel modelo predeterminado del agente durante el incorporation, utiliza los asistentes preestablecidos de`openclaw/plugin-sdk/provider-onboard`. Los asistentes más específicos son`createDefaultModelPresetAppliers(...)`,`createDefaultModelsPresetAppliers(...)` y`createModelCatalogPresetAppliers(...)`.Agregar resolución dinámica de modelos
Si su proveedor acepta ID de modelo arbitrarios (como un proxy o enrutador), agregue
resolveDynamicModel:api.registerProvider({// ... id, label, auth, catalog from aboveresolveDynamicModel: (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
prepareDynamicModelpara la preparación asincrónica —resolveDynamicModelse ejecuta nuevamente después de que se complete.Añadir hooks de tiempo de ejecución (según sea necesario)
La mayoría de los proveedores solo necesitan
catalog+resolveDynamicModel. Añada hooks de manera incremental a medida que su proveedor los requiera.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,};},Para proveedores que necesitan cabeceras de solicitud personalizadas o modificaciones del cuerpo:
// wrapStreamFn returns a StreamFn derived from ctx.streamFnwrapStreamFn: (ctx) => {if (!ctx.streamFn) return undefined;const inner = ctx.streamFn;return async (params) => {params.headers = {...params.headers,"X-Acme-Version": "2",};return inner(params);};},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:
# Hook Cuándo usar 1 catalogCatálogo de modelos o URL base predeterminadas 2 resolveDynamicModelAceptar IDs de modelo ascendentes arbitrarios 3 prepareDynamicModelObtención asincrónica de metadatos antes de resolver 4 normalizeResolvedModelReescrituras de transporte antes del ejecutor 5 capabilitiesMetadatos de transcripción/herramientas (datos, no invocables) 6 prepareExtraParamsParámetros de solicitud predeterminados 7 wrapStreamFnEnvoltorios de cabeceras/cuerpo personalizados 8 formatApiKeyForma de token de tiempo de ejecución personalizada 9 refreshOAuthActualización de OAuth personalizada 10 buildAuthDoctorHintGuía de reparación de autenticación 11 isCacheTtlEligibleControl de TTL de caché de prompts 12 buildMissingAuthMessageSugerencia personalizada de autenticación faltante 13 suppressBuiltInModelOcultar filas ascendentes obsoletas 14 augmentModelCatalogFilas sintéticas de compatibilidad futura 15 isBinaryThinkingPensamiento binario activado/desactivado 16 supportsXHighThinkingSoporte de razonamiento xhigh17 resolveDefaultThinkingLevelPolítica predeterminada de /think18 isModernModelRefCoincidencia de modelos en vivo/prueba 19 prepareRuntimeAuthIntercambio de tokens antes de la inferencia 20 resolveUsageAuthAnálisis personalizado de credenciales de uso 21 fetchUsageSnapshotPunto final de uso personalizado 22 onModelSelectedDevolución de llamada posterior a la selección (ej. telemetría) Para descripciones detalladas y ejemplos del mundo real, consulte Internals: Provider Runtime Hooks.
Añadir capacidades adicionales (opcional)
Un proveedor de plugins puede registrar voz, comprensión de medios, generación de imágenes y búsqueda web junto con la inferencia de texto:
register(api) {api.registerProvider({ id: "acme-ai", /* ... */ });api.registerSpeechProvider({id: "acme-ai",label: "Acme Speech",isConfigured: ({ config }) => Boolean(config.messages?.tts),synthesize: async (req) => ({audioBuffer: Buffer.from(/* PCM data */),outputFormat: "mp3",fileExtension: ".mp3",voiceCompatible: false,}),});api.registerMediaUnderstandingProvider({id: "acme-ai",capabilities: ["image", "audio"],describeImage: async (req) => ({ text: "A photo of..." }),transcribeAudio: async (req) => ({ text: "Transcript..." }),});api.registerImageGenerationProvider({id: "acme-ai",label: "Acme Images",generate: async (req) => ({ /* image result */ }),});}OpenClaw clasifica esto como un plugin de capacidad híbrida. Este es el patrón recomendado para los plugins de empresas (un plugin por proveedor). Consulte Internals: Capability Ownership.
Probar
import { describe, it, expect } from "vitest";// Export your provider config object from index.ts or a dedicated fileimport { 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
Sección titulada «Publicar en ClawHub»Los plugins de proveedores se publican de la misma manera que cualquier otro plugin de código externo:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginNo use el alias de publicación heredado solo para habilidades aquí; los paquetes de plugins deben usar
clawhub package publish.
Estructura de archivos
Sección titulada «Estructura de archivos»/acme-ai/ ├── package.json # openclaw.providers metadata ├── openclaw.plugin.json # Manifest with providerAuthEnvVars ├── index.ts # definePluginEntry + registerProvider └── src/ ├── provider.test.ts # Tests └── usage.ts # Usage endpoint (optional)
## Referencia del orden del catálogo
`catalog.order` controla cuándo se fusiona su catálogo en relación con losproveedores integrados:
| Orden | Cuándo | Caso de uso || --------- | ------------------ | ------------------------------------------------------ || `simple` | Primera pasada | Proveedores de clave API simple || `profile` | Después de simple | Proveedores restringidos por perfiles de autenticación || `paired` | Después del perfil | Sintetizar múltiples entradas relacionadas || `late` | Última pasada | Anular proveedores existentes (gana en colisión) |
## Siguientes pasos
- [Channel Plugins](/en/plugins/sdk-channel-plugins) — si su plugin también proporciona un canal- [SDK Runtime](/en/plugins/sdk-runtime) — ayudantes `api.runtime` (TTS, búsqueda, subagente)- [SDK Overview](/en/plugins/sdk-overview) — referencia completa de importación de subrutas- [Plugin Internals](/en/plugins/architecture#provider-runtime-hooks) — detalles de los hooks y ejemplos incluidos