Passer au contenu principal
Ce guide explique comment créer un plugin fournisseur qui ajoute un fournisseur de modèles (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 des modèles.
Si vous n’avez encore créé aucun plugin OpenClaw, lisez d’abord Bien démarrer pour comprendre la structure de package de base et la configuration du manifeste.
Les plugins fournisseurs ajoutent des modèles à la boucle d’inférence normale d’OpenClaw. Si le modèle doit s’exécuter via un daemon d’agent natif qui possède les fils, la compaction ou les événements d’outils, associez le fournisseur à un harnais d’agent au lieu de placer les détails du protocole du daemon dans le cœur.

Procédure pas à pas

1

Package et manifeste

Étape 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"],
  "modelSupport": {
    "modelPrefixes": ["acme-"]
  },
  "setup": {
    "providers": [
      {
        "id": "acme-ai",
        "envVars": ["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
  }
}
Le manifeste déclare setup.providers[].envVars afin qu’OpenClaw puisse détecter les identifiants sans charger l’exécution de votre plugin. Ajoutez providerAuthAliases lorsqu’une variante de fournisseur doit réutiliser l’authentification de l’id d’un autre fournisseur. modelSupport est facultatif et permet à OpenClaw de charger automatiquement votre plugin fournisseur à partir d’identifiants de modèle abrégés comme acme-large avant que les hooks d’exécution existent. 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 de texte minimal nécessite un id, un label, une auth et un catalog. catalog est le hook d’exécution/configuration détenu par le fournisseur ; il peut appeler des API fournisseur en direct et retourne des entrées models.providers.
index.ts
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",
          },
        ];
      },
    });
  },
});
registerModelCatalogProvider est la nouvelle surface de catalogue du plan de contrôle pour l’interface de liste/aide/sélection. Utilisez-la pour les lignes de texte, de génération d’images, de génération de vidéos et de génération de musique. Gardez les appels aux points de terminaison fournisseur et le mappage des réponses dans le plugin ; OpenClaw possède la forme de ligne partagée, les libellés de source et le rendu de l’aide.Il s’agit d’un fournisseur fonctionnel. Les utilisateurs peuvent maintenant exécuter openclaw onboard --acme-ai-api-key <key> et sélectionner acme-ai/acme-large comme modèle.

Découverte de modèles en direct

Si votre fournisseur expose une API de type /models, conservez le point de terminaison spécifique au fournisseur et la projection des lignes dans votre plugin, puis utilisez openclaw/plugin-sdk/provider-catalog-live-runtime pour le cycle de vie de récupération partagé. L’assistant vous donne des récupérations HTTP protégées, des en-têtes d’authentification fournisseur, des erreurs HTTP structurées, une mise en cache TTL et un comportement de repli statique sans placer la politique fournisseur dans le cœur d’OpenClaw.Utilisez buildLiveModelProviderConfig lorsque l’API en direct vous indique uniquement quelles lignes du catalogue statique détenu par le fournisseur sont actuellement disponibles :
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import {
  buildLiveModelProviderConfig,
  type LiveModelCatalogFetchGuard,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

const STATIC_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,
  },
] as const;

async function buildAcmeLiveProvider(params: {
  apiKey: string;
  discoveryApiKey?: string;
  fetchGuard?: LiveModelCatalogFetchGuard;
}) {
  return await buildLiveModelProviderConfig({
    providerId: "acme-ai",
    endpoint: "https://api.acme-ai.com/v1/models",
    providerConfig: {
      baseUrl: "https://api.acme-ai.com/v1",
      api: "openai-completions",
    },
    models: STATIC_MODELS,
    apiKey: params.apiKey,
    discoveryApiKey: params.discoveryApiKey,
    fetchGuard: params.fetchGuard,
    ttlMs: 60_000,
    auditContext: "acme-ai-model-discovery",
  });
}

export default definePluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  register(api) {
    api.registerProvider({
      id: "acme-ai",
      label: "Acme AI",
      catalog: {
        order: "simple",
        run: async (ctx) => {
          const auth = ctx.resolveProviderAuth("acme-ai");
          const apiKey =
            auth.apiKey ?? ctx.resolveProviderApiKey("acme-ai").apiKey;
          if (!apiKey) return null;
          return {
            provider: await buildAcmeLiveProvider({
              apiKey,
              discoveryApiKey: auth.discoveryApiKey,
            }),
          };
        },
      },
      staticCatalog: {
        order: "simple",
        run: async () => ({
          provider: {
            baseUrl: "https://api.acme-ai.com/v1",
            api: "openai-completions",
            models: [...STATIC_MODELS],
          },
        }),
      },
    });
  },
});
Utilisez getCachedLiveProviderModelRows lorsque l’API du fournisseur retourne des métadonnées plus riches et que le plugin doit lui-même projeter les lignes dans les définitions de modèles OpenClaw :
index.ts
import {
  getCachedLiveProviderModelRows,
  LiveModelCatalogHttpError,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

async function discoverAcmeModels(apiKey: string) {
  try {
    const rows = await getCachedLiveProviderModelRows({
      providerId: "acme-ai",
      endpoint: "https://api.acme-ai.com/v1/models",
      apiKey,
      ttlMs: 60_000,
      auditContext: "acme-ai-model-discovery",
    });
    return rows
      .map((row) => projectAcmeModel(row))
      .filter((model) => model !== null);
  } catch (error) {
    if (error instanceof LiveModelCatalogHttpError) {
      return STATIC_MODELS;
    }
    throw error;
  }
}
run doit rester protégé par l’authentification et retourner null lorsqu’aucun identifiant utilisable n’est disponible. Conservez un staticRun hors ligne ou un repli statique afin que les surfaces de configuration, de documentation, de tests et de sélection ne dépendent pas d’un accès réseau en direct. Utilisez un TTL adapté à la fraîcheur de la liste des modèles, évitez l’interrogation du système de fichiers au moment des requêtes, et passez un readRows / readModelId spécifique au fournisseur uniquement lorsque la réponse en amont n’est pas une forme compatible OpenAI { data: [{ id, object }] }.Si le fournisseur en amont utilise des jetons de contrôle différents de ceux d’OpenClaw, ajoutez une petite transformation de texte bidirectionnelle au lieu de remplacer le chemin de flux :
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 réécrit le prompt système final et le contenu des messages texte avant le transport. output réécrit les deltas de texte de l’assistant et le texte final avant qu’OpenClaw analyse ses propres marqueurs de contrôle ou la livraison au canal.Pour les fournisseurs groupés qui enregistrent uniquement un fournisseur de texte avec une authentification par clé API et une seule exécution adossée à un catalogue, préférez l’assistant plus étroit 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 est le chemin de catalogue en direct utilisé quand OpenClaw peut résoudre une véritable authentification de fournisseur. Il peut effectuer une découverte propre au fournisseur. Utilisez buildStaticProvider uniquement pour les lignes hors ligne qui peuvent être affichées sans risque avant que l’authentification soit configurée ; il ne doit pas exiger d’identifiants ni effectuer de requêtes réseau. L’affichage models list --all d’OpenClaw exécute actuellement les catalogues statiques uniquement pour les plugins de fournisseurs intégrés, avec une configuration vide, un environnement vide et aucun chemin d’agent/espace de travail.Si votre flux d’authentification doit aussi corriger models.providers.*, les alias et le modèle par défaut de l’agent pendant l’onboarding, utilisez les assistants de préréglage de openclaw/plugin-sdk/provider-onboard. Les assistants les plus ciblés sont createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) et createModelCatalogPresetAppliers(...).Quand l’endpoint natif d’un fournisseur prend en charge les blocs d’utilisation en streaming sur le transport openai-completions normal, préférez les assistants de catalogue partagés dans openclaw/plugin-sdk/provider-catalog-shared plutôt que de coder en dur des vérifications d’identifiant de fournisseur. supportsNativeStreamingUsageCompat(...) et applyProviderNativeStreamingUsageCompat(...) détectent la prise en charge à partir de la carte des capacités d’endpoint, de sorte que les endpoints natifs de style Moonshot/DashScope s’activent toujours même lorsqu’un plugin utilise un identifiant de fournisseur personnalisé.Les exemples de découverte en direct ci-dessus couvrent les API de fournisseur de style /models. Gardez cette découverte dans catalog.run, protégée par une authentification utilisable, et gardez staticRun sans réseau pour la génération de catalogue hors ligne.
3

Ajouter la résolution dynamique de modèle

Si votre fournisseur accepte des ID de modèle 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 de nouveau une fois celui-ci terminé.
4

Ajouter des hooks d’exécution (si nécessaire)

La plupart des fournisseurs n’ont besoin que de catalog + resolveDynamicModel. Ajoutez des hooks progressivement, selon les besoins de votre fournisseur.Les constructeurs d’assistants partagés couvrent désormais les familles de replay/compatibilité d’outils les plus courantes, de sorte que les plugins n’ont généralement pas besoin de câbler chaque hook un par un :
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,
});
Familles de replay disponibles aujourd’hui :
FamilleCe que cela connecteExemples intégrés
openai-compatiblePolitique de replay partagée de style OpenAI pour les transports compatibles OpenAI, incluant l’assainissement des ID d’appels d’outils, les corrections d’ordre assistant-first et la validation générique des tours Gemini là où le transport en a besoinmoonshot, ollama, xai, zai
anthropic-by-modelPolitique de replay compatible Claude choisie par modelId, afin que les transports de messages Anthropic n’obtiennent le nettoyage des blocs de réflexion propre à Claude que lorsque le modèle résolu est réellement un ID Claudeamazon-bedrock, anthropic-vertex
google-geminiPolitique de replay Gemini native plus assainissement du replay d’amorçage. La famille partagée garde le CLI Gemini à sortie texte sur le raisonnement balisé ; le fournisseur direct google remplace resolveReasoningOutputMode par native, car la réflexion de l’API Gemini arrive sous forme de parties de pensée natives.google, google-gemini-cli
passthrough-geminiAssainissement des signatures de pensée Gemini pour les modèles Gemini exécutés via des transports proxy compatibles OpenAI ; n’active pas la validation native de replay Gemini ni les réécritures d’amorçageopenrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openaiPolitique hybride pour les fournisseurs qui mélangent des surfaces de modèles de messages Anthropic et compatibles OpenAI dans un même plugin ; la suppression optionnelle des blocs de réflexion réservée à Claude reste limitée au côté Anthropicminimax
Familles de flux disponibles aujourd’hui :
FamilleCe que cela connecteExemples intégrés
google-thinkingNormalisation de la charge utile de réflexion Gemini sur le chemin de flux partagégoogle, google-gemini-cli
kilocode-thinkingEnveloppe de raisonnement Kilo sur le chemin de flux proxy partagé, avec kilo/auto et les ID de raisonnement proxy non pris en charge ignorant la réflexion injectéekilocode
moonshot-thinkingMappage de charge utile native-thinking binaire Moonshot depuis la configuration + le niveau /thinkmoonshot
minimax-fast-modeRéécriture de modèle en mode rapide MiniMax sur le chemin de flux partagéminimax, minimax-portal
openai-responses-defaultsEnveloppes Responses natives OpenAI/Codex partagées : en-têtes d’attribution, /fast/serviceTier, verbosité du texte, recherche web native Codex, mise en forme de charge utile compatible raisonnement et gestion du contexte Responsesopenai
openrouter-thinkingEnveloppe de raisonnement OpenRouter pour les routes proxy, avec les exclusions de modèles non pris en charge/auto gérées de manière centraliséeopenrouter
tool-stream-default-onEnveloppe tool_stream activée par défaut pour les fournisseurs comme Z.AI qui veulent le streaming d’outils sauf désactivation explicitezai
Chaque constructeur de famille est composé à partir d’assistants publics de plus bas niveau exportés par le même package, que vous pouvez utiliser quand un fournisseur doit sortir du schéma commun :
  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...) et les constructeurs de replay bruts (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). Exporte aussi les assistants de replay Gemini (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) et les assistants d’endpoint/modèle (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId).
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), ainsi que les enveloppes OpenAI/Codex partagées (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), l’enveloppe compatible OpenAI DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), le nettoyage de préremplissage de réflexion Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper), la compatibilité d’appels d’outils en texte brut (createPlainTextToolCallCompatWrapper) et les enveloppes proxy/fournisseur partagées (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
  • openclaw/plugin-sdk/provider-stream-shared - enveloppes légères de charge utile et d’événements pour les chemins de fournisseurs critiques, incluant createOpenAICompatibleCompletionsThinkingOffWrapper, createPayloadPatchStreamWrapper, createPlainTextToolCallCompatWrapper, normalizeOpenAICompatibleReasoningPayload(...) et setQwenChatTemplateThinking(...).
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai") et les assistants de schéma de fournisseur sous-jacents.
Pour les fournisseurs de la famille Gemini, gardez le mode de sortie de raisonnement aligné sur le transport. Les fournisseurs directs de l’API Google Gemini doivent utiliser une sortie de raisonnement native, afin qu’OpenClaw consomme les parties de pensée natives sans ajouter de directives de prompt <think> / <final>. Les backends de style CLI Gemini à texte seul qui analysent une réponse finale JSON/texte peuvent conserver le contrat balisé google-gemini partagé.Certains assistants de flux restent volontairement locaux au fournisseur. @openclaw/anthropic-provider garde wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier et les constructeurs d’enveloppes Anthropic de plus bas niveau dans son propre seam public api.ts / contract-api.ts, car ils encodent la gestion des bêtas OAuth Claude et le contrôle de context1m. Le plugin xAI garde de même la mise en forme native xAI Responses dans son propre wrapStreamFn (alias /fast, tool_stream par défaut, nettoyage strict-tool non pris en charge, suppression de charge utile de raisonnement propre à xAI).Le même schéma de racine de package prend aussi en charge @openclaw/openai-provider (constructeurs de fournisseur, assistants de modèle par défaut, constructeurs de fournisseur realtime) et @openclaw/openrouter-provider (constructeur de fournisseur plus assistants d’onboarding/configuration).
Pour les fournisseurs qui ont besoin d’un échange de jeton 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,
  };
},
OpenClaw appelle les hooks dans cet ordre. La plupart des fournisseurs n’en utilisent que 2 ou 3 : les champs de fournisseur réservés à la compatibilité qu’OpenClaw n’appelle plus, tels que ProviderPlugin.capabilities et suppressBuiltInModel, ne sont pas répertoriés ici.
#HookQuand l’utiliser
1catalogCatalogue de modèles ou valeurs par défaut de l’URL de base
2applyConfigDefaultsValeurs globales par défaut détenues par le fournisseur lors de la matérialisation de la configuration
3normalizeModelIdNettoyage des alias d’ID de modèle hérités/en préversion avant la recherche
4normalizeTransportNettoyage de api / baseUrl pour une famille de fournisseurs avant l’assemblage générique du modèle
5normalizeConfigNormaliser la configuration models.providers.<id>
6applyNativeStreamingUsageCompatRéécritures de compatibilité d’utilisation en streaming natif pour les fournisseurs de configuration
7resolveConfigApiKeyRésolution d’authentification par marqueur d’environnement détenue par le fournisseur
8resolveSyntheticAuthAuthentification synthétique locale/auto-hébergée ou adossée à la configuration
9shouldDeferSyntheticProfileAuthAbaisser les espaces réservés de profils stockés synthétiques derrière l’authentification par environnement/configuration
10resolveDynamicModelAccepter des ID de modèle amont arbitraires
11prepareDynamicModelRécupération asynchrone des métadonnées avant la résolution
12normalizeResolvedModelRéécritures de transport avant l’exécuteur
13normalizeToolSchemasNettoyage des schémas d’outils détenu par le fournisseur avant l’enregistrement
14inspectToolSchemasDiagnostics des schémas d’outils détenus par le fournisseur
15resolveReasoningOutputModeContrat de sortie de raisonnement balisée ou native
16prepareExtraParamsParamètres de requête par défaut
17createStreamFnTransport StreamFn entièrement personnalisé
19wrapStreamFnEnveloppes d’en-têtes/corps personnalisées sur le chemin de flux normal
20resolveTransportTurnStateEn-têtes/métadonnées natifs par tour
21resolveWebSocketSessionPolicyEn-têtes de session WS natifs/temps de récupération
22formatApiKeyForme personnalisée du jeton d’exécution
23refreshOAuthActualisation OAuth personnalisée
24buildAuthDoctorHintConseils de réparation d’authentification
25matchesContextOverflowErrorDétection de dépassement détenue par le fournisseur
26classifyFailoverReasonClassification des limites de débit/surcharges détenue par le fournisseur
27isCacheTtlEligibleContrôle du TTL du cache de prompts
28buildMissingAuthMessageIndication personnalisée d’authentification manquante
29augmentModelCatalogLignes synthétiques de compatibilité ascendante
30resolveThinkingProfileEnsemble d’options /think propre au modèle
31isBinaryThinkingCompatibilité de réflexion binaire activée/désactivée
32supportsXHighThinkingCompatibilité de prise en charge du raisonnement xhigh
33resolveDefaultThinkingLevelCompatibilité de la stratégie /think par défaut
34isModernModelRefCorrespondance de modèle en direct/smoke
35prepareRuntimeAuthÉchange de jeton avant l’inférence
36resolveUsageAuthAnalyse personnalisée des identifiants d’utilisation
37fetchUsageSnapshotPoint de terminaison d’utilisation personnalisé
38createEmbeddingProviderAdaptateur d’embeddings détenu par le fournisseur pour la mémoire/recherche
39buildReplayPolicyStratégie personnalisée de relecture/compaction de transcript
40sanitizeReplayHistoryRéécritures de relecture propres au fournisseur après le nettoyage générique
41validateReplayTurnsValidation stricte des tours de relecture avant l’exécuteur intégré
42onModelSelectedRappel après sélection, par exemple pour la télémétrie
Notes sur la solution de repli du runtime :
  • normalizeConfig vérifie d’abord le fournisseur correspondant, puis les autres plugins de fournisseur capables d’exposer ce hook jusqu’à ce que l’un d’eux modifie réellement la configuration. Si aucun hook de fournisseur ne réécrit une entrée de configuration prise en charge de la famille Google, le normalisateur de configuration Google intégré s’applique quand même.
  • resolveConfigApiKey utilise le hook du fournisseur lorsqu’il est exposé. Amazon Bedrock conserve la résolution des marqueurs d’environnement AWS dans son plugin de fournisseur ; l’authentification du runtime elle-même utilise toujours la chaîne par défaut du SDK AWS lorsqu’elle est configurée avec auth: "aws-sdk".
  • resolveThinkingProfile(ctx) reçoit le provider sélectionné, modelId, l’indication facultative de catalogue reasoning fusionnée, et les faits compat facultatifs du modèle fusionnés. Utilisez compat uniquement pour sélectionner l’interface ou le profil de réflexion du fournisseur.
  • resolveSystemPromptContribution permet à un fournisseur d’injecter des conseils de prompt système compatibles avec le cache pour une famille de modèles. Préférez-le à before_prompt_build lorsque le comportement appartient à un fournisseur ou une famille de modèles et doit préserver la séparation stable/dynamique du cache.
Pour des descriptions détaillées et des exemples concrets, consultez Internes : hooks de runtime des fournisseurs.
5

Ajouter des capacités supplémentaires (facultatif)

Étape 5 : Ajouter des capacités supplémentaires

Un plugin de fournisseur peut enregistrer des embeddings, la synthèse vocale, la transcription en temps réel, la voix en temps réel, la compréhension des médias, la génération d’images, la génération de vidéos, la récupération web et la recherche web en plus de l’inférence de texte. OpenClaw classe cela comme un plugin à capacité hybride - le modèle recommandé pour les plugins d’entreprise (un plugin par fournisseur). Consultez Internes : propriété des capacités.Enregistrez chaque capacité dans register(api) à côté de votre appel api.registerProvider(...) existant. Choisissez uniquement les onglets dont vous avez besoin :
import {
  assertOkOrThrowProviderError,
  postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";

api.registerSpeechProvider({
  id: "acme-ai",
  label: "Acme Speech",
  defaultTimeoutMs: 120_000,
  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();
    }
  },
});
Utilisez assertOkOrThrowProviderError(...) pour les échecs HTTP du fournisseur afin que les plugins partagent des lectures plafonnées des corps d’erreur, l’analyse des erreurs JSON et les suffixes d’ID de requête.
6

Test

Étape 6 : tester

src/provider.test.ts
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();
  });
});

Publier sur ClawHub

Les Plugins de fournisseurs se publient de la même manière que n’importe quel autre Plugin de code externe :
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
N’utilisez pas ici l’ancien alias de publication réservé aux Skills ; les packages de Plugin doivent utiliser clawhub package publish.

Structure des fichiers

<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)

Référence d’ordre du catalogue

catalog.order contrôle le moment où votre catalogue est fusionné par rapport aux fournisseurs intégrés :
OrdreQuandCas d’utilisation
simplePremier passageFournisseurs simples à clé API
profileAprès simpleFournisseurs soumis aux profils d’authentification
pairedAprès profileSynthétiser plusieurs entrées liées
lateDernier passageRemplacer des fournisseurs existants (gagne en cas de collision)

Prochaines étapes

Associé