Building plugins
Provider-plugins bouwen
Deze gids leidt je door het bouwen van een provider-Plugin die een modelprovider (LLM) aan OpenClaw toevoegt. Aan het einde heb je een provider met een modelcatalogus, API-sleutelauthenticatie en dynamische modelresolutie.
Stapsgewijze handleiding
Pakket en manifest
Stap 1: Pakket en manifest
{"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}}Het manifest declareert providerAuthEnvVars zodat OpenClaw
inloggegevens kan detecteren zonder de runtime van je Plugin te laden. Voeg providerAuthAliases
toe wanneer een providervariant de auth van een andere provider-id moet hergebruiken. modelSupport
is optioneel en laat OpenClaw je provider-Plugin automatisch laden vanuit verkorte
model-id's zoals acme-large voordat runtime-hooks bestaan. Als je de
provider op ClawHub publiceert, zijn die velden openclaw.compat en openclaw.build
verplicht in package.json.
De provider registreren
Een minimale tekstprovider heeft een id, label, auth en catalog nodig.
catalog is de runtime-/configuratiehook die eigendom is van de provider; deze kan live
vendor-API's aanroepen en retourneert models.providers-items.
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 is het nieuwere control-plane-catalogusoppervlak
voor UI voor lijsten/help/kiezers. Gebruik het voor rijen voor tekst,
afbeeldingsgeneratie, videogeneratie en muziekgeneratie. Houd aanroepen naar vendor-eindpunten en
responsmapping in de Plugin; OpenClaw is eigenaar van de gedeelde rijvorm, bronlabels
en helpweergave.
Dat is een werkende provider. Gebruikers kunnen nu
openclaw onboard --acme-ai-api-key <key> uitvoeren en
acme-ai/acme-large als hun model selecteren.
Als de upstream-provider andere controletokens gebruikt dan OpenClaw, voeg dan een kleine bidirectionele teksttransformatie toe in plaats van het streampad te vervangen:
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 herschrijft de uiteindelijke systeemprompt en tekstberichtinhoud vóór
transport. output herschrijft assistent-tekstdelta's en definitieve tekst voordat
OpenClaw zijn eigen controlemarkeringen of kanaalbezorging parseert.
Voor gebundelde providers die slechts één tekstprovider registreren met API-sleutel-
auth plus één door de catalogus ondersteunde runtime, geef de voorkeur aan de smallere
helper 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 is het live-cataloguspad dat wordt gebruikt wanneer OpenClaw echte
providerauth kan oplossen. Het mag provider-specifieke ontdekking uitvoeren. Gebruik
buildStaticProvider alleen voor offline rijen die veilig zijn om te tonen voordat auth
is geconfigureerd; het mag geen inloggegevens vereisen of netwerkverzoeken doen.
De weergave models list --all van OpenClaw voert momenteel statische catalogi
alleen uit voor gebundelde provider-Plugins, met een lege config, lege env en zonder
agent-/workspacepaden.
Als je auth-flow ook models.providers.*, aliassen en
het standaardmodel van de agent tijdens onboarding moet patchen, gebruik dan de presethelpers uit
openclaw/plugin-sdk/provider-onboard. De smalste helpers zijn
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) en
createModelCatalogPresetAppliers(...).
Wanneer het native eindpunt van een provider gestreamde gebruiksblokken op het
normale openai-completions-transport ondersteunt, geef dan de voorkeur aan de gedeelde catalogushelpers in
openclaw/plugin-sdk/provider-catalog-shared in plaats van
provider-id-controles hard te coderen. supportsNativeStreamingUsageCompat(...) en
applyProviderNativeStreamingUsageCompat(...) detecteren ondersteuning vanuit de
capability-map van het eindpunt, zodat native Moonshot-/DashScope-achtige eindpunten zich nog steeds
aanmelden, zelfs wanneer een Plugin een aangepaste provider-id gebruikt.
Dynamische modelresolutie toevoegen
Als je provider willekeurige model-ID's accepteert (zoals een proxy of router),
voeg dan resolveDynamicModel toe:
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, }),});Als oplossen een netwerkaanroep vereist, gebruik dan prepareDynamicModel voor asynchrone
opwarming - resolveDynamicModel wordt opnieuw uitgevoerd nadat dit is voltooid.
Runtime-hooks toevoegen (indien nodig)
De meeste providers hebben alleen catalog + resolveDynamicModel nodig. Voeg hooks
stapsgewijs toe wanneer je provider ze vereist.
Gedeelde helperbuilders dekken nu de meest voorkomende replay-/toolcompatibiliteits- families, zodat Plugins meestal niet elke hook één voor één met de hand hoeven te verbinden:
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,});Beschikbare replayfamilies vandaag:
| Familie | Wat het koppelt | Meegeleverde voorbeelden |
|---|---|---|
openai-compatible |
Gedeeld OpenAI-achtig replaybeleid voor OpenAI-compatibele transporten, inclusief opschoning van tool-call-id's, oplossingen voor assistant-first-volgorde en generieke Gemini-turn-validatie waar het transport dit nodig heeft | moonshot, ollama, xai, zai |
anthropic-by-model |
Claude-bewust replaybeleid gekozen door modelId, zodat Anthropic-message-transporten alleen Claude-specifieke opschoning van thinking-blokken krijgen wanneer het opgeloste model daadwerkelijk een Claude-id is |
amazon-bedrock, anthropic-vertex |
google-gemini |
Native Gemini-replaybeleid plus opschoning van bootstrap-replay en modus voor getagde reasoning-uitvoer | google, google-gemini-cli |
passthrough-gemini |
Opschoning van Gemini-thought-signatures voor Gemini-modellen die via OpenAI-compatibele proxytransporten draaien; schakelt geen native Gemini-replayvalidatie of bootstrap-herschrijvingen in | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Hybride beleid voor providers die Anthropic-message- en OpenAI-compatibele modeloppervlakken in één plugin combineren; optioneel verwijderen van thinking-blokken alleen voor Claude blijft beperkt tot de Anthropic-kant | minimax |
Vandaag beschikbare streamfamilies:
| Familie | Wat het koppelt | Meegeleverde voorbeelden |
|---|---|---|
google-thinking |
Normalisatie van Gemini-thinking-payloads op het gedeelde streampad | google, google-gemini-cli |
kilocode-thinking |
Kilo-reasoning-wrapper op het gedeelde proxy-streampad, waarbij kilo/auto en niet-ondersteunde proxy-reasoning-id's geïnjecteerde thinking overslaan |
kilocode |
moonshot-thinking |
Moonshot binaire native-thinking-payloadmapping vanuit config + /think-niveau |
moonshot |
minimax-fast-mode |
MiniMax fast-mode-modelherschrijving op het gedeelde streampad | minimax, minimax-portal |
openai-responses-defaults |
Gedeelde native OpenAI/Codex Responses-wrappers: attributieheaders, /fast/serviceTier, tekstuitgebreidheid, native Codex-webzoekactie, reasoning-compat-payloadvorming en Responses-contextbeheer |
openai, openai-codex |
openrouter-thinking |
OpenRouter-reasoning-wrapper voor proxyroutes, waarbij overslaan voor niet-ondersteunde modellen/auto centraal wordt afgehandeld |
openrouter |
tool-stream-default-on |
Standaard ingeschakelde tool_stream-wrapper voor providers zoals Z.AI die toolstreaming willen tenzij dit expliciet is uitgeschakeld |
zai |
SDK-seams die de familiebouwers aandrijven
Elke familiebouwer is samengesteld uit lagere openbare helpers die uit hetzelfde pakket worden geëxporteerd en die je kunt gebruiken wanneer een provider van het gemeenschappelijke patroon moet afwijken:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)en de onbewerkte replaybouwers (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Exporteert ook Gemini-replayhelpers (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) en endpoint-/modelhelpers (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...), plus de gedeelde OpenAI/Codex-wrappers (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), DeepSeek V4 OpenAI-compatibele wrapper (createDeepSeekV4OpenAICompatibleThinkingWrapper), Anthropic Messages-opruiming van thinking-prefill (createAnthropicThinkingPrefillPayloadWrapper) en gedeelde proxy-/providerwrappers (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini")en onderliggende Gemini-schemahelpers (normalizeGeminiToolSchemas,inspectGeminiToolSchemas).
Sommige streamhelpers blijven bewust provider-lokaal. @openclaw/anthropic-provider houdt wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier en de lagere Anthropic-wrapperbouwers in zijn eigen openbare api.ts / contract-api.ts-seam omdat ze Claude OAuth-bèta-afhandeling en context1m-gating coderen. De xAI-plugin houdt native xAI Responses-vorming op vergelijkbare wijze in zijn eigen wrapStreamFn (/fast-aliassen, standaard tool_stream, opschoning van niet-ondersteunde strikte tools, xAI-specifieke verwijdering van reasoning-payloads).
Hetzelfde pakket-rootpatroon ondersteunt ook @openclaw/openai-provider (providerbouwers, default-modelhelpers, realtime-providerbouwers) en @openclaw/openrouter-provider (providerbouwer plus onboarding-/confighelpers).
Tokenuitwisseling
Voor providers die vóór elke inference-aanroep een tokenuitwisseling nodig hebben:
prepareRuntimeAuth: async (ctx) => { const exchanged = await exchangeToken(ctx.apiKey); return { apiKey: exchanged.token, baseUrl: exchanged.baseUrl, expiresAt: exchanged.expiresAt, };},Aangepaste headers
Voor providers die aangepaste requestheaders of body-aanpassingen nodig hebben:
// 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); };},Native transportidentiteit
Voor providers die native request-/sessieheaders of metadata nodig hebben op generieke HTTP- of WebSocket-transporten:
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,}),Gebruik en facturering
Voor providers die gebruiks-/factureringsgegevens beschikbaar stellen:
resolveUsageAuth: async (ctx) => { const auth = await ctx.resolveOAuthToken(); return auth ? { token: auth.token } : null;},fetchUsageSnapshot: async (ctx) => { return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);},Alle beschikbare providerhooks
OpenClaw roept hooks in deze volgorde aan. De meeste providers gebruiken er slechts 2-3:
Provider-velden die alleen nog voor compatibiliteit bestaan en die OpenClaw niet meer aanroept, zoals
ProviderPlugin.capabilities en suppressBuiltInModel, worden hier niet vermeld.
| # | Hook | Wanneer te gebruiken |
|---|---|---|
| 1 | catalog |
Modelcatalogus of standaardwaarden voor base-URL |
| 2 | applyConfigDefaults |
Globale standaardwaarden in eigendom van de provider tijdens configmaterialisatie |
| 3 | normalizeModelId |
Opschoning van legacy-/preview-model-id-aliassen vóór lookup |
| 4 | normalizeTransport |
Opschoning van providerfamilie-api / baseUrl vóór generieke modelassemblage |
| 5 | normalizeConfig |
models.providers.<id>-config normaliseren |
| 6 | applyNativeStreamingUsageCompat |
Native streaming-usage-compat-herschrijvingen voor configproviders |
| 7 | resolveConfigApiKey |
Auth-resolutie van env-markers in eigendom van de provider |
| 8 | resolveSyntheticAuth |
Lokale/zelfgehoste of config-ondersteunde synthetische auth |
| 9 | shouldDeferSyntheticProfileAuth |
Synthetische opgeslagen-profiel-placeholders lager prioriteren achter env-/config-auth |
| 10 | resolveDynamicModel |
Willekeurige upstream-model-id's accepteren |
| 11 | prepareDynamicModel |
Asynchrone metadatafetch vóór resolutie |
| 12 | normalizeResolvedModel |
Transportherschrijvingen vóór de runner |
| 13 | contributeResolvedModelCompat |
Compatflags voor vendormodellen achter een ander compatibel transport |
| 14 | normalizeToolSchemas |
Opschoning van toolschema's in eigendom van de provider vóór registratie |
| 15 | inspectToolSchemas |
Toolschema-diagnostiek in eigendom van de provider |
| 16 | resolveReasoningOutputMode |
Contract voor getagde versus native reasoning-uitvoer |
| 17 | prepareExtraParams |
Standaard requestparameters |
| 18 | createStreamFn |
Volledig aangepast StreamFn-transport |
| 19 | wrapStreamFn |
Aangepaste header-/bodywrappers op het normale streampad |
| 20 | resolveTransportTurnState |
Native headers/metadata per turn |
| 21 | resolveWebSocketSessionPolicy |
Native WS-sessieheaders/-cooldown |
| 22 | formatApiKey |
Aangepaste runtime-tokenvorm |
| 23 | refreshOAuth |
Aangepaste OAuth-refresh |
| 24 | buildAuthDoctorHint |
Richtlijnen voor auth-herstel |
| 25 | matchesContextOverflowError |
Overflow-detectie in eigendom van de provider |
| 26 | classifyFailoverReason |
Classificatie van rate-limit/overbelasting in eigendom van de provider |
| 27 | isCacheTtlEligible |
TTL-gating voor promptcache |
| 28 | buildMissingAuthMessage |
Aangepaste hint voor ontbrekende auth |
| 29 | augmentModelCatalog |
Synthetische forward-compat-rijen |
| 30 | resolveThinkingProfile |
Modelspecifieke /think-optieset |
| 31 | isBinaryThinking |
Compatibiliteit voor binair thinking aan/uit |
| 32 | supportsXHighThinking |
Compatibiliteit voor xhigh-reasoningondersteuning |
| 33 | resolveDefaultThinkingLevel |
Compatibiliteit voor standaard /think-beleid |
| 34 | isModernModelRef |
Live-/smoke-modelmatching |
| 35 | prepareRuntimeAuth |
Tokenuitwisseling vóór inference |
| 36 | resolveUsageAuth |
Aangepaste parsing van gebruikscredentials |
| 37 | fetchUsageSnapshot |
Aangepast gebruiksendpoint |
| 38 | createEmbeddingProvider |
Embeddingadapter in eigendom van de provider voor geheugen/zoeken |
| 39 | buildReplayPolicy |
Aangepast transcript-replay-/Compaction-beleid |
| 40 | sanitizeReplayHistory |
Provider-specifieke replayherschrijvingen na generieke opschoning |
| 41 | validateReplayTurns |
Strikte replay-turn-validatie vóór de embedded runner |
| 42 | onModelSelected |
Callback na selectie (bijv. telemetry) |
Runtime-fallbacknotities:
normalizeConfigcontroleert eerst de gematchte provider en daarna andere hook-capabele providerplugins totdat er één de config daadwerkelijk wijzigt. Als geen providerhook een ondersteunde Google-familie-configentry herschrijft, wordt de meegeleverde Google-confignormalizer nog steeds toegepast.resolveConfigApiKeygebruikt de providerhook wanneer die beschikbaar is. Het meegeleverdeamazon-bedrock-pad heeft hier ook een ingebouwde AWS env-marker-resolver, hoewel Bedrock-runtime-auth zelf nog steeds de standaardketen van de AWS SDK gebruikt.resolveSystemPromptContributionlaat een provider cachebewuste system-prompt-richtlijnen injecteren voor een modelfamilie. Geef hieraan de voorkeur bovenbefore_prompt_buildwanneer het gedrag bij één provider-/modelfamilie hoort en de stabiele/dynamische cachesplitsing moet behouden.
Zie Internals: Provider Runtime Hooks voor gedetailleerde beschrijvingen en praktijkvoorbeelden.
Extra capabilities toevoegen (optioneel)
Stap 5: Extra capabilities toevoegen
Een provider-Plugin kan spraak, realtime transcriptie, realtime spraak, mediabegrip, beeldgeneratie, videogeneratie, web-fetch en webzoekopdrachten naast tekstinferentie registreren. OpenClaw classificeert dit als een hybrid-capability-Plugin - het aanbevolen patroon voor bedrijfs-Plugins (een Plugin per leverancier). Zie Internals: Eigendom van capabilities.
Registreer elke capability binnen register(api) naast je bestaande
api.registerProvider(...)-aanroep. Kies alleen de tabbladen die je nodig hebt:
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(); } },});Gebruik assertOkOrThrowProviderError(...) voor HTTP-fouten van providers, zodat
Plugins gedeelde begrensde reads van foutbody's, JSON-foutparsing en
request-id-achtervoegsels gebruiken.
Realtime transcription
Geef de voorkeur aan createRealtimeTranscriptionWebSocketSession(...) - de gedeelde
helper handelt proxyvastlegging, reconnect-backoff, close-flushing, ready-
handshakes, audiowachtrijen en diagnostiek van close-events af. Je Plugin
koppelt alleen upstream-events.
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" }); }, }); },});Batch-STT-providers die multipart-audio POSTen, moeten
buildAudioTranscriptionFormData(...) uit
openclaw/plugin-sdk/provider-http gebruiken. De helper normaliseert upload-
bestandsnamen, inclusief AAC-uploads die een M4A-achtige bestandsnaam nodig hebben voor
compatibele transcriptie-API's.
Realtime voice
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, }),});Declareer capabilities zodat talk.catalog geldige modi,
transports, audioformaten en featureflags kan tonen aan browser- en native Talk-
clients. Implementeer handleBargeIn wanneer een transport kan detecteren dat een
mens de audioweergave van de assistent onderbreekt en de provider het
afkappen of wissen van de actieve audiorespons ondersteunt.
Media understanding
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "A photo of..." }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});Image and video generation
Videocapabilities gebruiken een modusbewuste vorm: generate,
imageToVideo en videoToVideo. Platte aggregaatvelden zoals
maxInputImages / maxInputVideos / maxDurationSeconds zijn niet
genoeg om ondersteuning voor transformatiemodi of uitgeschakelde modi duidelijk te
adverteren. Muziekgeneratie volgt hetzelfde patroon met expliciete generate /
edit-blokken.
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: [] }),});Web fetch and search
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: [] }),});Test
Stap 6: Test
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(); });});Publiceren naar ClawHub
Provider-Plugins publiceren op dezelfde manier als elke andere externe code-Plugin:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginGebruik hier niet de verouderde alias voor alleen skills; Plugin-pakketten moeten
clawhub package publish gebruiken.
Bestandsstructuur
<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)Referentie voor catalogusvolgorde
catalog.order bepaalt wanneer je catalogus samenvoegt ten opzichte van ingebouwde
providers:
| Volgorde | Wanneer | Usecase |
|---|---|---|
simple |
Eerste pass | Gewone API-key-providers |
profile |
Na simple | Providers afgeschermd via auth profiles |
paired |
Na profile | Meerdere gerelateerde items synthetiseren |
late |
Laatste pass | Bestaande providers overschrijven (wint bij botsing) |
Volgende stappen
- Channel-Plugins - als je Plugin ook een kanaal levert
- SDK Runtime -
api.runtime-helpers (TTS, zoeken, subagent) - SDK-overzicht - volledige importreferentie voor subpaden
- Plugin-internals - hookdetails en gebundelde voorbeelden