Plugins
Providerplugins bouwen
Deze gids behandelt het bouwen van een provider-Plugin die een modelprovider (LLM) toevoegt aan OpenClaw. 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
referenties kan detecteren zonder je Plugin-runtime te laden. Voeg providerAuthAliases
toe wanneer een providervariant de authenticatie van een andere provider-id moet hergebruiken. modelSupport
is optioneel en laat OpenClaw je provider-Plugin automatisch laden op basis van korte
model-id's zoals acme-large voordat runtime-hooks bestaan. Als je de
provider publiceert op ClawHub, zijn die velden openclaw.compat en openclaw.build
verplicht in package.json.
De provider registreren
Een minimale provider heeft een id, label, auth en catalog nodig:
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,
},
],
},
};
},
},
});
},
});
Dit 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 upstreamprovider 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 assistenttekstdelta's en definitieve tekst voordat
OpenClaw zijn eigen controlemarkeringen of kanaalbezorging parseert.
Voor gebundelde providers die slechts één tekstprovider met API-sleutel-
authenticatie plus één door catalogus ondersteunde runtime registreren, geef je de voorkeur aan de specifiekere
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
providerauthenticatie kan oplossen. Het mag providerspecifieke ontdekking uitvoeren. Gebruik
buildStaticProvider alleen voor offline rijen die veilig kunnen worden getoond voordat authenticatie
is geconfigureerd; het mag geen referenties 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 geen
agent-/werkruimtepaden.
Als je authenticatiestroom 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 endpoint van een provider gestreamde usage-blokken ondersteunt op het
normale openai-completions-transport, geef je 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 uit de
endpoint-capabilitymap, zodat native Moonshot-/DashScope-achtige endpoints nog steeds
opt-in gebruiken, 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 resolutie een netwerkoproep 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
incrementeel toe wanneer je provider ze vereist.
Gedeelde helperbouwers dekken nu de meest voorkomende replay-/toolcompatibiliteits- families, dus Plugins hoeven elke hook meestal niet één voor één handmatig te bedraden:
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,
});
Vandaag beschikbare replayfamilies:
| Familie | Wat er wordt aangesloten | Gebundelde voorbeelden |
|---|---|---|
openai-compatible |
Gedeeld replaybeleid in OpenAI-stijl voor OpenAI-compatibele transports, inclusief sanering van tool-call-id's, correcties voor assistant-first-volgorde en generieke Gemini-turnvalidatie waar het transport dat nodig heeft | moonshot, ollama, xai, zai |
anthropic-by-model |
Claude-bewust replaybeleid gekozen op basis van modelId, zodat Anthropic-berichttransports 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 bootstrap-replaysanering en getagde modus voor reasoning-output | google, google-gemini-cli |
passthrough-gemini |
Sanering van Gemini thought-signatures voor Gemini-modellen die via OpenAI-compatibele proxytransports draaien; schakelt native Gemini-replayvalidatie of bootstrap-herschrijvingen niet in | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Hybride beleid voor providers die Anthropic-bericht- en OpenAI-compatibele modeloppervlakken in één Plugin combineren; optioneel Claude-only verwijderen van thinking-blokken blijft beperkt tot de Anthropic-kant | minimax |
Beschikbare streamfamilies vandaag:
| Familie | Wat ermee wordt aangesloten | Meegeleverde voorbeelden |
|---|---|---|
google-thinking |
Normalisatie van Gemini-denkpayloads op het gedeelde streampad | google, google-gemini-cli |
kilocode-thinking |
Kilo-redeneerwrapper op het gedeelde proxy-streampad, waarbij kilo/auto en niet-ondersteunde proxy-redeneer-id's geïnjecteerd denken overslaan |
kilocode |
moonshot-thinking |
Moonshot binaire native-thinking-payloadmapping vanuit configuratie + /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, tekstuitvoerigheid, native Codex-webzoekfunctie, reasoning-compat-payloadvorming en Responses-contextbeheer |
openai, openai-codex |
openrouter-thinking |
OpenRouter-redeneerwrapper voor proxyroutes, met centraal afgehandelde skips voor niet-ondersteunde modellen/auto |
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 opgebouwd uit publieke helpers op lager niveau die uit hetzelfde pakket worden geëxporteerd, waarop je kunt terugvallen wanneer een provider moet afwijken van het gemeenschappelijke patroon:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)en de ruwe replaybouwers (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Exporteert ook Gemini-replayhelpers (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) en endpoint-/modelhelpers (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId,normalizeNativeXaiModelId).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"), onderliggende Gemini-schemahelpers (normalizeGeminiToolSchemas,inspectGeminiToolSchemas) en xAI-compathelpers (resolveXaiModelCompatPatch(),applyXaiModelCompat(model)). De meegeleverde xAI-Plugin gebruiktnormalizeResolvedModel+contributeResolvedModelCompathiermee om xAI-regels eigendom van de provider te houden.
Sommige streamhelpers blijven bewust provider-lokaal. @openclaw/anthropic-provider houdt wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier en de Anthropic-wrapperbouwers op lager niveau in zijn eigen publieke api.ts / contract-api.ts-seam omdat ze Claude OAuth-betaverwerking 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 redeneerpayloads).
Hetzelfde pakketrootpatroon ondersteunt ook @openclaw/openai-provider (providerbouwers, helpers voor standaardmodellen, realtime-providerbouwers) en @openclaw/openrouter-provider (providerbouwer plus onboarding-/configuratiehelpers).
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 aanvraagheaders of body-aanpassingen nodig hebben:
// 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 transportidentiteit
Voor providers die native aanvraag-/sessieheaders of metadata nodig hebben op generieke HTTP- of WebSocket-transports:
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 maken:
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 alleen voor compatibiliteit 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 provider-eigen standaardwaarden tijdens configuratiematerialisatie |
| 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>-configuratie normaliseren |
| 6 | applyNativeStreamingUsageCompat |
Native streaminggebruik-compat-herschrijvingen voor configuratieproviders |
| 7 | resolveConfigApiKey |
Provider-eigen env-marker-authenticatieresolutie |
| 8 | resolveSyntheticAuth |
Lokale/self-hosted of configuratie-ondersteunde synthetische authenticatie |
| 9 | shouldDeferSyntheticProfileAuth |
Synthetische opgeslagen-profielplaceholders achter env-/configuratieauthenticatie plaatsen |
| 10 | resolveDynamicModel |
Willekeurige upstream-model-id's accepteren |
| 11 | prepareDynamicModel |
Asynchrone metadatafetch vóór resolutie |
| 12 | normalizeResolvedModel |
Transportherschrijvingen vóór de runner |
| 13 | contributeResolvedModelCompat |
Compat-flags voor vendormodellen achter een ander compatibel transport |
| 14 | normalizeToolSchemas |
Provider-eigen opschoning van toolschema's vóór registratie |
| 15 | inspectToolSchemas |
Provider-eigen toolschema-diagnostiek |
| 16 | resolveReasoningOutputMode |
Contract voor getagde versus native redeneeruitvoer |
| 17 | prepareExtraParams |
Standaard aanvraagparams |
| 18 | createStreamFn |
Volledig aangepast StreamFn-transport |
| 19 | wrapStreamFn |
Aangepaste header-/bodywrappers op het normale streampad |
| 20 | resolveTransportTurnState |
Native headers/metadata per beurt |
| 21 | resolveWebSocketSessionPolicy |
Native WS-sessieheaders/cooldown |
| 22 | formatApiKey |
Aangepaste runtimetokenvorm |
| 23 | refreshOAuth |
Aangepaste OAuth-vernieuwing |
| 24 | buildAuthDoctorHint |
Richtlijnen voor auth-herstel |
| 25 | matchesContextOverflowError |
Provider-eigen overflowdetectie |
| 26 | classifyFailoverReason |
Provider-eigen classificatie van rate-limit/overbelasting |
| 27 | isCacheTtlEligible |
Promptcache-TTL-gating |
| 28 | buildMissingAuthMessage |
Aangepaste hint voor ontbrekende authenticatie |
| 29 | augmentModelCatalog |
Synthetische forward-compat-rijen |
| 30 | resolveThinkingProfile |
Modelspecifieke /think-optieset |
| 31 | isBinaryThinking |
Compatibiliteit voor binair denken aan/uit |
| 32 | supportsXHighThinking |
Compatibiliteit voor xhigh-redeneerondersteuning |
| 33 | resolveDefaultThinkingLevel |
Compatibiliteit voor standaard /think-beleid |
| 34 | isModernModelRef |
Live-/smoke-modelmatching |
| 35 | prepareRuntimeAuth |
Tokenuitwisseling vóór inference |
| 36 | resolveUsageAuth |
Aangepaste parsing van gebruiksreferenties |
| 37 | fetchUsageSnapshot |
Aangepast gebruiksendpoint |
| 38 | createEmbeddingProvider |
Provider-eigen embeddingadapter voor geheugen/zoeken |
| 39 | buildReplayPolicy |
Aangepast beleid voor transcriptreplay/Compaction |
| 40 | sanitizeReplayHistory |
Provider-specifieke replayherschrijvingen na generieke opschoning |
| 41 | validateReplayTurns |
Strikte replay-beurtvalidatie vóór de ingesloten runner |
| 42 | onModelSelected |
Callback na selectie (bijv. telemetrie) |
Runtime-fallbacknotities:
normalizeConfigcontroleert eerst de gematchte provider en daarna andere hook-capabele providerplugins totdat één daarvan de configuratie daadwerkelijk wijzigt. Als geen providerhook een ondersteunde Google-familieconfiguratie-entry herschrijft, wordt de meegeleverde Google-configuratienormalizer alsnog toegepast.resolveConfigApiKeygebruikt de providerhook wanneer die beschikbaar is. Het meegeleverdeamazon-bedrock-pad heeft hier ook een ingebouwde AWS env-marker-resolver, ook al gebruikt Bedrock-runtimeauthenticatie zelf nog steeds de standaardketen van de AWS SDK.resolveSystemPromptContributionlaat een provider cachebewuste systeemprompt-richtlijnen injecteren voor een modelfamilie. Gebruik dit bij 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 mogelijkheden toevoegen (optioneel)
Stap 5: Extra mogelijkheden toevoegen
Een providerplugin kan spraak, realtime transcriptie, realtime stem, mediabegrip, beeldgeneratie, videogeneratie, webfetch, en webzoekfunctie registreren naast tekstinference. OpenClaw classificeert dit als een hybrid-capability-Plugin - het aanbevolen patroon voor bedrijfsplugins (één Plugin per vendor). Zie Internals: Capability Ownership.
Registreer elke mogelijkheid binnen register(api) naast je bestaande
api.registerProvider(...)-aanroep. Kies alleen de tabs die je nodig hebt:
Spraak (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 dezelfde begrensde verwerking van foutbody's, JSON-foutparsing en
request-id-achtervoegsels gebruiken.
Realtime transcriptie
Geef de voorkeur aan createRealtimeTranscriptionWebSocketSession(...) - de gedeelde
helper handelt proxy-capture, 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 uploadbestandsnamen,
inclusief AAC-uploads die voor compatibele transcriptie-API's een M4A-achtige bestandsnaam
nodig hebben.
Realtime-spraak
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 het afspelen door de assistent onderbreekt en de provider ondersteuning biedt voor
het inkorten of wissen van de actieve audiorespons.
Mediabegrip
api.registerMediaUnderstandingProvider({
id: "acme-ai",
capabilities: ["image", "audio"],
describeImage: async (req) => ({ text: "A photo of..." }),
transcribeAudio: async (req) => ({ text: "Transcript..." }),
});
Afbeeldings- en videogeneratie
Videomogelijkheden gebruiken een modusbewuste structuur: generate,
imageToVideo en videoToVideo. Platte aggregatievelden zoals
maxInputImages / maxInputVideos / maxDurationSeconds zijn niet
genoeg om ondersteuning voor transformatiemodi of uitgeschakelde modi netjes 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 ophalen en zoeken
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: [] }),
});
Testen
Stap 6: Testen
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();
});
});
Publiceren naar ClawHub
Providerplugins worden op dezelfde manier gepubliceerd als elke andere externe codeplugin:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
Gebruik hier niet de verouderde publish-alias voor alleen Skills; pluginpakketten 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 wordt samengevoegd ten opzichte van ingebouwde
providers:
| Volgorde | Wanneer | Gebruiksscenario |
|---|---|---|
simple |
Eerste ronde | Gewone providers met API-sleutel |
profile |
Na simple | Providers die afhankelijk zijn van authenticatieprofielen |
paired |
Na profile | Meerdere gerelateerde vermeldingen samenstellen |
late |
Laatste ronde | Bestaande providers overschrijven (wint bij conflict) |
Volgende stappen
- Kanaalplugins - als je plugin ook een kanaal aanbiedt
- SDK-runtime -
api.runtime-helpers (TTS, zoeken, subagent) - SDK-overzicht - volledige referentie voor subpath-imports
- Interne Plugin-details - hookdetails en gebundelde voorbeelden