Building plugins
Provider-Plugins erstellen
Diese Anleitung führt Sie durch das Erstellen eines Provider-Plugins, das OpenClaw einen Modell-Provider (LLM) hinzufügt. Am Ende verfügen Sie über einen Provider mit einem Modellkatalog, API-Schlüssel-Authentifizierung und dynamischer Modellauflösung.
Exemplarische Anleitung
Paket und Manifest
Schritt 1: Paket und 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}}Das Manifest deklariert providerAuthEnvVars, damit OpenClaw
Zugangsdaten erkennen kann, ohne die Runtime Ihres Plugins zu laden. Fügen Sie providerAuthAliases
hinzu, wenn eine Provider-Variante die Authentifizierung einer anderen Provider-ID wiederverwenden soll. modelSupport
ist optional und ermöglicht OpenClaw, Ihr Provider-Plugin aus Kurzform-
Modell-IDs wie acme-large automatisch zu laden, bevor Runtime-Hooks vorhanden sind. Wenn Sie den
Provider auf ClawHub veröffentlichen, sind diese Felder openclaw.compat und openclaw.build
in package.json erforderlich.
Provider registrieren
Ein minimaler Text-Provider benötigt eine id, ein label, auth und catalog.
catalog ist der providerseitige Runtime-/Konfigurations-Hook; er kann Live-
Hersteller-APIs aufrufen und gibt models.providers-Einträge zurück.
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 ist die neuere Control-Plane-Katalogoberfläche
für Listen-, Hilfe- und Auswahl-UI. Verwenden Sie sie für Zeilen zu Text, Bilderzeugung,
Videoerzeugung und Musikerzeugung. Belassen Sie Hersteller-Endpunktaufrufe und
Antwortzuordnung im Plugin; OpenClaw besitzt die gemeinsame Zeilenform, Quell-
Labels und Hilfe-Rendering.
Damit ist ein funktionsfähiger Provider vorhanden. Benutzer können nun
openclaw onboard --acme-ai-api-key <key> ausführen und
acme-ai/acme-large als Modell auswählen.
Wenn der vorgelagerte Provider andere Steuerungstokens als OpenClaw verwendet, fügen Sie eine kleine bidirektionale Texttransformation hinzu, anstatt den Stream-Pfad zu ersetzen:
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 schreibt den endgültigen System-Prompt und den Inhalt von Textnachrichten vor
dem Transport um. output schreibt Assistenten-Text-Deltas und den endgültigen Text um,
bevor OpenClaw seine eigenen Steuerungsmarker oder die Kanalauslieferung parst.
Für gebündelte Provider, die nur einen Text-Provider mit API-Schlüssel-
Authentifizierung sowie einer einzelnen kataloggestützten Runtime registrieren, verwenden Sie bevorzugt den enger gefassten
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 ist der Live-Katalogpfad, der verwendet wird, wenn OpenClaw echte
Provider-Authentifizierung auflösen kann. Er kann providerspezifische Erkennung durchführen. Verwenden Sie
buildStaticProvider nur für Offline-Zeilen, die vor der Konfiguration der Authentifizierung sicher angezeigt werden können;
er darf keine Zugangsdaten erfordern und keine Netzwerkanfragen stellen.
Die Anzeige models list --all von OpenClaw führt statische Kataloge derzeit
nur für gebündelte Provider-Plugins aus, mit leerer Konfiguration, leerer Umgebung und ohne
Agent-/Workspace-Pfade.
Wenn Ihr Authentifizierungsablauf außerdem models.providers.*, Aliasse und
das Standardmodell des Agents während des Onboardings patchen muss, verwenden Sie die Preset-Helper aus
openclaw/plugin-sdk/provider-onboard. Die engsten Helper sind
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) und
createModelCatalogPresetAppliers(...).
Wenn ein nativer Endpunkt eines Providers gestreamte Nutzungsblöcke über den
normalen openai-completions-Transport unterstützt, verwenden Sie bevorzugt die gemeinsamen Katalog-Helper in
openclaw/plugin-sdk/provider-catalog-shared, anstatt
Provider-ID-Prüfungen fest zu codieren. supportsNativeStreamingUsageCompat(...) und
applyProviderNativeStreamingUsageCompat(...) erkennen Unterstützung anhand der
Endpunkt-Capability-Map, sodass native Endpunkte im Moonshot-/DashScope-Stil weiterhin
opt-in verwenden, selbst wenn ein Plugin eine benutzerdefinierte Provider-ID nutzt.
Dynamische Modellauflösung hinzufügen
Wenn Ihr Provider beliebige Modell-IDs akzeptiert (wie ein Proxy oder Router),
fügen Sie resolveDynamicModel hinzu:
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, }),});Wenn die Auflösung einen Netzwerkaufruf erfordert, verwenden Sie prepareDynamicModel für asynchrone
Vorbereitung - resolveDynamicModel wird nach Abschluss erneut ausgeführt.
Runtime-Hooks hinzufügen (bei Bedarf)
Die meisten Provider benötigen nur catalog + resolveDynamicModel. Fügen Sie Hooks
schrittweise hinzu, wenn Ihr Provider sie benötigt.
Gemeinsame Helper-Builder decken inzwischen die häufigsten Replay-/Tool-Kompatibilitäts- Familien ab, sodass Plugins normalerweise nicht jeden Hook einzeln verdrahten müssen:
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,});Heute verfügbare Replay-Familien:
| Familie | Was sie einbindet | Mitgelieferte Beispiele |
|---|---|---|
openai-compatible |
Gemeinsame OpenAI-artige Replay-Policy für OpenAI-kompatible Transporte, einschließlich Bereinigung von Tool-Call-IDs, Korrekturen für Assistant-First-Reihenfolge und generischer Gemini-Turn-Validierung, wo der Transport sie benötigt | moonshot, ollama, xai, zai |
anthropic-by-model |
Claude-bewusste Replay-Policy, ausgewählt per modelId, sodass Anthropic-Message-Transporte Claude-spezifische Thinking-Block-Bereinigung nur erhalten, wenn das aufgelöste Modell tatsächlich eine Claude-ID ist |
amazon-bedrock, anthropic-vertex |
google-gemini |
Native Gemini-Replay-Policy plus Bootstrap-Replay-Bereinigung und Modus für getaggte Reasoning-Ausgabe | google, google-gemini-cli |
passthrough-gemini |
Gemini-Thought-Signature-Bereinigung für Gemini-Modelle, die über OpenAI-kompatible Proxy-Transporte laufen; aktiviert keine native Gemini-Replay-Validierung oder Bootstrap-Neuschreibungen | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Hybride Policy für Provider, die Anthropic-Message- und OpenAI-kompatible Modelloberflächen in einem Plugin mischen; optionales Entfernen von Thinking-Blocks nur für Claude bleibt auf die Anthropic-Seite beschränkt | minimax |
Heute verfügbare Stream-Familien:
| Familie | Was sie einbindet | Mitgelieferte Beispiele |
|---|---|---|
google-thinking |
Normalisierung von Gemini-Thinking-Payloads im gemeinsamen Stream-Pfad | google, google-gemini-cli |
kilocode-thinking |
Kilo-Reasoning-Wrapper im gemeinsamen Proxy-Stream-Pfad, wobei kilo/auto und nicht unterstützte Proxy-Reasoning-IDs injiziertes Thinking überspringen |
kilocode |
moonshot-thinking |
Zuordnung nativer Moonshot-Binary-Thinking-Payloads aus Konfiguration + /think-Stufe |
moonshot |
minimax-fast-mode |
MiniMax-Fast-Mode-Modellumschreibung im gemeinsamen Stream-Pfad | minimax, minimax-portal |
openai-responses-defaults |
Gemeinsame native OpenAI/Codex-Responses-Wrapper: Attributions-Header, /fast/serviceTier, Text-Ausführlichkeit, native Codex-Websuche, Reasoning-kompatible Payload-Formung und Responses-Kontextverwaltung |
openai, openai-codex |
openrouter-thinking |
OpenRouter-Reasoning-Wrapper für Proxy-Routen, wobei Überspringen bei nicht unterstützten Modellen/auto zentral gehandhabt wird |
openrouter |
tool-stream-default-on |
Standardmäßig aktivierter tool_stream-Wrapper für Provider wie Z.AI, die Tool-Streaming wünschen, sofern es nicht explizit deaktiviert ist |
zai |
SDK-Seams, die die Family Builder antreiben
Jeder Family Builder besteht aus öffentlichen Low-Level-Helfern, die aus demselben Paket exportiert werden und auf die Sie zurückgreifen können, wenn ein Provider vom gemeinsamen Muster abweichen muss:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)und die rohen Replay-Builder (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Exportiert außerdem Gemini-Replay-Helfer (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) und Endpunkt-/Modell-Helfer (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...)sowie die gemeinsamen OpenAI/Codex-Wrapper (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), DeepSeek-V4-OpenAI-kompatibler Wrapper (createDeepSeekV4OpenAICompatibleThinkingWrapper), Anthropic-Messages-Thinking-Prefill-Bereinigung (createAnthropicThinkingPrefillPayloadWrapper) und gemeinsame Proxy-/Provider-Wrapper (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini")und zugrunde liegende Gemini-Schema-Helfer (normalizeGeminiToolSchemas,inspectGeminiToolSchemas).
Einige Stream-Helfer bleiben absichtlich providerlokal. @openclaw/anthropic-provider behält wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier und die Low-Level-Anthropic-Wrapper-Builder in seinem eigenen öffentlichen api.ts-/contract-api.ts-Seam, weil sie Claude-OAuth-Beta-Behandlung und context1m-Gating kodieren. Das xAI-Plugin behält ähnlich die native xAI-Responses-Formung in seinem eigenen wrapStreamFn (/fast-Aliasse, Standard-tool_stream, Bereinigung nicht unterstützter Strict-Tools, xAI-spezifisches Entfernen von Reasoning-Payloads).
Dasselbe Package-Root-Muster stützt auch @openclaw/openai-provider (Provider-Builder, Default-Model-Helfer, Realtime-Provider-Builder) und @openclaw/openrouter-provider (Provider-Builder plus Onboarding-/Konfigurationshelfer).
Token-Austausch
Für Provider, die vor jedem Inferenzaufruf einen Token-Austausch benötigen:
prepareRuntimeAuth: async (ctx) => { const exchanged = await exchangeToken(ctx.apiKey); return { apiKey: exchanged.token, baseUrl: exchanged.baseUrl, expiresAt: exchanged.expiresAt, };},Benutzerdefinierte Header
Für Provider, die benutzerdefinierte Request-Header oder Body-Änderungen benötigen:
// 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 transport identity
Für Provider, die native Anfrage-/Sitzungs-Header oder Metadaten auf generischen HTTP- oder WebSocket-Transporten benötigen:
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
Für Provider, die Nutzungs-/Abrechnungsdaten bereitstellen:
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 ruft Hooks in dieser Reihenfolge auf. Die meisten Provider verwenden nur 2-3:
Nur der Kompatibilität dienende Provider-Felder, die OpenClaw nicht mehr aufruft, wie
ProviderPlugin.capabilities und suppressBuiltInModel, sind hier nicht aufgeführt.
| # | Hook | Wann verwenden |
|---|---|---|
| 1 | catalog |
Modellkatalog oder Standardwerte für die Basis-URL |
| 2 | applyConfigDefaults |
Provider-eigene globale Standardwerte während der Konfigurationsmaterialisierung |
| 3 | normalizeModelId |
Bereinigung von Aliasen für Legacy-/Preview-Modell-IDs vor dem Lookup |
| 4 | normalizeTransport |
Bereinigung von api / baseUrl der Provider-Familie vor der generischen Modellassemblierung |
| 5 | normalizeConfig |
models.providers.<id>-Konfiguration normalisieren |
| 6 | applyNativeStreamingUsageCompat |
Kompatibilitäts-Umschreibungen für native Streaming-Nutzung bei Konfigurations-Providern |
| 7 | resolveConfigApiKey |
Provider-eigene Authentifizierungsauflösung für Env-Marker |
| 8 | resolveSyntheticAuth |
Lokale/selbst gehostete oder konfigurationsgestützte synthetische Authentifizierung |
| 9 | shouldDeferSyntheticProfileAuth |
Synthetische Platzhalter für gespeicherte Profile hinter Env-/Konfigurationsauthentifizierung zurückstufen |
| 10 | resolveDynamicModel |
Beliebige Upstream-Modell-IDs akzeptieren |
| 11 | prepareDynamicModel |
Asynchroner Metadatenabruf vor der Auflösung |
| 12 | normalizeResolvedModel |
Transport-Umschreibungen vor dem Runner |
| 13 | contributeResolvedModelCompat |
Kompatibilitäts-Flags für Vendor-Modelle hinter einem anderen kompatiblen Transport |
| 14 | normalizeToolSchemas |
Provider-eigene Bereinigung von Tool-Schemata vor der Registrierung |
| 15 | inspectToolSchemas |
Provider-eigene Diagnosen für Tool-Schemata |
| 16 | resolveReasoningOutputMode |
Vertrag für getaggte vs. native Reasoning-Ausgabe |
| 17 | prepareExtraParams |
Standard-Anfrageparameter |
| 18 | createStreamFn |
Vollständig benutzerdefinierter StreamFn-Transport |
| 19 | wrapStreamFn |
Benutzerdefinierte Header-/Body-Wrapper auf dem normalen Stream-Pfad |
| 20 | resolveTransportTurnState |
Native Header/Metadaten pro Turn |
| 21 | resolveWebSocketSessionPolicy |
Native WS-Sitzungs-Header/Cool-down |
| 22 | formatApiKey |
Benutzerdefinierte Runtime-Token-Form |
| 23 | refreshOAuth |
Benutzerdefinierte OAuth-Aktualisierung |
| 24 | buildAuthDoctorHint |
Anleitung zur Authentifizierungsreparatur |
| 25 | matchesContextOverflowError |
Provider-eigene Overflow-Erkennung |
| 26 | classifyFailoverReason |
Provider-eigene Klassifizierung von Rate-Limits/Überlastung |
| 27 | isCacheTtlEligible |
Prompt-Cache-TTL-Gating |
| 28 | buildMissingAuthMessage |
Benutzerdefinierter Hinweis bei fehlender Authentifizierung |
| 29 | augmentModelCatalog |
Synthetische Forward-Compat-Zeilen |
| 30 | resolveThinkingProfile |
Modellspezifisches /think-Optionsset |
| 31 | isBinaryThinking |
Binäre Thinking-Ein/Aus-Kompatibilität |
| 32 | supportsXHighThinking |
Kompatibilität für xhigh-Reasoning-Unterstützung |
| 33 | resolveDefaultThinkingLevel |
Kompatibilität der Standard-/think-Richtlinie |
| 34 | isModernModelRef |
Live-/Smoke-Modellabgleich |
| 35 | prepareRuntimeAuth |
Token-Austausch vor der Inferenz |
| 36 | resolveUsageAuth |
Benutzerdefiniertes Parsen von Nutzungsanmeldedaten |
| 37 | fetchUsageSnapshot |
Benutzerdefinierter Nutzungs-Endpunkt |
| 38 | createEmbeddingProvider |
Provider-eigener Embedding-Adapter für Speicher/Suche |
| 39 | buildReplayPolicy |
Benutzerdefinierte Richtlinie für Transcript-Replay/Compaction |
| 40 | sanitizeReplayHistory |
Provider-spezifische Replay-Umschreibungen nach generischer Bereinigung |
| 41 | validateReplayTurns |
Strikte Replay-Turn-Validierung vor dem eingebetteten Runner |
| 42 | onModelSelected |
Callback nach der Auswahl (z. B. Telemetrie) |
Hinweise zu Runtime-Fallbacks:
normalizeConfigprüft zuerst den passenden Provider und dann andere hook-fähige Provider-Plugins, bis eines die Konfiguration tatsächlich ändert. Wenn kein Provider-Hook einen unterstützten Google-Family-Konfigurationseintrag umschreibt, greift weiterhin der gebündelte Google-Konfigurationsnormalisierer.resolveConfigApiKeyverwendet den Provider-Hook, wenn er bereitgestellt wird. Der gebündelteamazon-bedrock-Pfad hat hier außerdem einen integrierten AWS-Env-Marker-Resolver, obwohl die Bedrock-Runtime-Authentifizierung selbst weiterhin die Standardkette des AWS SDK verwendet.resolveSystemPromptContributionermöglicht einem Provider, cache-bewusste System-Prompt-Anleitung für eine Modellfamilie einzuschleusen. Bevorzugen Sie dies gegenüberbefore_prompt_build, wenn das Verhalten zu einem Provider/einer Modellfamilie gehört und die stabile/dynamische Cache-Aufteilung erhalten soll.
Detaillierte Beschreibungen und Beispiele aus der Praxis finden Sie unter Interna: Provider-Runtime-Hooks.
Add extra capabilities (optional)
Schritt 5: Zusätzliche Fähigkeiten hinzufügen
Ein Provider-Plugin kann Sprachausgabe, Echtzeittranskription, Echtzeit-Voice, Medienverständnis, Bildgenerierung, Videogenerierung, Web-Abruf und Websuche neben Textinferenz registrieren. OpenClaw klassifiziert dies als Hybrid-Capability-Plugin - das empfohlene Muster für Unternehmens-Plugins (ein Plugin pro Anbieter). Siehe Interna: Capability-Zuständigkeit.
Registrieren Sie jede Capability in register(api) neben Ihrem bestehenden api.registerProvider(...)-Aufruf. Wählen Sie nur die Tabs aus, die Sie benötigen:
Sprachausgabe (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(); } },});Verwenden Sie assertOkOrThrowProviderError(...) für HTTP-Fehler von Providern, damit Plugins begrenzte Fehlertext-Lesevorgänge, JSON-Fehleranalyse und Request-ID-Suffixe gemeinsam nutzen.
Echtzeittranskription
Bevorzugen Sie createRealtimeTranscriptionWebSocketSession(...) - der gemeinsame Helper übernimmt Proxy-Erfassung, Reconnect-Backoff, Flush beim Schließen, Ready-Handshakes, Audio-Queueing und Diagnosen für Close-Events. Ihr Plugin ordnet nur Upstream-Events zu.
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-Provider, die Multipart-Audio per POST senden, sollten buildAudioTranscriptionFormData(...) aus openclaw/plugin-sdk/provider-http verwenden. Der Helper normalisiert Upload-Dateinamen, einschließlich AAC-Uploads, die für kompatible Transkriptions-APIs einen Dateinamen im M4A-Stil benötigen.
Echtzeit-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, }),});Deklarieren Sie capabilities, damit talk.catalog gültige Modi, Transports, Audioformate und Feature-Flags für Browser- und native Talk-Clients verfügbar machen kann. Implementieren Sie handleBargeIn, wenn ein Transport erkennen kann, dass ein Mensch die Wiedergabe des Assistenten unterbricht und der Provider das Kürzen oder Löschen der aktiven Audioantwort unterstützt.
Medienverständnis
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "A photo of..." }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});Bild- und Videogenerierung
Video-Capabilities verwenden eine modusbewusste Struktur: generate, imageToVideo und videoToVideo. Flache Aggregatfelder wie maxInputImages / maxInputVideos / maxDurationSeconds reichen nicht aus, um Unterstützung für Transformationsmodi oder deaktivierte Modi sauber auszuweisen. Musikgenerierung folgt demselben Muster mit expliziten generate- / edit-Blöcken.
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-Abruf und Suche
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
Schritt 6: Testen
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(); });});In ClawHub veröffentlichen
Provider-Plugins werden genauso veröffentlicht wie jedes andere externe Code-Plugin:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginVerwenden Sie hier nicht den alten Alias nur für Skills; Plugin-Pakete sollten clawhub package publish verwenden.
Dateistruktur
<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)Referenz zur Katalogreihenfolge
catalog.order steuert, wann Ihr Katalog relativ zu integrierten Providern zusammengeführt wird:
| Reihenfolge | Zeitpunkt | Anwendungsfall |
|---|---|---|
simple |
Erster Durchlauf | Einfache API-Key-Provider |
profile |
Nach simple | Provider, die durch Auth-Profile gesteuert sind |
paired |
Nach profile | Mehrere verwandte Einträge synthetisieren |
late |
Letzter Durchlauf | Bestehende Provider überschreiben (gewinnt bei Kollision) |
Nächste Schritte
- Channel-Plugins - wenn Ihr Plugin auch einen Channel bereitstellt
- SDK Runtime -
api.runtime-Helper (TTS, Suche, Subagent) - SDK-Überblick - vollständige Referenz für Subpath-Imports
- Plugin-Interna - Hook-Details und gebündelte Beispiele