Aller au contenu

Construire 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.

  1. 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 providerAuthEnvVars afin que OpenClaw puisse détecter les identifiants sans charger votre runtime de plugin. Si vous publiez le fournisseur sur ClawHub, ces champs openclaw.compat et openclaw.build sont requis dans package.json.

  2. Enregistrer le fournisseur

    Un fournisseur minimal nécessite un id, un label, un auth et un 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,
    },
    ],
    },
    };
    },
    },
    });
    },
    });

    C’est un fournisseur fonctionnel. Les utilisateurs peuvent maintenant `openclaw onboard —acme-ai-api-key

    et sélectionner acme-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é API
    ainsi qu'un runtime basé sur un catalogue unique, préférez l'assistant plus
    restreint `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" }],
    }),
    },
    },
    });
    ```
    Si votre flux d'authentification doit également modifier `models.providers.*`, les alias et
    le 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(...)`.
  3. 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 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 résolution nécessite un appel réseau, utilisez prepareDynamicModel pour un préchauffage asynchrone — resolveDynamicModel s’exécute à nouveau après son achèvement.

  4. 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,
    };
    },
    Tous les hooks de provider disponibles

    OpenClaw appelle les hooks dans cet ordre. La plupart des providers n’en utilisent que 2 ou 3 :

    #HookQuand l’utiliser
    1catalogCatalogue de modèles ou URL de base par défaut
    2resolveDynamicModelAccepter les ID de modèle en amont arbitraires
    3prepareDynamicModelRécupération asynchrone des métadonnées avant la résolution
    4normalizeResolvedModelRéécritures du transport avant le runner
    5capabilitiesMétadonnées de transcription/outillage (données, non appelable)
    6prepareExtraParamsParamètres de requête par défaut
    7wrapStreamFnEnveloppes d’en-têtes/corps personnalisés
    8formatApiKeyForme de jeton d’exécution personnalisée
    9refreshOAuthActualisation OAuth personnalisée
    10buildAuthDoctorHintConseils de réparation d’authentification
    11isCacheTtlEligibleFiltrage TTL du cache de prompt
    12buildMissingAuthMessageIndication personnalisée d’authentification manquante
    13suppressBuiltInModelMasquer les lignes en amont obsolètes
    14augmentModelCatalogLignes synthétiques de compatibilité ascendante
    15isBinaryThinkingPensée binaire activée/désactivée
    16supportsXHighThinkingSupport du raisonnement xhigh
    17resolveDefaultThinkingLevelPolitique /think par défaut
    18isModernModelRefCorrespondance de modèle live/smoke
    19prepareRuntimeAuthÉchange de jetons avant l’inférence
    20resolveUsageAuthAnalyse personnalisée des informations d’identification d’utilisation
    21fetchUsageSnapshotPoint de terminaison d’utilisation personnalisé
    22onModelSelectedRappel post-sélection (ex. télémétrie)

    Pour des descriptions détaillées et des exemples concrets, voir Internals: Provider Runtime Hooks.

  5. 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.

  6. Tester

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

Les plugins provider sont publiés de la même manière que tout autre plugin de code externe :

Fenêtre de terminal
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin

N’utilisez pas ici l’alias de publication legacy réservé aux compétences ; les packages de plugins doivent utiliser clawhub package publish.

/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