Building plugins
Provider-Plugins erstellen
Erstellen Sie ein Provider-Plugin, um OpenClaw einen Modell-Provider (LLM) hinzuzufügen: einen Modellkatalog, API-Schlüssel-Authentifizierung und dynamische Modellauflösung.
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-Modell-Provider","providers": ["acme-ai"],"modelSupport": { "modelPrefixes": ["acme-"]},"setup": { "providers": [ { "id": "acme-ai", "envVars": ["ACME_AI_API_KEY"] } ]},"providerAuthAliases": { "acme-ai-coding": "acme-ai"},"providerAuthChoices": [ { "provider": "acme-ai", "method": "api-key", "choiceId": "acme-ai-api-key", "choiceLabel": "Acme-AI-API-Schlüssel", "groupId": "acme-ai", "groupLabel": "Acme AI", "cliFlag": "--acme-ai-api-key", "cliOption": "--acme-ai-api-key <key>", "cliDescription": "Acme-AI-API-Schlüssel" }],"configSchema": { "type": "object", "additionalProperties": false}}Mit setup.providers[].envVars kann OpenClaw Zugangsdaten erkennen, ohne
die Laufzeit 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 anhand verkürzter
Modell-IDs wie acme-large automatisch zu laden, bevor Laufzeit-Hooks vorhanden sind. openclaw.compat
und openclaw.build in package.json sind für die Veröffentlichung auf ClawHub
erforderlich (openclaw.compat.pluginApi und openclaw.build.openclawVersion
sind die beiden Pflichtfelder; minGatewayVersion greift bei Auslassung auf
openclaw.install.minHostVersion zurück).
Provider registrieren
Ein minimaler Text-Provider benötigt id, label, auth und catalog.
catalog ist der vom Provider verwaltete Laufzeit-/Konfigurations-Hook; er kann aktive
Anbieter-APIs aufrufen und Einträge für models.providers zurückgeben.
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 Katalogoberfläche der Steuerungsebene
für Listen-, Hilfe- und Auswahloberflächen und deckt Zeilen der Typen text, voice, image_generation,
video_generation und music_generation ab. Belassen Sie Aufrufe von Anbieter-Endpunkten
und die Zuordnung der Antworten im Plugin; OpenClaw verwaltet die gemeinsame Zeilenstruktur,
Quellenbezeichnungen und die Darstellung der Hilfe.
Damit ist der Provider funktionsfähig. Benutzer können nun
openclaw onboard --acme-ai-api-key <key> ausführen und
acme-ai/acme-large als Modell auswählen.
Dynamische Modellerkennung
Wenn Ihr Provider eine API im Stil von /models bereitstellt, belassen Sie den Provider-spezifischen
Endpunkt und die Zeilenprojektion in Ihrem Plugin und verwenden Sie
openclaw/plugin-sdk/provider-catalog-live-runtime für den gemeinsamen Abruf-
Lebenszyklus. Der Helfer bietet Ihnen abgesicherte HTTP-Abrufe, Provider-Authentifizierungsheader,
strukturierte HTTP-Fehler, TTL-Caching und statisches Fallback-Verhalten, ohne
Provider-Richtlinien in den OpenClaw-Core zu integrieren.
Verwenden Sie buildLiveModelProviderConfig, wenn die Live-API Ihnen nur mitteilt, welche
vom Provider verwalteten statischen Katalogzeilen derzeit verfügbar sind:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";import { buildLiveModelProviderConfig, type LiveModelCatalogFetchGuard,} from "openclaw/plugin-sdk/provider-catalog-live-runtime"; const STATIC_MODELS = [ { id: "acme-large", name: "Acme Large", reasoning: true, input: ["text", "image"], cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, contextWindow: 200000, maxTokens: 32768, }, { id: "acme-small", name: "Acme Small", reasoning: false, input: ["text"], cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 }, contextWindow: 128000, maxTokens: 8192, },] as const; async function buildAcmeLiveProvider(params: { apiKey: string; discoveryApiKey?: string; fetchGuard?: LiveModelCatalogFetchGuard;}) { return await buildLiveModelProviderConfig({ providerId: "acme-ai", endpoint: "https://api.acme-ai.com/v1/models", providerConfig: { baseUrl: "https://api.acme-ai.com/v1", api: "openai-completions", }, models: STATIC_MODELS, apiKey: params.apiKey, discoveryApiKey: params.discoveryApiKey, fetchGuard: params.fetchGuard, ttlMs: 60_000, auditContext: "acme-ai-model-discovery", });} export default definePluginEntry({ id: "acme-ai", name: "Acme AI", register(api) { api.registerProvider({ id: "acme-ai", label: "Acme AI", catalog: { order: "simple", run: async (ctx) => { const auth = ctx.resolveProviderAuth("acme-ai"); const apiKey = auth.apiKey ?? ctx.resolveProviderApiKey("acme-ai").apiKey; if (!apiKey) return null; return { provider: await buildAcmeLiveProvider({ apiKey, discoveryApiKey: auth.discoveryApiKey, }), }; }, }, staticCatalog: { order: "simple", run: async () => ({ provider: { baseUrl: "https://api.acme-ai.com/v1", api: "openai-completions", models: [...STATIC_MODELS], }, }), }, }); },});Verwenden Sie getCachedLiveProviderModelRows, wenn die Provider-API umfangreichere
Metadaten zurückgibt und das Plugin die Zeilen selbst in OpenClaw-
Modelldefinitionen projizieren muss:
import { getCachedLiveProviderModelRows, LiveModelCatalogHttpError,} from "openclaw/plugin-sdk/provider-catalog-live-runtime"; async function discoverAcmeModels(apiKey: string) { try { const rows = await getCachedLiveProviderModelRows({ providerId: "acme-ai", endpoint: "https://api.acme-ai.com/v1/models", apiKey, ttlMs: 60_000, auditContext: "acme-ai-model-discovery", }); return rows .map((row) => projectAcmeModel(row)) .filter((model) => model !== null); } catch (error) { if (error instanceof LiveModelCatalogHttpError) { return STATIC_MODELS; } throw error; }}run sollte weiterhin durch die Authentifizierung geschützt sein und null zurückgeben, wenn keine verwendbaren Zugangsdaten
verfügbar sind. Behalten Sie ein Offline-staticRun oder einen statischen Fallback bei, damit Einrichtung, Dokumentation,
Tests und Auswahloberflächen nicht von einem aktiven Netzwerkzugriff abhängen. Verwenden Sie eine TTL,
die für die Aktualität der Modellliste angemessen ist, vermeiden Sie Dateisystemabfragen zur Anfragezeit
und übergeben Sie ein Provider-spezifisches readRows / readModelId nur, wenn die
vorgelagerte Antwort nicht die OpenAI-kompatible Struktur { data: [{ id, object }] }
aufweist.
Wenn der vorgelagerte Provider andere Steuerungstoken als OpenClaw verwendet, fügen Sie eine kleine bidirektionale Texttransformation hinzu, statt 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 Text-Deltas des Assistenten und den endgültigen Text um, bevor
OpenClaw seine eigenen Steuerungsmarker analysiert oder die Zustellung an den Kanal durchführt.
Für gebündelte Provider, die nur einen Text-Provider mit API-Schlüssel-
Authentifizierung und einer einzelnen kataloggestützten Laufzeit registrieren, verwenden Sie vorzugsweise den spezielleren
Helfer 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 eine echte
Provider-Authentifizierung auflösen kann. Er kann eine Provider-spezifische Erkennung
durchführen. Verwenden Sie buildStaticProvider nur für Offline-Einträge, die vor der
Konfiguration der Authentifizierung sicher angezeigt werden können; er darf weder
Anmeldedaten erfordern noch Netzwerkanfragen stellen. Die Anzeige von OpenClaws
models list --all führt statische Kataloge derzeit nur für mitgelieferte
Provider-Plugins aus, mit einer leeren Konfiguration, einer leeren Umgebung und ohne
Agent-/Arbeitsbereichspfade.
Wenn Ihr Authentifizierungsablauf während des Onboardings außerdem
models.providers.*, Aliasse und das Standardmodell des Agenten aktualisieren muss,
verwenden Sie die Voreinstellungshelfer aus
openclaw/plugin-sdk/provider-onboard. Die spezifischsten Helfer sind
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) und
createModelCatalogPresetAppliers(...).
Wenn der native Endpunkt eines Providers gestreamte Nutzungsblöcke über den
normalen openai-completions-Transport unterstützt, verwenden Sie vorzugsweise die
gemeinsamen Kataloghelfer in
openclaw/plugin-sdk/provider-catalog-shared, anstatt Prüfungen der
Provider-ID fest zu codieren. supportsNativeStreamingUsageCompat(...) und
applyProviderNativeStreamingUsageCompat(...) erkennen die Unterstützung anhand
der Endpunkt-Fähigkeitszuordnung, sodass native Endpunkte im Stil von
Moonshot/DashScope weiterhin aktiviert werden, selbst wenn ein Plugin eine
benutzerdefinierte Provider-ID verwendet.
Die obigen Beispiele für die Live-Erkennung decken Provider-APIs im Stil von
/models ab. Belassen Sie diese Erkennung innerhalb von catalog.run, beschränkt
auf verwendbare Authentifizierung, und halten Sie staticRun für die
Offline-Kataloggenerierung netzwerkfrei.
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 die asynchrone Vorbereitung – resolveDynamicModel
wird nach deren Abschluss erneut ausgeführt.
Runtime-Hooks hinzufügen (nach 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 nun die häufigsten Familien für Replay-/Tool-Kompatibilität ab, sodass Plugins normalerweise nicht mehr jeden Hook einzeln manuell 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,});Derzeit verfügbare Replay-Familien:
| Familie | Eingebundene Funktionen | Mitgelieferte Beispiele |
|---|---|---|
openai-compatible |
Gemeinsame Replay-Richtlinie im OpenAI-Stil für OpenAI-kompatible Transporte, einschließlich Bereinigung von Tool-Aufruf-IDs, Korrekturen der Reihenfolge mit Assistent zuerst und generischer Validierung von Gemini-Zügen, wo der Transport sie benötigt | moonshot, ollama, xai, zai |
anthropic-by-model |
An Claude angepasste Replay-Richtlinie, die anhand von modelId ausgewählt wird, sodass Transporte für Anthropic-Nachrichten die Claude-spezifische Bereinigung von Denkblöcken nur erhalten, wenn das aufgelöste Modell tatsächlich eine Claude-ID ist |
amazon-bedrock |
native-anthropic-by-model |
Dieselbe modellabhängige Claude-Richtlinie wie anthropic-by-model, ergänzt um die Bereinigung von Tool-Aufruf-IDs und die Beibehaltung nativer Anthropic-Tool-Nutzungs-IDs für Transporte, die herstellernative IDs beibehalten müssen |
anthropic-vertex, clawrouter |
google-gemini |
Native Gemini-Replay-Richtlinie plus Bereinigung des Bootstrap-Replays. Die gemeinsame Familie belässt die textausgebende Gemini CLI beim markierten Reasoning; der direkte Provider google überschreibt resolveReasoningOutputMode mit native, da das Denken der Gemini API als native Gedankenteile eintrifft. |
google, google-gemini-cli |
passthrough-gemini |
Bereinigung von Gemini-Gedankensignaturen für Gemini-Modelle, die über OpenAI-kompatible Proxy-Transporte ausgeführt werden; aktiviert weder die native Gemini-Replay-Validierung noch Bootstrap-Umschreibungen | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Hybride Richtlinie für Provider, die Oberflächen für Anthropic-Nachrichten und OpenAI-kompatible Modelle in einem Plugin kombinieren; das optionale Verwerfen von Denkblöcken ausschließlich für Claude bleibt auf die Anthropic-Seite beschränkt | minimax |
Derzeit verfügbare Stream-Familien:
| Familie | Eingebundene Funktionen | Mitgelieferte Beispiele |
|---|---|---|
google-thinking |
Normalisierung von Gemini-Denkinhalten 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 das eingefügte Denken überspringen |
kilocode |
moonshot-thinking |
Zuordnung binärer nativer Moonshot-Denkinhalte aus Konfiguration + /think-Stufe |
moonshot |
minimax-fast-mode |
Umschreiben des MiniMax-Fast-Mode-Modells im gemeinsamen Stream-Pfad | minimax, minimax-portal |
openai-responses-defaults |
Gemeinsame native OpenAI-/Codex-Responses-Wrapper: Zuordnungs-Header, /fast/serviceTier, Textausführlichkeit, native Codex-Websuche, Reasoning-Kompatibilitätsaufbereitung der Nutzlast und Responses-Kontextverwaltung |
openai |
openrouter-thinking |
OpenRouter-Reasoning-Wrapper für Proxy-Routen, wobei das Überspringen nicht unterstützter Modelle/auto zentral behandelt wird |
openrouter |
tool-stream-default-on |
Standardmäßig aktivierter tool_stream-Wrapper für Provider wie Z.AI, die Tool-Streaming verwenden möchten, sofern es nicht ausdrücklich deaktiviert ist |
zai |
SDK-Schnittstellen hinter den Familien-Buildern
Jeder Familien-Builder setzt sich aus öffentlichen Helfern niedrigerer Ebene zusammen, die aus demselben Paket exportiert werden und auf die Sie zurückgreifen können, wenn ein Provider vom üblichen Muster abweichen muss:
openclaw/plugin-sdk/provider-model-shared–ProviderReplayFamily,buildProviderReplayFamilyHooks(...)und die direkten Replay-Builder (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Exportiert außerdem Gemini-Replay-Helfer (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) und Endpunkt-/Modellhelfer (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId).openclaw/plugin-sdk/provider-stream–ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...)sowie die gemeinsamen OpenAI-/Codex-Wrapper (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), der OpenAI-kompatible DeepSeek-V4-Wrapper (createDeepSeekV4OpenAICompatibleThinkingWrapper), die Bereinigung der Denkvorbelegung für Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper), die Kompatibilität für Tool-Aufrufe im Klartext (createPlainTextToolCallCompatWrapper) und gemeinsame Proxy-/Provider-Wrapper (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-stream-shared– schlanke Nutzlast- und Ereignis-Wrapper für häufig ausgeführte Provider-Pfade, daruntercreateOpenAICompatibleCompletionsThinkingOffWrapper,createPayloadPatchStreamWrapper,createPlainTextToolCallCompatWrapper,normalizeOpenAICompatibleReasoningPayload(...)undsetQwenChatTemplateThinking(...).openclaw/plugin-sdk/provider-tools–ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")und die zugrunde liegenden Provider-Schemahelfer.
Halten Sie bei Providern der Gemini-Familie den Ausgabemodus für Reasoning
auf den Transport abgestimmt. Direkte Provider für die Google Gemini API sollten
die Reasoning-Ausgabe native verwenden, damit OpenClaw native Gedankenteile
verarbeitet, ohne die Prompt-Direktiven <think> / <final> hinzuzufügen.
Reine Text-Backends im Stil der Gemini CLI, die eine abschließende
JSON-/Textantwort parsen, können den gemeinsamen markierten Vertrag
google-gemini beibehalten.
Einige Stream-Helfer bleiben absichtlich Provider-lokal. @openclaw/anthropic-provider
behält wrapAnthropicProviderStream, resolveAnthropicBetas,
resolveAnthropicFastMode, resolveAnthropicServiceTier und die
Anthropic-Wrapper-Builder niedrigerer Ebene in seiner eigenen öffentlichen
api.ts-/contract-api.ts-Schnittstelle, da sie die Verarbeitung von
Claude-OAuth-Betaversionen und die Beschränkung von context1m abbilden. Das
xAI-Plugin behält die native Aufbereitung von xAI Responses auf ähnliche Weise
in seinem eigenen wrapStreamFn (/fast-Aliasse, standardmäßiges
tool_stream, Bereinigung nicht unterstützter strikter Tools, xAI-spezifische
Entfernung der Reasoning-Nutzlast).
Dasselbe Muster auf Paketstammebene bildet auch die Grundlage für
@openclaw/openai-provider (Provider-Builder, Standardmodell-Helfer,
Echtzeit-Provider-Builder) und @openclaw/openrouter-provider
(Provider-Builder sowie 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 Anfrage-Header oder Änderungen am Anfragekörper 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 Transportidentität
Für Provider, die native Anfrage-/Sitzungs-Header oder Metadaten für generische HTTP- oder WebSocket-Transporte 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,}),Nutzung und Abrechnung
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);},resolveUsageAuth hat drei mögliche Ergebnisse. Geben Sie
{ token, accountId?, subscriptionType?, rateLimitTier? } zurück, wenn der
Provider über Anmeldedaten für Nutzung/Abrechnung verfügt (die optionalen Felder
übertragen nicht geheime Tarifmetadaten aus dem aufgelösten Profil an
fetchUsageSnapshot). Geben Sie
{ handled: true } nur zurück, wenn der Provider die Authentifizierung für die
Nutzung definitiv verarbeitet hat, aber über kein verwendbares Nutzungstoken
verfügt und OpenClaw den generischen API-Schlüssel-/OAuth-Fallback überspringen
muss. Geben Sie null oder undefined zurück, wenn der Provider die Anfrage
nicht verarbeitet hat und OpenClaw mit dem generischen Fallback fortfahren soll.
Deklarieren Sie die Provider-ID in contracts.usageProviders. Wenn dieser
Manifest-Vertrag und beide Hooks vorhanden sind, nimmt OpenClaw den
Provider automatisch in die Nutzungserfassung auf, ohne nicht zugehörige
Provider-Plugins zu laden. Eine Aktualisierung der Allowlist im Kern ist nicht
erforderlich.
fetchUsageSnapshot gibt die gemeinsame providerneutrale Struktur zurück:
plan: vom Provider gemeldete Abonnement- oder Schlüsselbezeichnungwindows: zurücksetzbare Kontingentzeitfenster als verwendete Prozentwertebilling: typisierte Einträge fürbalance,spendoderbudget;unitkann eine ISO-Währung oder eine Provider-Einheit wiecreditsseinsummary: kompakter providerspezifischer Kontext, der nicht in diese strukturierten Felder passt
Behalten Sie die Währungssemantik exakt bei. Ein Provider-Guthaben entspricht
nicht USD, sofern der Upstream-Vertrag dies nicht ausdrücklich festlegt. Ein
Plugin, das nur fetchUsageSnapshot implementiert, bleibt für explizite/synthetische
Aufrufer verfügbar, wird aber nicht automatisch erkannt, da OpenClaw seine
Anmeldedaten für die Nutzung nicht auflösen kann.
Allgemeine Provider-Hooks
OpenClaw ruft Hooks für Modell-/Provider-Plugins ungefähr in dieser Reihenfolge auf.
Die meisten Provider verwenden nur 2-3. Dies ist nicht der vollständige Vertrag
von ProviderPlugin – die vollständige, derzeit aktuelle Hook-Liste und Hinweise
zu Fallbacks finden Sie unter Interna: Provider-Laufzeit-
Hooks.
Provider-Felder, die nur der Kompatibilität dienen und von OpenClaw nicht mehr
aufgerufen werden, beispielsweise ProviderPlugin.capabilities und
suppressBuiltInModel, sind hier nicht aufgeführt.
| Hook | Verwendungszweck |
|---|---|
catalog |
Modellkatalog oder Standardwerte für die Basis-URL |
applyConfigDefaults |
Providereigene globale Standardwerte bei der Materialisierung der Konfiguration |
normalizeModelId |
Bereinigung von Aliasen für ältere/Vorschau-Modell-IDs vor der Suche |
normalizeTransport |
Bereinigung von api / baseUrl für die Provider-Familie vor der generischen Modellzusammenstellung |
normalizeConfig |
Konfiguration models.providers.<id> normalisieren |
applyNativeStreamingUsageCompat |
Native Kompatibilitätsumschreibungen für die Streaming-Nutzung bei konfigurierten Providern |
resolveConfigApiKey |
Providereigene Auflösung der Authentifizierung über Umgebungsmarker |
resolveSyntheticAuth |
Synthetische Authentifizierung für lokale/selbst gehostete oder konfigurationsgestützte Umgebungen |
resolveExternalAuthProfiles |
Providereigene externe Authentifizierungsprofile für CLI-/App-verwaltete Anmeldedaten überlagern |
shouldDeferSyntheticProfileAuth |
Synthetische Platzhalter gespeicherter Profile hinter Umgebungs-/Konfigurationsauthentifizierung zurückstufen |
resolveDynamicModel |
Beliebige Upstream-Modell-IDs akzeptieren |
prepareDynamicModel |
Asynchroner Metadatenabruf vor der Auflösung |
normalizeResolvedModel |
Transportumschreibungen vor dem Runner |
normalizeToolSchemas |
Providereigene Bereinigung von Tool-Schemas vor der Registrierung |
inspectToolSchemas |
Providereigene Diagnose von Tool-Schemas |
resolveReasoningOutputMode |
Vertrag für markierte gegenüber nativer Reasoning-Ausgabe |
prepareExtraParams |
Standardmäßige Anfrageparameter |
createStreamFn |
Vollständig benutzerdefinierter StreamFn-Transport |
wrapStreamFn |
Benutzerdefinierte Header-/Body-Wrapper im normalen Stream-Pfad |
resolveTransportTurnState |
Native Header/Metadaten pro Turn |
resolveWebSocketSessionPolicy |
Native WS-Sitzungsheader/-Abkühlzeit |
formatApiKey |
Benutzerdefinierte Laufzeit-Tokenstruktur |
refreshOAuth |
Benutzerdefinierte OAuth-Aktualisierung |
buildAuthDoctorHint |
Anleitung zur Reparatur der Authentifizierung |
matchesContextOverflowError |
Providereigene Erkennung von Kontextüberläufen |
classifyFailoverReason |
Providereigene Klassifizierung von Ratenbegrenzung/Überlastung |
isCacheTtlEligible |
TTL-Steuerung für den Prompt-Cache |
buildMissingAuthMessage |
Benutzerdefinierter Hinweis bei fehlender Authentifizierung |
augmentModelCatalog |
Synthetische Zeilen für Vorwärtskompatibilität (veraltet – bevorzugen Sie registerModelCatalogProvider) |
resolveThinkingProfile |
Modellspezifischer Optionssatz für /think |
isBinaryThinking |
Kompatibilität für binäres Ein-/Ausschalten des Denkens (veraltet – bevorzugen Sie resolveThinkingProfile) |
supportsXHighThinking |
Kompatibilität für xhigh-Reasoning-Unterstützung (veraltet – bevorzugen Sie resolveThinkingProfile) |
resolveDefaultThinkingLevel |
Kompatibilität für die standardmäßige /think-Richtlinie (veraltet – bevorzugen Sie resolveThinkingProfile) |
isModernModelRef |
Modellabgleich für Live-/Smoke-Tests |
prepareRuntimeAuth |
Tokenaustausch vor der Inferenz |
resolveUsageAuth |
Benutzerdefinierte Verarbeitung von Anmeldedaten für die Nutzung |
fetchUsageSnapshot |
Benutzerdefinierter Nutzungsendpunkt |
createEmbeddingProvider |
Providereigener Embedding-Adapter für Speicher/Suche |
buildReplayPolicy |
Benutzerdefinierte Richtlinie für Transkriptwiedergabe/Compaction |
sanitizeReplayHistory |
Providerspezifische Umschreibungen der Wiedergabe nach der generischen Bereinigung |
validateReplayTurns |
Strikte Validierung der Wiedergabe-Turns vor dem eingebetteten Runner |
onModelSelected |
Rückruf nach der Auswahl (z. B. Telemetrie) |
Hinweise zu Laufzeit-Fallbacks:
normalizeConfigermittelt pro Provider-ID ein zuständiges Plugin (zuerst gebündelte Provider, dann das passende Laufzeit-Plugin) und ruft ausschließlich diesen Hook auf – andere Provider werden nicht durchsucht. Googles eigenernormalizeConfig-Hook normalisiert die Konfigurationseinträgegoogle/google-vertex/google-antigravity; dies ist kein separater Kern-Fallback.resolveConfigApiKeyverwendet den Provider-Hook, wenn dieser bereitgestellt wird. Amazon Bedrock behält die Auflösung von AWS-Umgebungsmarkern in seinem Provider-Plugin; die Laufzeitauthentifizierung selbst verwendet bei einer Konfiguration mitauth: "aws-sdk"weiterhin die Standardkette des AWS SDK.resolveThinkingProfile(ctx)erhält den ausgewähltenprovider, diemodelId, einen optional zusammengeführtenreasoning-Kataloghinweis und optional zusammengeführtecompat-Fakten des Modells. Verwenden Siecompatnur zur Auswahl der Denkoberfläche/des Denkprofils des Providers.resolveSystemPromptContributionermöglicht es einem Provider, cachebewusste System-Prompt-Anleitungen für eine Modellfamilie einzufügen. Bevorzugen Sie dies gegenüber dem älteren pluginweiten Hookbefore_prompt_build, wenn das Verhalten zu einer einzelnen Provider-/Modellfamilie gehört und die stabile/dynamische Cache-Aufteilung beibehalten werden soll.
Zusätzliche Fähigkeiten hinzufügen (optional)
Schritt 5: Zusätzliche Fähigkeiten hinzufügen
Ein Provider-Plugin kann neben der Textinferenz Embeddings, Sprache, Echtzeittranskription, Echtzeitstimme, Medienverständnis, Bilderzeugung, Videoerzeugung, Webabruf und Websuche registrieren. OpenClaw klassifiziert dies als Plugin mit hybriden Fähigkeiten – das empfohlene Muster für Unternehmens-Plugins (ein Plugin pro Anbieter). Siehe Interna: Zuständigkeit für Fähigkeiten.
Registrieren Sie jede Fähigkeit innerhalb von register(api) neben Ihrem
vorhandenen Aufruf api.registerProvider(...). Wählen Sie nur die benötigten
Tabs aus:
Sprache (TTS)
import { assertOkOrThrowProviderError, postJsonRequest,} from "openclaw/plugin-sdk/provider-http"; api.registerSpeechProvider({ id: "acme-ai", label: "Acme Speech", defaultTimeoutMs: 120_000, isConfigured: ({ config }) => Boolean(config.messages?.tts), synthesize: async (req) => { const { response, release } = await postJsonRequest({ url: "https://api.example.com/v1/speech", headers: new Headers({ "Content-Type": "application/json" }), body: { text: req.text }, timeoutMs: req.timeoutMs, fetchFn: fetch, auditContext: "acme speech", }); try { await assertOkOrThrowProviderError(response, "Acme Speech API error"); return { audioBuffer: Buffer.from(await response.arrayBuffer()), outputFormat: "mp3", fileExtension: ".mp3", voiceCompatible: false, }; } finally { await release(); } },});Verwenden Sie assertOkOrThrowProviderError(...) bei HTTP-Fehlern des
Providers, damit Plugins begrenzte Fehlertext-Lesevorgänge, die
JSON-Fehlerverarbeitung und Anfrage-ID-Suffixe gemeinsam nutzen.
Echtzeittranskription
Bevorzugen Sie createRealtimeTranscriptionWebSocketSession(...) – der
gemeinsame Helfer übernimmt Proxy-Erfassung, exponentielle Verzögerung bei
erneuten Verbindungen, das Leeren beim Schließen, Bereitschafts-Handshakes,
Audio-Warteschlangen und die Diagnose von Schließereignissen. Ihr Plugin
ordnet lediglich Upstream-Ereignisse 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. Die Hilfsfunktion normalisiert die
Dateinamen von Uploads, einschließlich AAC-Uploads, die für
kompatible Transkriptions-APIs einen Dateinamen im M4A-Stil benötigen.
Echtzeit-Spracheingabe
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, handlesInputAudioBargeIn: true, supportsToolCalls: true, }, isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey), createBridge: (req) => ({ // Legen Sie dies nur fest, wenn der Provider mehrere Tool-Antworten akzeptiert für // ein Aufruf, zum Beispiel eine sofortige „wird bearbeitet“-Antwort, gefolgt von // das Endergebnis. supportsToolResultContinuation: false, connect: async () => {}, sendAudio: () => {}, setMediaTimestamp: () => {}, handleBargeIn: () => {}, submitToolResult: () => {}, acknowledgeMark: () => {}, close: () => {}, isConnected: () => true, }),});Deklarieren Sie capabilities, damit talk.catalog gültige Modi,
Übertragungsarten, Audioformate und Feature-Flags für browserbasierte und native Talk-
Clients bereitstellen kann. Implementieren Sie handleBargeIn, wenn eine Übertragungsart erkennen kann, dass ein
Mensch die Wiedergabe des Assistenten unterbricht, und der Provider das
Kürzen oder Löschen der aktiven Audioantwort unterstützt.
submitToolResult kann für eine synchrone Übermittlung void oder für eine
asynchrone Abschlussgrenze, die die Provider-
Bridge bereitstellen kann, Promise<void> zurückgeben. Gateway-Relay-Sitzungen warten auf dieses Promise, bevor
sie ein endgültiges Ergebnis bestätigen oder den verknüpften Lauf löschen; lehnen Sie es ab, wenn
die Übermittlung fehlschlägt.
Legen Sie supportsToolResultSuppression: false fest, wenn der Provider
options.suppressResponse nicht berücksichtigen kann. OpenClaw vermeidet dann die Unterdrückung für
interne Ergebnisse aus erzwungener Konsultation und Abbruch und lehnt direkte
Anforderungen unterdrückter Ergebnisse ab, statt stillschweigend eine Antwort zu starten.
Nutzer von createRealtimeVoiceBridgeSession können ebenfalls ein
Promise von onToolCall zurückgeben; synchrone Ausnahmen und Ablehnungen werden an
den onError-Callback der Sitzung weitergeleitet.
Legen Sie handlesInputAudioBargeIn nur fest, wenn die VAD des Providers eine
Unterbrechung durch den Aufruf von onClearAudio("barge-in") bestätigt. Provider, die
das Flag weglassen, verwenden die lokale Erkennung von Eingabeaudio-Unterbrechungen von OpenClaw.
Medienverständnis
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "Ein Foto von..." }), transcribeAudio: async (req) => ({ text: "Transkript..." }),});Lokale oder selbst gehostete Medien-Provider, die absichtlich keine
Anmeldedaten erfordern, können resolveAuth bereitstellen und kind: "none"
zurückgeben. OpenClaw behält weiterhin die normale Authentifizierungsprüfung
für Provider bei, die sich nicht ausdrücklich dafür entscheiden. Bestehende
Provider können weiterhin req.apiKey auslesen; neue Provider sollten
req.auth bevorzugen.
api.registerMediaUnderstandingProvider({ id: "local-audio", capabilities: ["audio"], resolveAuth: () => ({ kind: "none", source: "local-audio plugin no-auth", }), transcribeAudio: async (req) => ({ text: "Transkript..." }),});Einbettungen
api.registerEmbeddingProvider({ id: "acme-ai", defaultModel: "acme-embed", transport: "remote", authProviderId: "acme-ai", create: async ({ model }) => ({ provider: { id: "acme-ai", model, dimensions: 1536, embed: async (input) => { const text = typeof input === "string" ? input : input.text; return fetchAcmeEmbedding(text); }, embedBatch: async (inputs) => Promise.all( inputs.map((input) => fetchAcmeEmbedding(typeof input === "string" ? input : input.text), ), ), }, }),});Deklarieren Sie dieselbe ID in contracts.embeddingProviders. Dies ist der
allgemeine Embedding-Vertrag für die wiederverwendbare Vektorgenerierung,
einschließlich der Speichersuche. registerMemoryEmbeddingProvider(...) ist
eine veraltete Kompatibilitätsschnittstelle für bestehende speicherspezifische Adapter.
Bild- und Videogenerierung
Bild- und Videofunktionen verwenden eine modusabhängige Struktur. Bild-
Provider deklarieren erforderliche generate- und edit-Funktionsblöcke;
Video-Provider deklarieren generate, imageToVideo und
videoToVideo. Flache aggregierte Felder wie maxInputImages /
maxInputVideos / maxDurationSeconds reichen nicht aus, um die
Unterstützung von Transformationsmodi oder deaktivierte Modi eindeutig
anzugeben. Die Musikgenerierung folgt demselben Muster aus generate /
edit.
api.registerImageGenerationProvider({ id: "acme-ai", label: "Acme Images", capabilities: { generate: { maxCount: 4, supportsSize: true }, edit: { enabled: false }, }, generateImage: async (req) => ({ images: [] }),}); api.registerVideoGenerationProvider({ id: "acme-ai", label: "Acme Video", defaultTimeoutMs: 600_000, models: ["acme-video", "acme-image-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 }, }, catalogByModel: { "acme-image-video": { modes: ["imageToVideo"], capabilities: { imageToVideo: { enabled: true, maxVideos: 1, maxInputImages: 1, resolutions: ["480P", "720P", "1080P"], supportsResolution: true, }, videoToVideo: { enabled: false }, }, }, }, generateVideo: async (req) => ({ videos: [] }),});capabilities ist für beide Provider-Typen erforderlich; edit und die
Videotransformationsblöcke (imageToVideo, videoToVideo) benötigen immer ein
explizites enabled-Flag.
Verwenden Sie catalogByModel, wenn die statischen Modi oder Fähigkeiten
eines aufgeführten Modells von den Standardeinstellungen des Providers abweichen. Diese Metadaten sorgen dafür, dass
video_generate action=list und Modellkataloge korrekt bleiben, ohne
Provider-Code aufzurufen. Die Ermittlung und Durchsetzung von Fähigkeiten
zur Anfragezeit gehören weiterhin in resolveModelCapabilities und generateVideo; verwenden Sie
nach Möglichkeit für beide Pfade dieselbe Fähigkeitskonstante.
Webabruf und -suche
api.registerWebFetchProvider({ id: "acme-ai-fetch", label: "Acme Fetch", hint: "Seiten über das Rendering-Backend von Acme abrufen.", 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: "Eine Seite über Acme Fetch abrufen.", parameters: {}, execute: async (args) => ({ content: [] }), }),}); api.registerWebSearchProvider({ id: "acme-ai-search", label: "Acme Search", hint: "Das Web über das Such-Backend von Acme durchsuchen.", envVars: ["ACME_SEARCH_API_KEY"], placeholder: "acme-...", signupUrl: "https://acme.example.com/search", credentialPath: "plugins.entries.acme.config.webSearch.apiKey", getCredentialValue: (searchConfig) => searchConfig?.acme?.apiKey, setCredentialValue: (searchConfigTarget, value) => { const acme = (searchConfigTarget.acme ??= {}); acme.apiKey = value; }, createTool: () => ({ description: "Das Web über Acme Search durchsuchen.", parameters: {}, execute: async (args) => ({ content: [] }), }),});Beide Provider-Typen verwenden dieselbe Struktur für die Anbindung von Zugangsdaten:
hint, envVars, placeholder, signupUrl, credentialPath,
getCredentialValue, setCredentialValue und createTool sind sämtlich
erforderlich.
Test
Schritt 6: Test
import { describe, it, expect } from "vitest";// Exportieren Sie Ihr Provider-Konfigurationsobjekt aus index.ts oder einer separaten Dateiimport { acmeProvider } from "./provider.js"; describe("acme-ai provider", () => { it("löst dynamische Modelle auf", () => { const model = acmeProvider.resolveDynamicModel!({ modelId: "acme-beta-v3", } as any); expect(model.id).toBe("acme-beta-v3"); expect(model.provider).toBe("acme-ai"); }); it("gibt den Katalog zurück, wenn ein Schlüssel verfügbar ist", async () => { const result = await acmeProvider.catalog!.run({ resolveProviderApiKey: () => ({ apiKey: "test-key" }), } as any); expect(result?.provider?.models).toHaveLength(2); }); it("gibt einen Null-Katalog zurück, wenn kein Schlüssel vorhanden ist", async () => { const result = await acmeProvider.catalog!.run({ resolveProviderApiKey: () => ({ apiKey: undefined }), } as any); expect(result).toBeNull(); });});Auf 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-pluginclawhub skill publish <path> ist ein anderer Befehl zum Veröffentlichen eines
Skill-Ordners und nicht eines Plugin-Pakets – verwenden Sie ihn hier nicht.
Dateistruktur
<bundled-plugin-root>/acme-ai/├── package.json # openclaw.providers-Metadaten├── openclaw.plugin.json # Manifest mit Metadaten zur Provider-Authentifizierung├── index.ts # definePluginEntry + registerProvider└── src/ ├── provider.test.ts # Tests └── usage.ts # Nutzungsendpunkt (optional)Referenz zur Katalogreihenfolge
catalog.order steuert, wann Ihr Katalog relativ zu integrierten
Providern zusammengeführt wird:
| Reihenfolge | Zeitpunkt | Anwendungsfall |
|---|---|---|
simple |
Erster Durchlauf | Einfache Provider mit API-Schlüssel |
profile |
Nach simple | Provider, die Authentifizierungsprofile voraussetzen |
paired |
Nach profile | Mehrere zusammengehörige Einträge synthetisieren |
late |
Letzter Durchlauf | Vorhandene Provider überschreiben (hat bei Kollisionen Vorrang) |
Nächste Schritte
- Kanal-Plugins - wenn Ihr Plugin auch einen Kanal bereitstellt
- SDK-Laufzeit - Hilfsfunktionen von
api.runtime(TTS, Suche, Subagent) - SDK-Übersicht - vollständige Referenz für Subpfadimporte
- Plugin-Interna - Details zu Hooks und gebündelte Beispiele