Construire des plugins de fournisseur
Créer des plugins de fournisseur
Section intitulée « Créer des plugins de fournisseur »Ce guide explique la création d’un plugin de fournisseur qui ajoute un fournisseur de modèle (LLM) à OpenClaw. À la fin, vous disposerez d’un fournisseur avec un catalogue de modèles, une authentification par clé API et une résolution dynamique de modèle.
Procédure pas à pas
Section intitulée « Procédure pas à pas »Package et manifeste
{"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 } } ```
Le manifeste déclare
providerAuthEnvVarsafin que OpenClaw puisse détecter les identifiants sans charger votre runtime de plugin. Si vous publiez le fournisseur sur ClawHub, ces champsopenclaw.compatetopenclaw.buildsont requis danspackage.json.Enregistrer le fournisseur
Un fournisseur minimal nécessite un
id, unlabel, unauthet 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,},],},};},},});},});C’est un fournisseur fonctionnel. Les utilisateurs peuvent maintenant `openclaw onboard —acme-ai-api-key
et sélectionneracme-ai/acme-large` comme modèle.Pour les fournisseurs groupés qui n'enregistrent qu'un seul fournisseur de texte avec une authentification par clé APIainsi qu'un runtime basé sur un catalogue unique, préférez l'assistant plusrestreint `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 votre flux d'authentification doit également modifier `models.providers.*`, les alias etle modèle par défaut de l'agent lors de l'onboarding, utilisez les assistants prédéfinis de`openclaw/plugin-sdk/provider-onboard`. Les assistants les plus restreints sont`createDefaultModelPresetAppliers(...)`,`createDefaultModelsPresetAppliers(...)` et`createModelCatalogPresetAppliers(...)`.Ajouter une résolution dynamique de model
Si votre provider accepte des ID de model arbitraires (comme un proxy ou un routeur), ajoutez
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 résolution nécessite un appel réseau, utilisez
prepareDynamicModelpour un préchauffage asynchrone —resolveDynamicModels’exécute à nouveau après son achèvement.Ajouter les hooks d'exécution (si nécessaire)
La plupart des providers n’ont besoin que de
catalog+resolveDynamicModel. Ajoutez les hooks de manière incrémentielle au fur et à mesure que votre provider en a besoin.Pour les providers qui ont besoin d’un échange de jetons avant chaque appel d’inférence :
prepareRuntimeAuth: async (ctx) => {const exchanged = await exchangeToken(ctx.apiKey);return {apiKey: exchanged.token,baseUrl: exchanged.baseUrl,expiresAt: exchanged.expiresAt,};},Pour les providers qui ont besoin d’en-têtes de requête personnalisés ou de modifications du corps :
// 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);};},Pour les providers qui exposent des données d’utilisation/facturation :
resolveUsageAuth: async (ctx) => {const auth = await ctx.resolveOAuthToken();return auth ? { token: auth.token } : null;},fetchUsageSnapshot: async (ctx) => {return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);},Tous les hooks de provider disponibles
OpenClaw appelle les hooks dans cet ordre. La plupart des providers n’en utilisent que 2 ou 3 :
# Hook Quand l’utiliser 1 catalogCatalogue de modèles ou URL de base par défaut 2 resolveDynamicModelAccepter les ID de modèle en amont arbitraires 3 prepareDynamicModelRécupération asynchrone des métadonnées avant la résolution 4 normalizeResolvedModelRéécritures du transport avant le runner 5 capabilitiesMétadonnées de transcription/outillage (données, non appelable) 6 prepareExtraParamsParamètres de requête par défaut 7 wrapStreamFnEnveloppes d’en-têtes/corps personnalisés 8 formatApiKeyForme de jeton d’exécution personnalisée 9 refreshOAuthActualisation OAuth personnalisée 10 buildAuthDoctorHintConseils de réparation d’authentification 11 isCacheTtlEligibleFiltrage TTL du cache de prompt 12 buildMissingAuthMessageIndication personnalisée d’authentification manquante 13 suppressBuiltInModelMasquer les lignes en amont obsolètes 14 augmentModelCatalogLignes synthétiques de compatibilité ascendante 15 isBinaryThinkingPensée binaire activée/désactivée 16 supportsXHighThinkingSupport du raisonnement xhigh17 resolveDefaultThinkingLevelPolitique /thinkpar défaut18 isModernModelRefCorrespondance de modèle live/smoke 19 prepareRuntimeAuthÉchange de jetons avant l’inférence 20 resolveUsageAuthAnalyse personnalisée des informations d’identification d’utilisation 21 fetchUsageSnapshotPoint de terminaison d’utilisation personnalisé 22 onModelSelectedRappel post-sélection (ex. télémétrie) Pour des descriptions détaillées et des exemples concrets, voir Internals: Provider Runtime Hooks.
Ajouter des capacités supplémentaires (facultatif)
Un plugin provider peut enregistrer la reconnaissance vocale, la compréhension des médias, la génération d’images et la recherche web en plus de l’inférence de texte :
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 classe cela comme un plugin à capacités hybrides. C’est le modèle recommandé pour les plugins d’entreprise (un plugin par fournisseur). Voir Internes : Propriété des capacités.
Tester
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();});});
Publier sur ClawHub
Section intitulée « Publier sur ClawHub »Les plugins provider sont publiés de la même manière que tout autre plugin de code externe :
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginN’utilisez pas ici l’alias de publication legacy réservé aux compétences ; les packages de plugins doivent utiliser
clawhub package publish.
Structure des fichiers
Section intitulée « Structure des fichiers »/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)
## Référence de l'ordre du catalogue
`catalog.order` contrôle le moment où votre catalogue fusionne par rapport aux providers intégrés :
| Ordre | Quand | Cas d'usage || --------- | -------------- | ------------------------------------------------------------- || `simple` | Première passe | Providers avec clé API simple || `profile` | Après simple | Providers conditionnés par des profils d'authentification || `paired` | Après profil | Synthétiser plusieurs entrées liées || `late` | Dernière passe | Remplacer les providers existants (gagne en cas de collision) |
## Étapes suivantes
- [Plugins de canal](/en/plugins/sdk-channel-plugins) — si votre plugin fournit également un canal- [SDK Runtime](/en/plugins/sdk-runtime) — assistants `api.runtime` (TTS, recherche, subagent)- [Aperçu du SDK](/en/plugins/sdk-overview) — référence complète des imports de sous-chemins- [Internes des plugins](/en/plugins/architecture#provider-runtime-hooks) — détails des hooks et exemples inclus