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.
Schritt-für-Schritt-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}}setup.providers[].envVars ermöglicht OpenClaw, Anmeldedaten zu 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 abgekü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 auf
openclaw.install.minHostVersion zurück, wenn es ausgelassen wird).
Provider registrieren
Ein minimaler Text-Provider benötigt id, label, auth und catalog.
catalog ist der Provider-eigene Laufzeit-/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 Katalogoberfläche der Steuerungsebene
für Listen-, Hilfe- und Auswahloberflächen und deckt Zeilen vom Typ text, voice, image_generation,
video_generation und music_generation ab. Belassen Sie Aufrufe von Herstellerendpunkten
und die Zuordnung von Antworten im Plugin; OpenClaw verwaltet die gemeinsame Zeilenstruktur,
Quellenbezeichnungen und die Darstellung der Hilfe.
Damit ist der Provider funktionsfähig. Benutzer können jetzt
openclaw onboard --acme-ai-api-key <key> ausführen und
acme-ai/acme-large als Modell auswählen.
Live-Modellerkennung
Wenn Ihr Provider eine OpenAI-kompatible /models-API bereitstellt, aktivieren Sie für die
Einzel-Provider-Hilfsfunktion die gemeinsame Erkennung:
catalog: { buildProvider: () => ({ api: "openai-completions", baseUrl: "https://api.acme-ai.com/v1", models: [...STATIC_MODELS], }), buildStaticProvider: () => ({ api: "openai-completions", baseUrl: "https://api.acme-ai.com/v1", models: [...STATIC_MODELS], }), liveModelDiscovery: true,},liveModelDiscovery: true ist ein öffentlicher Vertrag des Plugin SDK mit folgenden
Verhaltensweisen:
| Bereich | Vertrag |
|---|---|
| Anmeldedaten | Die Erkennung verwendet die aufgelösten Provider-Anmeldedaten des Katalogs und bevorzugt discoveryApiKey, wenn die Authentifizierung solche bereitstellt. Markierungen für Geheimnisreferenzen werden niemals als Token gesendet. Die Standardanfrage verwendet Authorization: Bearer <token>; verwenden Sie buildRequestHeaders für ein anderes Authentifizierungsschema des Herstellers. |
| Endpunkt | Die Standard-URL ist models relativ zur effektiven Provider-baseUrl, einschließlich einer Betreiberüberschreibung, wenn allowExplicitBaseUrl aktiviert ist. Verwenden Sie endpointPath für einen anderen relativen Pfad. Verwenden Sie endpointUrl: { url, requireBaseUrl } nur für eine feste Hersteller-URL; die Erkennung wird übersprungen, sofern die effektive Basis-URL nicht weiterhin requireBaseUrl entspricht, damit die Anmeldedaten eines benutzerdefinierten Proxys nicht an den Hersteller gesendet werden. |
| Netzwerkgrenzen | Abrufe verwenden den SSRF-Schutz von OpenClaw, ein einziges Zeitüberschreitungsbudget von 5 Sekunden für die gesamte Paginierung, ein Antwortlimit von 4 MiB pro Seite und ein Limit von 50 Seiten. Ursprungsübergreifende Paginierungslinks werden abgelehnt; Anmeldedaten werden nach einer ursprungsübergreifenden Weiterleitung entfernt. |
| Cache | Erfolgreiche, nicht leere Kataloge werden für 60 Sekunden nach Provider, Endpunkt und aufgelösten Anmeldedaten zwischengespeichert. Leere oder nicht verwendbare Ergebnisse werden nicht zwischengespeichert. |
| Filterung | Exakte Live-IDs behalten ihre vertrauenswürdigen statischen Metadaten. Neue Zeilen werden konservativ als Text-/Chatmodelle projiziert. Deaktivierte, archivierte, veraltete, ausdrücklich nicht für Chats vorgesehene sowie Embedding-, Reranking-, Moderations-, Sprach-, reine Bild- und reine Videomodelle werden ausgeschlossen. Verwenden Sie readRows nur, um Zeilen aus einer nicht standardmäßigen Antwortstruktur auszuwählen; Provider-spezifische Modellsemantik gehört weiterhin in einen benutzerdefinierten Katalog. |
| Fehler | Die Live-Erkennung ist unverbindlich. Fehler bei Authentifizierung, Netzwerk, Zeitüberschreitung, Paginierung, Parsing, leerem Katalog und Filterung geben den Provider-eigenen statischen Ausgangsbestand zurück, anstatt den Provider zu entfernen. |
Übergeben Sie für einen Endpunkt ohne Bearer-Authentifizierung oder einen nicht standardmäßigen Listenendpunkt Optionen anstelle von
true:
liveModelDiscovery: { endpointPath: "model-catalog", buildRequestHeaders: ({ apiKey, discoveryApiKey }) => ({ "vendor-version": "2026-01-01", "x-api-key": discoveryApiKey ?? apiKey ?? "", }), readRows: (body) => body && typeof body === "object" && Array.isArray((body as { models?: unknown }).models) ? (body as { models: unknown[] }).models : [],},Verwenden Sie endpointUrl nicht als uneingeschränkten alternativen Host. Die
requireBaseUrl-Prüfung bildet die Grenze zur Isolation von Anmeldedaten für Provider,
deren Host für die Modellliste sich von ihrem Inferenz-Host unterscheidet.
Wenn der Provider eine benutzerdefinierte Modellsemantik anstelle der konservativen
OpenAI-kompatiblen Projektion benötigt, belassen Sie diese Projektion im Plugin und verwenden Sie
openclaw/plugin-sdk/provider-catalog-live-runtime für den gemeinsamen Abruf-
Lebenszyklus. Die Hilfsfunktion stellt Ihnen geschützte HTTP-Abrufe, Provider-Authentifizierungsheader,
strukturierte HTTP-Fehler, TTL-Caching und statisches Rückfallverhalten bereit, ohne
Provider-Richtlinien in den OpenClaw-Core einzufügen.
Verwenden Sie buildLiveModelProviderConfig, wenn die Live-API Ihnen nur mitteilt, welche
Zeilen des Provider-eigenen statischen Katalogs 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 überführen 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 Anmeldedaten
verfügbar sind. Behalten Sie einen Offline-staticRun oder einen statischen Fallback bei, damit Einrichtung, Dokumentation,
Tests und Auswahloberflächen nicht vom Live-Netzwerkzugriff abhängen. Verwenden Sie eine TTL,
die für die Aktualität der Modellliste geeignet ist, vermeiden Sie Dateisystemabfragen zur Anfragezeit
und übergeben Sie einen providerspezifischen readRows / readModelId nur, wenn die
Upstream-Antwort keine OpenAI-kompatible { data: [{ id, object }] }-
Struktur aufweist.
Wenn der Upstream-Provider andere Steuerungstoken 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
der Übertragung um. output schreibt Assistenten-Text-Deltas und den endgültigen Text um, bevor
OpenClaw seine eigenen Steuerungsmarkierungen parst oder die Kanalauslieferung erfolgt.
Bevorzugen Sie für gebündelte Provider, die nur einen Text-Provider mit API-Schlüssel-
Authentifizierung und einer einzelnen kataloggestützten Laufzeit registrieren, den enger gefassten
Hilfsmechanismus 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-Authentifizierungsdaten auflösen kann. Er kann eine providerspezifische Erkennung durchführen. Verwenden Sie
buildStaticProvider nur für Offline-Zeilen, die sicher angezeigt werden können, bevor die Authentifizierung
konfiguriert ist; er darf weder Anmeldedaten erfordern noch Netzwerkanfragen ausführen.
Die models list --all-Anzeige von OpenClaw führt statische Kataloge derzeit
nur für gebündelte Provider-Plugins aus, mit einer leeren Konfiguration, einer leeren Umgebung und ohne
Agenten-/Arbeitsbereichspfade.
Wenn Ihr Authentifizierungsablauf während des Onboardings außerdem models.providers.*, Aliasse und
das Standardmodell des Agenten anpassen muss, verwenden Sie die Voreinstellungs-Hilfsmechanismen aus
openclaw/plugin-sdk/provider-onboard. Die am engsten gefassten Hilfsmechanismen sind
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) und
createModelCatalogPresetAppliers(...).
Wenn der native Endpunkt eines Providers gestreamte Nutzungsblöcke über den
normalen openai-completions-Transport unterstützt, bevorzugen Sie die gemeinsamen Katalog-Hilfsmechanismen in
openclaw/plugin-sdk/provider-catalog-shared, anstatt
Prüfungen auf Provider-IDs fest zu codieren. supportsNativeStreamingUsageCompat(...) und
applyProviderNativeStreamingUsageCompat(...) erkennen die Unterstützung anhand der
Endpunkt-Fähigkeitszuordnung, sodass native Endpunkte im Moonshot-/DashScope-Stil weiterhin
aktiviert werden, selbst wenn ein Plugin eine benutzerdefinierte Provider-ID verwendet.
Die obigen Beispiele zur Live-Erkennung decken Provider-APIs im Stil von /models ab. Belassen Sie
diese Erkennung innerhalb von catalog.run, geschützt durch verwendbare Authentifizierungsdaten, und halten Sie
staticRun für die Offline-Katalogerzeugung 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
Vorabinitialisierung – resolveDynamicModel wird nach deren Abschluss erneut ausgeführt.
Laufzeit-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 Hilfs-Builder decken jetzt die gängigsten Familien für Replay-/Tool-Kompatibilität ab, sodass Plugins normalerweise nicht 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 | Was sie einbindet | Gebündelte Beispiele |
|---|---|---|
openai-compatible |
Gemeinsame Replay-Richtlinie im OpenAI-Stil für OpenAI-kompatible Transporte, einschließlich Bereinigung von Tool-Aufruf-IDs, Korrekturen für die Assistent-zuerst-Reihenfolge und generischer Validierung von Gemini-Nachrichtenwechseln, wenn der Transport sie benötigt | moonshot, ollama, xai, zai |
anthropic-by-model |
Claude-spezifische Replay-Richtlinie, ausgewählt durch modelId, sodass Transporte für Anthropic-Nachrichten nur dann eine Claude-spezifische Bereinigung von Denkblöcken 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 erhalten müssen |
anthropic-vertex, clawrouter |
google-gemini |
Native Gemini-Replay-Richtlinie einschließlich Bereinigung des Bootstrap-Replays. Die gemeinsame Familie behält für die Gemini CLI mit Textausgabe markiertes Reasoning bei; der direkte Provider google überschreibt resolveReasoningOutputMode mit native, da das Denken der Gemini API als native Gedankenbestandteile 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 Anthropic-Nachrichten- und OpenAI-kompatible Modelloberflächen in einem Plugin kombinieren; das optionale, ausschließlich Claude betreffende Entfernen von Denkblöcken bleibt auf die Anthropic-Seite beschränkt | minimax |
Derzeit verfügbare Stream-Familien:
| Familie | Was sie einbindet | Gebündelte Beispiele |
|---|---|---|
google-thinking |
Normalisierung der Gemini-Denkinhalte im gemeinsamen Stream-Pfad | google, google-gemini-cli |
kilocode-thinking |
Kilo-Reasoning-Wrapper im gemeinsamen Proxy-Stream-Pfad, wobei kilo-auto/balanced und nicht unterstützte Proxy-Reasoning-IDs die injizierten Denkinhalte überspringen |
kilocode |
moonshot-thinking |
Zuordnung binärer nativer Moonshot-Denkinhalte aus der Konfiguration und der Stufe /think |
moonshot |
minimax-fast-mode |
MiniMax-Modellumschreibung für den Schnellmodus im gemeinsamen Stream-Pfad | minimax, minimax-portal |
openai-responses-defaults |
Gemeinsame native OpenAI/Codex-Responses-Wrapper: Attributionsheader, /fast/serviceTier, Textausführlichkeit, native Codex-Websuche, Reasoning-kompatible Nutzlastgestaltung und Responses-Kontextverwaltung |
openai |
openrouter-thinking |
OpenRouter-Reasoning-Wrapper für Proxy-Routen, wobei das Überspringen nicht unterstützter Modelle bzw. von 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 sollen, sofern es nicht ausdrücklich deaktiviert ist |
zai |
SDK-Schnittstellen für die Familien-Builder
Jeder Familien-Builder setzt sich aus öffentlichen Hilfsfunktionen niedrigerer Ebene zusammen, die aus demselben Paket exportiert werden und verwendet werden können, wenn ein Provider vom üblichen Muster abweichen muss:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)und die unverarbeiteten Replay-Builder (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Exportiert außerdem Gemini-Replay-Hilfsfunktionen (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) sowie Endpunkt-/Modell-Hilfsfunktionen (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...), außerdem die gemeinsamen OpenAI/Codex-Wrapper (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), den OpenAI-kompatiblen DeepSeek-V4-Wrapper (createDeepSeekV4OpenAICompatibleThinkingWrapper), die Bereinigung vorausgefüllter Denkinhalte 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- leichtgewichtige Nutzlast- und Ereignis-Wrapper für häufig durchlaufene Provider-Pfade, daruntercreateOpenAICompatibleCompletionsThinkingOffWrapper,createPayloadPatchStreamWrapper,createPlainTextToolCallCompatWrapper,normalizeOpenAICompatibleReasoningPayload(...)undsetQwenChatTemplateThinking(...).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")und zugrunde liegende Hilfsfunktionen für Provider-Schemas.
Halten Sie bei Providern der Gemini-Familie den Modus der Reasoning-Ausgabe
mit dem Transport in Einklang. Provider der direkten Google Gemini API sollten
die Reasoning-Ausgabe native verwenden, damit OpenClaw native
Gedankenbestandteile 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-Hilfsfunktionen bleiben absichtlich providerspezifisch. @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 Handhabung der Claude-OAuth-Beta und die context1m-Steuerung abbilden. Das xAI-Plugin behält die native Gestaltung von xAI Responses ebenfalls in seinem eigenen wrapStreamFn (/fast-Aliasse, standardmäßiges tool_stream, Bereinigung nicht unterstützter strikter Tools, xAI-spezifische Entfernung der Reasoning-Nutzlast).
Dasselbe Paketwurzelmuster bildet auch die Grundlage für @openclaw/openai-provider (Provider-Builder, Hilfsfunktionen für Standardmodelle, Echtzeit-Provider-Builder) und @openclaw/openrouter-provider (Provider-Builder sowie Hilfsfunktionen für Onboarding/Konfiguration).
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 Anfrageheader oder Änderungen am Body benötigen:
// wrapStreamFn gibt eine aus ctx.streamFn abgeleitete StreamFn zurückwrapStreamFn: (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-/Sitzungsheader oder Metadaten bei 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,}),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
in fetchUsageSnapshot). Geben Sie
{ handled: true } nur zurück, wenn der Provider die Authentifizierung für
die Nutzung definitiv verarbeitet hat, aber über keinen verwendbaren
Nutzungs-Token verfügt und OpenClaw den generischen Fallback für
API-Schlüssel/OAuth ü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
Manifestvertrag und beide Hooks vorhanden sind, nimmt OpenClaw den
Provider automatisch in die Nutzungserfassung auf, ohne nicht zugehörige
Provider-Plugins zu laden. Eine Aktualisierung einer Allowlist im Kern ist
nicht erforderlich. fetchUsageSnapshot gibt die gemeinsame
providerneutrale Struktur zurück:
plan: vom Provider gemeldetes Abonnement oder Schlüsselbezeichnungwindows: zurücksetzbare Kontingentzeiträume als verwendete Prozentwertebilling: typisierte Einträge vom Typbalance,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 ist
nicht USD, sofern der Upstream-Vertrag dies nicht festlegt. Ein Plugin,
das nur fetchUsageSnapshot implementiert, bleibt für explizite/synthetische
Aufrufer verfügbar, wird aber nicht automatisch erkannt, da OpenClaw
dessen Nutzungsanmeldedaten nicht auflösen kann.
Übliche 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 ProviderPlugin – die vollständige, derzeit aktuelle
Hook-Liste und Hinweise zu Fallbacks finden Sie unter Interna:
Provider-Laufzeit-Hooks.
Ausschließlich der Kompatibilität dienende Provider-Felder, die OpenClaw
nicht mehr aufruft, etwa 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 Legacy-/Vorschau-Modell-IDs vor der Suche |
normalizeTransport |
Bereinigung von api / baseUrl der Provider-Familie vor der generischen Modellzusammenstellung |
normalizeConfig |
Konfiguration models.providers.<id> normalisieren |
applyNativeStreamingUsageCompat |
Native Kompatibilitätsumschreibungen der Streaming-Nutzung für Konfigurations-Provider |
resolveConfigApiKey |
Providereigene Auflösung der Authentifizierung über Umgebungsmarkierungen |
resolveSyntheticAuth |
Synthetische Authentifizierung für lokale/selbst gehostete oder konfigurationsgestützte Systeme |
resolveExternalAuthProfiles |
Providereigene externe Authentifizierungsprofile für von CLI/App verwaltete Anmeldedaten überlagern |
shouldDeferSyntheticProfileAuth |
Synthetische Platzhalter für gespeicherte Profile hinter Umgebungs-/Konfigurationsauthentifizierung herabstufen |
resolveDynamicModel |
Beliebige Upstream-Modell-IDs akzeptieren |
prepareDynamicModel |
Asynchroner Abruf von Metadaten vor der Auflösung |
normalizeResolvedModel |
Transportumschreibungen vor dem Runner |
normalizeToolSchemas |
Providereigene Bereinigung des Tool-Schemas vor der Registrierung |
inspectToolSchemas |
Providereigene Diagnose des 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 Durchlauf |
resolveWebSocketSessionPolicy |
Native WS-Sitzungsheader/Abkühlzeit |
formatApiKey |
Benutzerdefinierte Laufzeit-Token-Struktur |
refreshOAuth |
Benutzerdefinierte OAuth-Aktualisierung |
buildAuthDoctorHint |
Anleitung zur Reparatur der Authentifizierung |
matchesContextOverflowError |
Providereigene Überlauferkennung |
classifyFailoverReason |
Providereigene Klassifizierung von Ratenbegrenzung/Überlastung |
isCacheTtlEligible |
TTL-Steuerung des Prompt-Caches |
buildMissingAuthMessage |
Benutzerdefinierter Hinweis bei fehlender Authentifizierung |
augmentModelCatalog |
Synthetische Zeilen für Vorwärtskompatibilität (veraltet – registerModelCatalogProvider bevorzugen) |
resolveThinkingProfile |
Modellspezifischer Optionssatz /think |
isBinaryThinking |
Kompatibilität für binäres Ein-/Ausschalten des Denkens (veraltet – resolveThinkingProfile bevorzugen) |
supportsXHighThinking |
Kompatibilität für xhigh-Reasoning-Unterstützung (veraltet – resolveThinkingProfile bevorzugen) |
resolveDefaultThinkingLevel |
Kompatibilität der standardmäßigen /think-Richtlinie (veraltet – resolveThinkingProfile bevorzugen) |
isModernModelRef |
Modellabgleich für Live-/Smoke-Tests |
prepareRuntimeAuth |
Token-Austausch vor der Inferenz |
resolveUsageAuth |
Benutzerdefiniertes Parsen von Nutzungsanmeldedaten |
fetchUsageSnapshot |
Benutzerdefinierter Nutzungsendpunkt |
createEmbeddingProvider |
Providereigener Embedding-Adapter für Speicher/Suche |
buildReplayPolicy |
Benutzerdefinierte Richtlinie für Transkript-Replay/Compaction |
sanitizeReplayHistory |
Providerspezifische Replay-Umschreibungen nach der generischen Bereinigung |
validateReplayTurns |
Strikte Validierung des Replay-Durchlaufs vor dem eingebetteten Runner |
onModelSelected |
Callback nach der Auswahl (z. B. Telemetrie) |
Hinweise zu Laufzeit-Fallbacks:
normalizeConfigermittelt pro Provider-ID genau 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; er ist kein separater Core-Fallback.resolveConfigApiKeyverwendet den Provider-Hook, wenn dieser verfügbar ist. Amazon Bedrock behält die Auflösung von AWS-Umgebungsmarkierungen in seinem Provider-Plugin; die Laufzeitauthentifizierung selbst verwendet bei einer Konfiguration mitauth: "aws-sdk"weiterhin die Standardkette des AWS SDK.resolveThinkingProfile(ctx)erhält die ausgewähltenprovider,modelId, den optional zusammengeführtenreasoning-Kataloghinweis und die optional zusammengeführtencompat-Modelldaten. Verwenden Siecompatausschließlich zur Auswahl der Denkoberfläche bzw. des Denkprofils des Providers.resolveSystemPromptContributionermöglicht einem Provider, cachebewusste System-Prompt-Hinweise für eine Modellfamilie einzufügen. Ziehen Sie diesen Hook dem veralteten pluginweitenbefore_prompt_build-Hook vor, wenn das Verhalten zu einer einzelnen Provider-/Modellfamilie gehört und die Trennung zwischen stabilem und dynamischem Cache erhalten bleiben soll.
Zusätzliche Fähigkeiten hinzufügen (optional)
Schritt 5: Zusätzliche Fähigkeiten hinzufügen
Ein Provider-Plugin kann neben der Textinferenz auch Embeddings, Sprache, Echtzeittranskription, Echtzeitsprache, Medienverständnis, Bildgenerierung, Videogenerierung, Webabruf und Websuche registrieren. OpenClaw klassifiziert dies als ein Plugin mit Hybridfä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
api.registerProvider(...)-Aufruf. 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(...) für HTTP-Fehler des Providers, damit
Plugins begrenzte Fehlertext-Lesevorgänge, die Analyse von JSON-Fehlern und
Anfrage-ID-Suffixe gemeinsam nutzen.
Echtzeittranskription
Bevorzugen Sie createRealtimeTranscriptionWebSocketSession(...) – der gemeinsame
Helfer übernimmt Proxy-Erfassung, Wiederverbindungsverzögerungen, das Leeren beim Schließen, Bereitschafts-
Handshakes, die Audiowarteschlange und die Diagnose von Schließereignissen. Ihr Plugin
ordnet lediglich die vorgelagerten 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 mehrteilige Audiodaten per POST senden, sollten
buildAudioTranscriptionFormData(...) aus
openclaw/plugin-sdk/provider-http verwenden. Der Helfer normalisiert Upload-
Dateinamen, einschließlich AAC-Uploads, die für kompatible
Transkriptions-APIs einen Dateinamen im M4A-Stil benötigen.
Echtzeitsprache
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) => ({ // Setzen Sie dies nur, wenn der Provider mehrere Tool-Antworten für // einen Aufruf akzeptiert, beispielsweise eine sofortige „Wird verarbeitet“-Antwort, gefolgt vom // endgültigen Ergebnis. supportsToolResultContinuation: false, connect: async () => {}, sendAudio: () => {}, setMediaTimestamp: () => {}, handleBargeIn: () => {}, submitToolResult: () => {}, acknowledgeMark: () => {}, close: () => {}, isConnected: () => true, }),});Deklarieren Sie capabilities, damit talk.catalog gültige Modi,
Transporte, Audioformate und Funktionsflags für Browser- und native Talk-
Clients bereitstellen 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.
submitToolResult kann void für die synchrone Übermittlung oder ein
Promise<void> für eine asynchrone Abschlussgrenze zurückgeben, die die Provider-
Bridge bereitstellen kann. 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.
Setzen Sie supportsToolResultSuppression: false, wenn der Provider
options.suppressResponse nicht berücksichtigen kann. OpenClaw vermeidet dann die Unterdrückung bei
internen Ergebnissen erzwungener Konsultationen und Abbrüchen und lehnt direkte
Anfragen nach unterdrückten Ergebnissen ab, anstatt stillschweigend eine Antwort zu starten.
Nutzer von createRealtimeVoiceBridgeSession können ebenso ein
Promise von onToolCall zurückgeben; synchrone Ausnahmen und Ablehnungen werden
an den onError-Callback der Sitzung weitergeleitet.
Setzen Sie handlesInputAudioBargeIn nur, wenn die Provider-VAD eine
Unterbrechung durch den Aufruf von onClearAudio("barge-in") bestätigt. Provider, die
das Flag weglassen, verwenden OpenClaws lokale Fallback-Erkennung für Eingangsaudio.
Medienverständnis
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "A photo of..." }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});Lokale oder selbst gehostete Medien-Provider, die absichtlich keine
Zugangsdaten benötigen, können resolveAuth bereitstellen und kind: "none" zurückgeben.
OpenClaw behält für Provider, die sich nicht
ausdrücklich dafür entscheiden, weiterhin die normale Authentifizierungssperre bei. Vorhandene Provider können weiterhin req.apiKey lesen;
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: "Transcript..." }),});Embeddings
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 wiederverwendbare Vektorgenerierung, einschließlich
der Speichersuche. registerMemoryEmbeddingProvider(...) ist eine veraltete
Kompatibilitätsschicht für vorhandene speicherspezifische Adapter.
Bild- und Videogenerierung
Bild- und Videofähigkeiten verwenden eine modusbewusste Struktur. Bild-
Provider deklarieren die erforderlichen Fähigkeitsblöcke generate und edit;
Video-Provider deklarieren generate, imageToVideo und
videoToVideo. Flache aggregierte Felder wie maxInputImages /
maxInputVideos / maxDurationSeconds reichen nicht aus, um
die Unterstützung des Transformationsmodus oder deaktivierte Modi eindeutig anzugeben. Die Musikgenerierung
folgt demselben Muster 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 Abfrage 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 zur Anbindung von Anmeldedaten:
hint, envVars, placeholder, signupUrl, credentialPath,
getCredentialValue, setCredentialValue und createTool sind alle
erforderlich.
Testen
Schritt 6: Testen
import { describe, it, expect } from "vitest";// Exportieren Sie Ihr Provider-Konfigurationsobjekt aus index.ts oder einer eigenen 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 wie alle anderen externen Code-Plugins veröffentlicht:
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 Skills-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 Provider-Authentifizierungsmetadaten├── index.ts # definePluginEntry + registerProvider└── src/ ├── provider.test.ts # Tests └── usage.ts # Nutzungsendpunkt (optional)Referenz zur Katalogreihenfolge
catalog.order steuert, wann Ihr Katalog relativ zu den integrierten
Providern zusammengeführt wird:
| Reihenfolge | Zeitpunkt | Anwendungsfall |
|---|---|---|
simple |
Erster Durchlauf | Einfache API-Schlüssel-Provider |
profile |
Nach einfachen Providern | Durch Authentifizierungsprofile beschränkte Provider |
paired |
Nach Profilen | Mehrere zusammengehörige Einträge synthetisieren |
late |
Letzter Durchlauf | Vorhandene Provider überschreiben (hat bei Konflikten Vorrang) |
Nächste Schritte
- Kanal-Plugins – wenn Ihr Plugin auch einen Kanal bereitstellt
- SDK-Laufzeit –
api.runtime-Hilfsfunktionen (TTS, Suche, Subagent) - SDK-Übersicht – vollständige Referenz für Unterpfadimporte
- Plugin-Interna – Hook-Details und mitgelieferte Beispiele