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

    package.json
    {"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"  }}}
    openclaw.plugin.json
    {"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.

    index.ts
    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:

    typescript
    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:

    typescript
    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:

    index.ts
    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:

    index.ts
    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:

    typescript
    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(...):

    typescript
    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:

    typescript
    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:

    typescript
    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, darunter createOpenAICompatibleCompletionsThinkingOffWrapper, createPayloadPatchStreamWrapper, createPlainTextToolCallCompatWrapper, normalizeOpenAICompatibleReasoningPayload(...) und setQwenChatTemplateThinking(...).
    • 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:

    typescript
    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:

    typescript
    // 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:

    typescript
    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:

    typescript
    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üsselbezeichnung
    • windows: zurücksetzbare Kontingentzeiträume als verwendete Prozentwerte
    • billing: typisierte Einträge vom Typ balance, spend oder budget; unit kann eine ISO-Währung oder eine Provider-Einheit wie credits sein
    • summary: 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:

    • normalizeConfig ermittelt 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 eigener normalizeConfig-Hook normalisiert die Konfigurationseinträge google / google-vertex / google-antigravity; er ist kein separater Core-Fallback.
    • resolveConfigApiKey verwendet 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 mit auth: "aws-sdk" weiterhin die Standardkette des AWS SDK.
    • resolveThinkingProfile(ctx) erhält die ausgewählten provider, modelId, den optional zusammengeführten reasoning-Kataloghinweis und die optional zusammengeführten compat-Modelldaten. Verwenden Sie compat ausschließlich zur Auswahl der Denkoberfläche bzw. des Denkprofils des Providers.
    • resolveSystemPromptContribution ermöglicht einem Provider, cachebewusste System-Prompt-Hinweise für eine Modellfamilie einzufügen. Ziehen Sie diesen Hook dem veralteten pluginweiten before_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)

    typescript
    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.

    typescript
    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

    typescript
    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

    typescript
    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.

    typescript
    api.registerMediaUnderstandingProvider({  id: "local-audio",  capabilities: ["audio"],  resolveAuth: () => ({    kind: "none",    source: "local-audio plugin no-auth",  }),  transcribeAudio: async (req) => ({ text: "Transcript..." }),});

    Embeddings

    typescript
    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.

    typescript
    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

    typescript
    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

    src/provider.test.ts
    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:

    bash
    clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-plugin

    clawhub skill publish <path> ist ein anderer Befehl zum Veröffentlichen eines Skills-Ordners und nicht eines Plugin-Pakets – verwenden Sie ihn hier nicht.

    Dateistruktur

    Code
    <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

    Verwandte Themen

    Was this useful?
    On this page

    On this page