Plugins
Créer des plugins de fournisseurs
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é d’API et une résolution dynamique des modèles.
Procédure
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-"]
},
"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 <key>",
"cliDescription": "Acme AI API key"
}
],
"configSchema": {
"type": "object",
"additionalProperties": false
}
}
Le manifeste déclare providerAuthEnvVars 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’identifiant d’un autre fournisseur. modelSupport
est facultatif et permet à OpenClaw de charger automatiquement votre plugin fournisseur depuis des identifiants
de modèle abrégés comme acme-large avant l’existence des hooks d’exécution. Si vous publiez le
fournisseur sur ClawHub, ces champs openclaw.compat et openclaw.build
sont obligatoires dans package.json.
Enregistrer le fournisseur
Un fournisseur minimal a besoin d’un id, d’un label, d’une auth et d’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 exécuter
openclaw onboard --acme-ai-api-key <key> et sélectionner
acme-ai/acme-large comme modèle.
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 l’invite système finale 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 n’analyse ses propres marqueurs de contrôle ou la livraison au canal.
Pour les fournisseurs intégrés qui n’enregistrent qu’un seul fournisseur de texte avec authentification
par clé d’API et une seule exécution adossée à un catalogue, préférez l’assistant plus ciblé
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 actif utilisé lorsqu’OpenClaw peut résoudre une
authentification réelle du fournisseur. Il peut effectuer une découverte propre au fournisseur. Utilisez
buildStaticProvider uniquement pour les lignes hors ligne qu’il est sûr d’afficher 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 fournisseurs intégrés, avec une configuration vide, un env vide et aucun
chemin d’agent ou d’espace de travail.
Si votre flux d’authentification doit aussi modifier 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(...).
Lorsqu’un endpoint natif de fournisseur prend en charge les blocs d’utilisation en streaming sur le
transport normal openai-completions, préférez les assistants de catalogue partagés dans
openclaw/plugin-sdk/provider-catalog-shared au lieu de coder en dur
des vérifications d’identifiant de fournisseur. supportsNativeStreamingUsageCompat(...) et
applyProviderNativeStreamingUsageCompat(...) détectent la prise en charge depuis la
carte des capacités de l’endpoint, afin que les endpoints natifs de style Moonshot/DashScope puissent toujours
s’y inscrire même lorsqu’un plugin utilise un identifiant de fournisseur personnalisé.
Ajouter la résolution dynamique des modèles
Si votre fournisseur accepte des identifiants 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 une
préparation asynchrone - resolveDynamicModel s’exécute de nouveau une fois celle-ci terminée.
Ajouter des hooks d’exécution (au besoin)
La plupart des fournisseurs n’ont besoin que de catalog + resolveDynamicModel. Ajoutez les hooks
progressivement selon les besoins de votre fournisseur.
Les générateurs d’assistants partagés couvrent désormais les familles les plus courantes de rejeu et de compatibilité d’outils, 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 rejeu disponibles aujourd’hui :
| Famille | Ce qu’elle câble | Exemples intégrés |
|---|---|---|
openai-compatible |
Politique de rejeu partagée de style OpenAI pour les transports compatibles OpenAI, incluant l’assainissement des identifiants d’appels d’outils, les corrections d’ordre assistant en premier et la validation générique des tours Gemini lorsque le transport en a besoin | moonshot, ollama, xai, zai |
anthropic-by-model |
Politique de rejeu adaptée à Claude choisie par modelId, de sorte que les transports de messages Anthropic ne reçoivent le nettoyage des blocs de réflexion propre à Claude que lorsque le modèle résolu est réellement un identifiant Claude |
amazon-bedrock, anthropic-vertex |
google-gemini |
Politique de rejeu Gemini native avec assainissement du rejeu d’amorçage et mode de sortie de raisonnement balisée | google, google-gemini-cli |
passthrough-gemini |
Assainissement 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 de rejeu Gemini native ni les réécritures d’amorçage | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Politique hybride pour les fournisseurs qui combinent des surfaces de modèles en messages Anthropic et compatibles OpenAI dans un seul plugin ; l’abandon facultatif des blocs de réflexion propre à Claude reste limité au côté Anthropic | minimax |
Familles de flux disponibles aujourd’hui :
| Famille | Ce qu’elle raccorde | Exemples intégrés |
|---|---|---|
google-thinking |
Normalisation des charges utiles de réflexion Gemini sur le chemin de flux partagé | google, google-gemini-cli |
kilocode-thinking |
Enveloppe de raisonnement Kilo sur le chemin de flux proxy partagé, avec kilo/auto et les identifiants de raisonnement proxy non pris en charge qui ignorent la réflexion injectée |
kilocode |
moonshot-thinking |
Correspondance des charges utiles binaires de réflexion native Moonshot depuis la configuration + le niveau /think |
moonshot |
minimax-fast-mode |
Réécriture du modèle en mode rapide MiniMax sur le chemin de flux partagé | minimax, minimax-portal |
openai-responses-defaults |
Enveloppes Responses OpenAI/Codex natives partagées : en-têtes d’attribution, /fast/serviceTier, verbosité du texte, recherche Web native Codex, mise en forme des charges utiles compatibles avec le raisonnement, et gestion du contexte Responses |
openai, openai-codex |
openrouter-thinking |
Enveloppe de raisonnement OpenRouter pour les routes proxy, avec les omissions des modèles non pris en charge/auto gérées centralement |
openrouter |
tool-stream-default-on |
Enveloppe tool_stream activée par défaut pour les fournisseurs comme Z.AI qui veulent le streaming d’outils sauf désactivation explicite |
zai |
SDK seams powering the family builders
Chaque constructeur de famille est composé à partir d’assistants publics de plus bas niveau exportés depuis le même package, auxquels vous pouvez recourir lorsqu’un fournisseur doit s’écarter du modèle 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 de point de terminaison/modèle (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId,normalizeNativeXaiModelId).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), et les enveloppes proxy/fournisseur partagées (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini"), les assistants de schéma Gemini sous-jacents (normalizeGeminiToolSchemas,inspectGeminiToolSchemas), et les assistants de compatibilité xAI (resolveXaiModelCompatPatch(),applyXaiModelCompat(model)). Le Plugin xAI intégré utilisenormalizeResolvedModel+contributeResolvedModelCompatavec ceux-ci afin que les règles xAI restent détenues par le fournisseur.
Certains assistants de flux restent volontairement locaux au fournisseur. @openclaw/anthropic-provider conserve wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier, et les constructeurs d’enveloppes Anthropic de plus bas niveau dans sa propre surface publique api.ts / contract-api.ts, car ils encodent la gestion des bêtas OAuth Claude et le verrouillage context1m. Le Plugin xAI conserve de même la mise en forme native xAI Responses dans son propre wrapStreamFn (alias /fast, tool_stream par défaut, nettoyage des outils stricts non pris en charge, suppression des charges utiles de raisonnement propres à xAI).
Le même modèle à la racine de package soutient aussi @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).
Token exchange
Pour les fournisseurs qui nécessitent 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,
};
},
Custom headers
Pour les fournisseurs qui nécessitent des en-têtes de requête personnalisés ou des modifications du corps :
// wrapStreamFn returns a StreamFn derived from ctx.streamFn
wrapStreamFn: (ctx) => {
if (!ctx.streamFn) return undefined;
const inner = ctx.streamFn;
return async (params) => {
params.headers = {
...params.headers,
"X-Acme-Version": "2",
};
return inner(params);
};
},
Native transport identity
Pour les fournisseurs qui nécessitent des en-têtes de requête/session natifs ou des métadonnées sur des transports HTTP ou WebSocket génériques :
resolveTransportTurnState: (ctx) => ({
headers: {
"x-request-id": ctx.turnId,
},
metadata: {
session_id: ctx.sessionId ?? "",
turn_id: ctx.turnId,
},
}),
resolveWebSocketSessionPolicy: (ctx) => ({
headers: {
"x-session-id": ctx.sessionId ?? "",
},
degradeCooldownMs: 60_000,
}),
Usage and billing
Pour les fournisseurs 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);
},
All available provider hooks
OpenClaw appelle les hooks dans cet ordre. La plupart des fournisseurs n’en utilisent que 2 ou 3 :
Les champs de fournisseur à compatibilité seule qu’OpenClaw n’appelle plus, comme
ProviderPlugin.capabilities et suppressBuiltInModel, ne sont pas listés
ici.
| # | Hook | Quand l’utiliser |
|---|---|---|
| 1 | catalog |
Catalogue de modèles ou valeurs par défaut de l’URL de base |
| 2 | applyConfigDefaults |
Valeurs par défaut globales détenues par le fournisseur pendant la matérialisation de la configuration |
| 3 | normalizeModelId |
Nettoyage des alias d’identifiants de modèle hérités/preview avant la recherche |
| 4 | normalizeTransport |
Nettoyage de api / baseUrl de la famille de fournisseurs avant l’assemblage générique du modèle |
| 5 | normalizeConfig |
Normaliser la configuration models.providers.<id> |
| 6 | applyNativeStreamingUsageCompat |
Réécritures de compatibilité d’utilisation en streaming natif pour les fournisseurs configurés |
| 7 | resolveConfigApiKey |
Résolution d’authentification par marqueur d’environnement détenue par le fournisseur |
| 8 | resolveSyntheticAuth |
Authentification synthétique locale/auto-hébergée ou adossée à la configuration |
| 9 | shouldDeferSyntheticProfileAuth |
Abaisser les espaces réservés de profil stocké synthétiques derrière l’authentification env/config |
| 10 | resolveDynamicModel |
Accepter des identifiants de modèle amont arbitraires |
| 11 | prepareDynamicModel |
Récupération asynchrone des métadonnées avant la résolution |
| 12 | normalizeResolvedModel |
Réécritures de transport avant le runner |
| 13 | contributeResolvedModelCompat |
Indicateurs de compatibilité pour les modèles de fournisseurs derrière un autre transport compatible |
| 14 | normalizeToolSchemas |
Nettoyage des schémas d’outils détenu par le fournisseur avant l’enregistrement |
| 15 | inspectToolSchemas |
Diagnostics de schémas d’outils détenus par le fournisseur |
| 16 | resolveReasoningOutputMode |
Contrat de sortie de raisonnement balisé vs natif |
| 17 | prepareExtraParams |
Paramètres de requête par défaut |
| 18 | createStreamFn |
Transport StreamFn entièrement personnalisé |
| 19 | wrapStreamFn |
Enveloppes d’en-têtes/corps personnalisées sur le chemin de flux normal |
| 20 | resolveTransportTurnState |
En-têtes/métadonnées natifs par tour |
| 21 | resolveWebSocketSessionPolicy |
En-têtes de session WS/cooldown natifs |
| 22 | formatApiKey |
Forme personnalisée du jeton d’exécution |
| 23 | refreshOAuth |
Actualisation OAuth personnalisée |
| 24 | buildAuthDoctorHint |
Conseils de réparation d’authentification |
| 25 | matchesContextOverflowError |
Détection de dépassement détenue par le fournisseur |
| 26 | classifyFailoverReason |
Classification des limites de débit/surcharges détenue par le fournisseur |
| 27 | isCacheTtlEligible |
Verrouillage du TTL du cache de prompt |
| 28 | buildMissingAuthMessage |
Indication personnalisée d’authentification manquante |
| 29 | augmentModelCatalog |
Lignes synthétiques de compatibilité ascendante |
| 30 | resolveThinkingProfile |
Jeu d’options /think propre au modèle |
| 31 | isBinaryThinking |
Compatibilité réflexion binaire activée/désactivée |
| 32 | supportsXHighThinking |
Compatibilité avec le raisonnement xhigh |
| 33 | resolveDefaultThinkingLevel |
Compatibilité de la politique /think par défaut |
| 34 | isModernModelRef |
Correspondance de modèle live/smoke |
| 35 | prepareRuntimeAuth |
Échange de jeton avant l’inférence |
| 36 | resolveUsageAuth |
Analyse personnalisée des identifiants d’utilisation |
| 37 | fetchUsageSnapshot |
Point de terminaison d’utilisation personnalisé |
| 38 | createEmbeddingProvider |
Adaptateur d’embeddings détenu par le fournisseur pour la mémoire/recherche |
| 39 | buildReplayPolicy |
Politique personnalisée de replay/Compaction de transcript |
| 40 | sanitizeReplayHistory |
Réécritures de replay propres au fournisseur après le nettoyage générique |
| 41 | validateReplayTurns |
Validation stricte des tours de replay avant le runner intégré |
| 42 | onModelSelected |
Callback après sélection (par ex. télémétrie) |
Notes de repli à l’exécution :
normalizeConfigvérifie d’abord le fournisseur correspondant, puis les autres Plugins fournisseurs capables de hooks jusqu’à ce que l’un modifie réellement la configuration. Si aucun hook de fournisseur ne réécrit une entrée de configuration de famille Google prise en charge, le normaliseur de configuration Google intégré s’applique quand même.resolveConfigApiKeyutilise le hook du fournisseur lorsqu’il est exposé. Le cheminamazon-bedrockintégré dispose aussi ici d’un résolveur de marqueur d’environnement AWS intégré, même si l’authentification d’exécution Bedrock elle-même utilise toujours la chaîne par défaut du SDK AWS.resolveSystemPromptContributionpermet à un fournisseur d’injecter des conseils de prompt système tenant compte du cache pour une famille de modèles. Préférez-le àbefore_prompt_buildlorsque le comportement appartient à une seule famille fournisseur/modèle et doit préserver la séparation stable/dynamique du cache.
Pour des descriptions détaillées et des exemples réels, consultez Internes : hooks d’exécution des fournisseurs.
Add extra capabilities (optional)
Étape 5 : Ajouter des capacités supplémentaires
Un Plugin fournisseur peut enregistrer la parole, la transcription realtime, la voix realtime, 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) avec votre appel
api.registerProvider(...) existant. Choisissez seulement les onglets dont vous avez besoin :
Speech (TTS)
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();
}
},
});
Utilisez assertOkOrThrowProviderError(...) pour les échecs HTTP de fournisseur afin que
les plugins partagent les lectures de corps d’erreur plafonnées, l’analyse des erreurs JSON et les
suffixes d’identifiant de requête.
Transcription en temps réel
Préférez createRealtimeTranscriptionWebSocketSession(...) : l’assistant partagé
gère la capture de proxy, le délai progressif de reconnexion, le vidage à la fermeture, les négociations
d’état prêt, la mise en file d’attente audio et les diagnostics d’événements de fermeture. Votre Plugin
mappe uniquement les événements amont.
api.registerRealtimeTranscriptionProvider({
id: "acme-ai",
label: "Acme Realtime Transcription",
isConfigured: () => true,
createSession: (req) => {
const apiKey = String(req.providerConfig.apiKey ?? "");
return createRealtimeTranscriptionWebSocketSession({
providerId: "acme-ai",
callbacks: req,
url: "wss://api.example.com/v1/realtime-transcription",
headers: { Authorization: `Bearer ${apiKey}` },
onMessage: (event, transport) => {
if (event.type === "session.created") {
transport.sendJson({ type: "session.update" });
transport.markReady();
return;
}
if (event.type === "transcript.final") {
req.onTranscript?.(event.text);
}
},
sendAudio: (audio, transport) => {
transport.sendJson({
type: "audio.append",
audio: audio.toString("base64"),
});
},
onClose: (transport) => {
transport.sendJson({ type: "audio.end" });
},
});
},
});
Les fournisseurs STT par lots qui envoient de l’audio multipart via POST doivent utiliser
buildAudioTranscriptionFormData(...) depuis
openclaw/plugin-sdk/provider-http. L’assistant normalise les noms de fichiers téléversés,
y compris les téléversements AAC qui nécessitent un nom de fichier de style M4A pour
les API de transcription compatibles.
Voix en temps réel
api.registerRealtimeVoiceProvider({
id: "acme-ai",
label: "Acme Realtime Voice",
capabilities: {
transports: ["gateway-relay"],
inputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
outputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
supportsBargeIn: true,
supportsToolCalls: true,
},
isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),
createBridge: (req) => ({
// Set this only if the provider accepts multiple tool responses for
// one call, for example an immediate "working" response followed by
// the final result.
supportsToolResultContinuation: false,
connect: async () => {},
sendAudio: () => {},
setMediaTimestamp: () => {},
handleBargeIn: () => {},
submitToolResult: () => {},
acknowledgeMark: () => {},
close: () => {},
isConnected: () => true,
}),
});
Déclarez capabilities afin que talk.catalog puisse exposer les modes,
transports, formats audio et indicateurs de fonctionnalités valides aux clients Talk
de navigateur et natifs. Implémentez handleBargeIn lorsqu’un transport peut détecter qu’un
humain interrompt la lecture de l’assistant et que le fournisseur prend en charge
la troncature ou l’effacement de la réponse audio active.
Compréhension des médias
api.registerMediaUnderstandingProvider({
id: "acme-ai",
capabilities: ["image", "audio"],
describeImage: async (req) => ({ text: "A photo of..." }),
transcribeAudio: async (req) => ({ text: "Transcript..." }),
});
Génération d’images et de vidéos
Les capacités vidéo utilisent une forme sensible au mode : generate,
imageToVideo et videoToVideo. Les champs agrégés plats comme
maxInputImages / maxInputVideos / maxDurationSeconds ne suffisent pas
pour annoncer proprement la prise en charge des modes de transformation ou les modes désactivés.
La génération de musique suit le même schéma avec des blocs generate /
edit explicites.
api.registerImageGenerationProvider({
id: "acme-ai",
label: "Acme Images",
generate: async (req) => ({ /* image result */ }),
});
api.registerVideoGenerationProvider({
id: "acme-ai",
label: "Acme Video",
capabilities: {
generate: { maxVideos: 1, maxDurationSeconds: 10, supportsResolution: true },
imageToVideo: {
enabled: true,
maxVideos: 1,
maxInputImages: 1,
maxInputImagesByModel: { "acme/reference-to-video": 9 },
maxDurationSeconds: 5,
},
videoToVideo: { enabled: false },
},
generateVideo: async (req) => ({ videos: [] }),
});
Récupération et recherche Web
api.registerWebFetchProvider({
id: "acme-ai-fetch",
label: "Acme Fetch",
hint: "Fetch pages through Acme's rendering backend.",
envVars: ["ACME_FETCH_API_KEY"],
placeholder: "acme-...",
signupUrl: "https://acme.example.com/fetch",
credentialPath: "plugins.entries.acme.config.webFetch.apiKey",
getCredentialValue: (fetchConfig) => fetchConfig?.acme?.apiKey,
setCredentialValue: (fetchConfigTarget, value) => {
const acme = (fetchConfigTarget.acme ??= {});
acme.apiKey = value;
},
createTool: () => ({
description: "Fetch a page through Acme Fetch.",
parameters: {},
execute: async (args) => ({ content: [] }),
}),
});
api.registerWebSearchProvider({
id: "acme-ai-search",
label: "Acme Search",
search: async (req) => ({ content: [] }),
});
Tester
Étape 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();
});
});
Publier sur ClawHub
Les Plugins fournisseurs se publient de la même manière que tout 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 de l’ordre du catalogue
catalog.order contrôle le moment où votre catalogue fusionne par rapport aux
fournisseurs intégrés :
| Ordre | Moment | Cas d’utilisation |
|---|---|---|
simple |
Premier passage | Fournisseurs à clé API simple |
profile |
Après simple | Fournisseurs conditionnés par des profils d’authentification |
paired |
Après profile | Synthétiser plusieurs entrées associées |
late |
Dernier passage | Remplacer les fournisseurs existants (gagne en cas de collision) |
Étapes suivantes
- Plugins de canal - si votre Plugin fournit aussi un canal
- SDK Runtime - assistants
api.runtime(TTS, recherche, sous-agent) - Vue d’ensemble du SDK - référence complète des importations de sous-chemins
- Internes des Plugins - détails des hooks et exemples intégrés