Building plugins

Provider-plugins bouwen

Deze gids leidt je door het bouwen van een provider-Plugin die een modelprovider (LLM) aan OpenClaw toevoegt. Aan het einde heb je een provider met een modelcatalogus, API-sleutelauthenticatie en dynamische modelresolutie.

Stapsgewijze handleiding

  • Pakket en manifest

    Stap 1: Pakket en manifest

    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 model provider","providers": ["acme-ai"],"modelSupport": {  "modelPrefixes": ["acme-"]},"providerAuthEnvVars": {  "acme-ai": ["ACME_AI_API_KEY"]},"providerAuthAliases": {  "acme-ai-coding": "acme-ai"},"providerAuthChoices": [  {    "provider": "acme-ai",    "method": "api-key",    "choiceId": "acme-ai-api-key",    "choiceLabel": "Acme AI API key",    "groupId": "acme-ai",    "groupLabel": "Acme AI",    "cliFlag": "--acme-ai-api-key",    "cliOption": "--acme-ai-api-key <key>",    "cliDescription": "Acme AI API key"  }],"configSchema": {  "type": "object",  "additionalProperties": false}}

    Het manifest declareert providerAuthEnvVars zodat OpenClaw inloggegevens kan detecteren zonder de runtime van je Plugin te laden. Voeg providerAuthAliases toe wanneer een providervariant de auth van een andere provider-id moet hergebruiken. modelSupport is optioneel en laat OpenClaw je provider-Plugin automatisch laden vanuit verkorte model-id's zoals acme-large voordat runtime-hooks bestaan. Als je de provider op ClawHub publiceert, zijn die velden openclaw.compat en openclaw.build verplicht in package.json.

  • De provider registreren

    Een minimale tekstprovider heeft een id, label, auth en catalog nodig. catalog is de runtime-/configuratiehook die eigendom is van de provider; deze kan live vendor-API's aanroepen en retourneert models.providers-items.

    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 is het nieuwere control-plane-catalogusoppervlak voor UI voor lijsten/help/kiezers. Gebruik het voor rijen voor tekst, afbeeldingsgeneratie, videogeneratie en muziekgeneratie. Houd aanroepen naar vendor-eindpunten en responsmapping in de Plugin; OpenClaw is eigenaar van de gedeelde rijvorm, bronlabels en helpweergave.

    Dat is een werkende provider. Gebruikers kunnen nu openclaw onboard --acme-ai-api-key <key> uitvoeren en acme-ai/acme-large als hun model selecteren.

    Als de upstream-provider andere controletokens gebruikt dan OpenClaw, voeg dan een kleine bidirectionele teksttransformatie toe in plaats van het streampad te vervangen:

    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 herschrijft de uiteindelijke systeemprompt en tekstberichtinhoud vóór transport. output herschrijft assistent-tekstdelta's en definitieve tekst voordat OpenClaw zijn eigen controlemarkeringen of kanaalbezorging parseert.

    Voor gebundelde providers die slechts één tekstprovider registreren met API-sleutel- auth plus één door de catalogus ondersteunde runtime, geef de voorkeur aan de smallere helper 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 is het live-cataloguspad dat wordt gebruikt wanneer OpenClaw echte providerauth kan oplossen. Het mag provider-specifieke ontdekking uitvoeren. Gebruik buildStaticProvider alleen voor offline rijen die veilig zijn om te tonen voordat auth is geconfigureerd; het mag geen inloggegevens vereisen of netwerkverzoeken doen. De weergave models list --all van OpenClaw voert momenteel statische catalogi alleen uit voor gebundelde provider-Plugins, met een lege config, lege env en zonder agent-/workspacepaden.

    Als je auth-flow ook models.providers.*, aliassen en het standaardmodel van de agent tijdens onboarding moet patchen, gebruik dan de presethelpers uit openclaw/plugin-sdk/provider-onboard. De smalste helpers zijn createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) en createModelCatalogPresetAppliers(...).

    Wanneer het native eindpunt van een provider gestreamde gebruiksblokken op het normale openai-completions-transport ondersteunt, geef dan de voorkeur aan de gedeelde catalogushelpers in openclaw/plugin-sdk/provider-catalog-shared in plaats van provider-id-controles hard te coderen. supportsNativeStreamingUsageCompat(...) en applyProviderNativeStreamingUsageCompat(...) detecteren ondersteuning vanuit de capability-map van het eindpunt, zodat native Moonshot-/DashScope-achtige eindpunten zich nog steeds aanmelden, zelfs wanneer een Plugin een aangepaste provider-id gebruikt.

  • Dynamische modelresolutie toevoegen

    Als je provider willekeurige model-ID's accepteert (zoals een proxy of router), voeg dan resolveDynamicModel toe:

    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,  }),});

    Als oplossen een netwerkaanroep vereist, gebruik dan prepareDynamicModel voor asynchrone opwarming - resolveDynamicModel wordt opnieuw uitgevoerd nadat dit is voltooid.

  • Runtime-hooks toevoegen (indien nodig)

    De meeste providers hebben alleen catalog + resolveDynamicModel nodig. Voeg hooks stapsgewijs toe wanneer je provider ze vereist.

    Gedeelde helperbuilders dekken nu de meest voorkomende replay-/toolcompatibiliteits- families, zodat Plugins meestal niet elke hook één voor één met de hand hoeven te verbinden:

    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,});

    Beschikbare replayfamilies vandaag:

    Familie Wat het koppelt Meegeleverde voorbeelden
    openai-compatible Gedeeld OpenAI-achtig replaybeleid voor OpenAI-compatibele transporten, inclusief opschoning van tool-call-id's, oplossingen voor assistant-first-volgorde en generieke Gemini-turn-validatie waar het transport dit nodig heeft moonshot, ollama, xai, zai
    anthropic-by-model Claude-bewust replaybeleid gekozen door modelId, zodat Anthropic-message-transporten alleen Claude-specifieke opschoning van thinking-blokken krijgen wanneer het opgeloste model daadwerkelijk een Claude-id is amazon-bedrock, anthropic-vertex
    google-gemini Native Gemini-replaybeleid plus opschoning van bootstrap-replay en modus voor getagde reasoning-uitvoer google, google-gemini-cli
    passthrough-gemini Opschoning van Gemini-thought-signatures voor Gemini-modellen die via OpenAI-compatibele proxytransporten draaien; schakelt geen native Gemini-replayvalidatie of bootstrap-herschrijvingen in openrouter, kilocode, opencode, opencode-go
    hybrid-anthropic-openai Hybride beleid voor providers die Anthropic-message- en OpenAI-compatibele modeloppervlakken in één plugin combineren; optioneel verwijderen van thinking-blokken alleen voor Claude blijft beperkt tot de Anthropic-kant minimax

    Vandaag beschikbare streamfamilies:

    Familie Wat het koppelt Meegeleverde voorbeelden
    google-thinking Normalisatie van Gemini-thinking-payloads op het gedeelde streampad google, google-gemini-cli
    kilocode-thinking Kilo-reasoning-wrapper op het gedeelde proxy-streampad, waarbij kilo/auto en niet-ondersteunde proxy-reasoning-id's geïnjecteerde thinking overslaan kilocode
    moonshot-thinking Moonshot binaire native-thinking-payloadmapping vanuit config + /think-niveau moonshot
    minimax-fast-mode MiniMax fast-mode-modelherschrijving op het gedeelde streampad minimax, minimax-portal
    openai-responses-defaults Gedeelde native OpenAI/Codex Responses-wrappers: attributieheaders, /fast/serviceTier, tekstuitgebreidheid, native Codex-webzoekactie, reasoning-compat-payloadvorming en Responses-contextbeheer openai, openai-codex
    openrouter-thinking OpenRouter-reasoning-wrapper voor proxyroutes, waarbij overslaan voor niet-ondersteunde modellen/auto centraal wordt afgehandeld openrouter
    tool-stream-default-on Standaard ingeschakelde tool_stream-wrapper voor providers zoals Z.AI die toolstreaming willen tenzij dit expliciet is uitgeschakeld zai
    SDK-seams die de familiebouwers aandrijven

    Elke familiebouwer is samengesteld uit lagere openbare helpers die uit hetzelfde pakket worden geëxporteerd en die je kunt gebruiken wanneer een provider van het gemeenschappelijke patroon moet afwijken:

    • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...) en de onbewerkte replaybouwers (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). Exporteert ook Gemini-replayhelpers (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) en endpoint-/modelhelpers (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId).
    • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), plus de gedeelde OpenAI/Codex-wrappers (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), DeepSeek V4 OpenAI-compatibele wrapper (createDeepSeekV4OpenAICompatibleThinkingWrapper), Anthropic Messages-opruiming van thinking-prefill (createAnthropicThinkingPrefillPayloadWrapper) en gedeelde proxy-/providerwrappers (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
    • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("gemini") en onderliggende Gemini-schemahelpers (normalizeGeminiToolSchemas, inspectGeminiToolSchemas).

    Sommige streamhelpers blijven bewust provider-lokaal. @openclaw/anthropic-provider houdt wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier en de lagere Anthropic-wrapperbouwers in zijn eigen openbare api.ts / contract-api.ts-seam omdat ze Claude OAuth-bèta-afhandeling en context1m-gating coderen. De xAI-plugin houdt native xAI Responses-vorming op vergelijkbare wijze in zijn eigen wrapStreamFn (/fast-aliassen, standaard tool_stream, opschoning van niet-ondersteunde strikte tools, xAI-specifieke verwijdering van reasoning-payloads).

    Hetzelfde pakket-rootpatroon ondersteunt ook @openclaw/openai-provider (providerbouwers, default-modelhelpers, realtime-providerbouwers) en @openclaw/openrouter-provider (providerbouwer plus onboarding-/confighelpers).

    Tokenuitwisseling

    Voor providers die vóór elke inference-aanroep een tokenuitwisseling nodig hebben:

    typescript
    prepareRuntimeAuth: async (ctx) => {  const exchanged = await exchangeToken(ctx.apiKey);  return {    apiKey: exchanged.token,    baseUrl: exchanged.baseUrl,    expiresAt: exchanged.expiresAt,  };},

    Aangepaste headers

    Voor providers die aangepaste requestheaders of body-aanpassingen nodig hebben:

    typescript
    // wrapStreamFn returns a StreamFn derived from ctx.streamFnwrapStreamFn: (ctx) => {  if (!ctx.streamFn) return undefined;  const inner = ctx.streamFn;  return async (params) => {    params.headers = {      ...params.headers,      "X-Acme-Version": "2",    };    return inner(params);  };},

    Native transportidentiteit

    Voor providers die native request-/sessieheaders of metadata nodig hebben op generieke HTTP- of WebSocket-transporten:

    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,}),

    Gebruik en facturering

    Voor providers die gebruiks-/factureringsgegevens beschikbaar stellen:

    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);},
    Alle beschikbare providerhooks

    OpenClaw roept hooks in deze volgorde aan. De meeste providers gebruiken er slechts 2-3: Provider-velden die alleen nog voor compatibiliteit bestaan en die OpenClaw niet meer aanroept, zoals ProviderPlugin.capabilities en suppressBuiltInModel, worden hier niet vermeld.

    # Hook Wanneer te gebruiken
    1 catalog Modelcatalogus of standaardwaarden voor base-URL
    2 applyConfigDefaults Globale standaardwaarden in eigendom van de provider tijdens configmaterialisatie
    3 normalizeModelId Opschoning van legacy-/preview-model-id-aliassen vóór lookup
    4 normalizeTransport Opschoning van providerfamilie-api / baseUrl vóór generieke modelassemblage
    5 normalizeConfig models.providers.<id>-config normaliseren
    6 applyNativeStreamingUsageCompat Native streaming-usage-compat-herschrijvingen voor configproviders
    7 resolveConfigApiKey Auth-resolutie van env-markers in eigendom van de provider
    8 resolveSyntheticAuth Lokale/zelfgehoste of config-ondersteunde synthetische auth
    9 shouldDeferSyntheticProfileAuth Synthetische opgeslagen-profiel-placeholders lager prioriteren achter env-/config-auth
    10 resolveDynamicModel Willekeurige upstream-model-id's accepteren
    11 prepareDynamicModel Asynchrone metadatafetch vóór resolutie
    12 normalizeResolvedModel Transportherschrijvingen vóór de runner
    13 contributeResolvedModelCompat Compatflags voor vendormodellen achter een ander compatibel transport
    14 normalizeToolSchemas Opschoning van toolschema's in eigendom van de provider vóór registratie
    15 inspectToolSchemas Toolschema-diagnostiek in eigendom van de provider
    16 resolveReasoningOutputMode Contract voor getagde versus native reasoning-uitvoer
    17 prepareExtraParams Standaard requestparameters
    18 createStreamFn Volledig aangepast StreamFn-transport
    19 wrapStreamFn Aangepaste header-/bodywrappers op het normale streampad
    20 resolveTransportTurnState Native headers/metadata per turn
    21 resolveWebSocketSessionPolicy Native WS-sessieheaders/-cooldown
    22 formatApiKey Aangepaste runtime-tokenvorm
    23 refreshOAuth Aangepaste OAuth-refresh
    24 buildAuthDoctorHint Richtlijnen voor auth-herstel
    25 matchesContextOverflowError Overflow-detectie in eigendom van de provider
    26 classifyFailoverReason Classificatie van rate-limit/overbelasting in eigendom van de provider
    27 isCacheTtlEligible TTL-gating voor promptcache
    28 buildMissingAuthMessage Aangepaste hint voor ontbrekende auth
    29 augmentModelCatalog Synthetische forward-compat-rijen
    30 resolveThinkingProfile Modelspecifieke /think-optieset
    31 isBinaryThinking Compatibiliteit voor binair thinking aan/uit
    32 supportsXHighThinking Compatibiliteit voor xhigh-reasoningondersteuning
    33 resolveDefaultThinkingLevel Compatibiliteit voor standaard /think-beleid
    34 isModernModelRef Live-/smoke-modelmatching
    35 prepareRuntimeAuth Tokenuitwisseling vóór inference
    36 resolveUsageAuth Aangepaste parsing van gebruikscredentials
    37 fetchUsageSnapshot Aangepast gebruiksendpoint
    38 createEmbeddingProvider Embeddingadapter in eigendom van de provider voor geheugen/zoeken
    39 buildReplayPolicy Aangepast transcript-replay-/Compaction-beleid
    40 sanitizeReplayHistory Provider-specifieke replayherschrijvingen na generieke opschoning
    41 validateReplayTurns Strikte replay-turn-validatie vóór de embedded runner
    42 onModelSelected Callback na selectie (bijv. telemetry)

    Runtime-fallbacknotities:

    • normalizeConfig controleert eerst de gematchte provider en daarna andere hook-capabele providerplugins totdat er één de config daadwerkelijk wijzigt. Als geen providerhook een ondersteunde Google-familie-configentry herschrijft, wordt de meegeleverde Google-confignormalizer nog steeds toegepast.
    • resolveConfigApiKey gebruikt de providerhook wanneer die beschikbaar is. Het meegeleverde amazon-bedrock-pad heeft hier ook een ingebouwde AWS env-marker-resolver, hoewel Bedrock-runtime-auth zelf nog steeds de standaardketen van de AWS SDK gebruikt.
    • resolveSystemPromptContribution laat een provider cachebewuste system-prompt-richtlijnen injecteren voor een modelfamilie. Geef hieraan de voorkeur boven before_prompt_build wanneer het gedrag bij één provider-/modelfamilie hoort en de stabiele/dynamische cachesplitsing moet behouden.

    Zie Internals: Provider Runtime Hooks voor gedetailleerde beschrijvingen en praktijkvoorbeelden.

  • Extra capabilities toevoegen (optioneel)

    Stap 5: Extra capabilities toevoegen

    Een provider-Plugin kan spraak, realtime transcriptie, realtime spraak, mediabegrip, beeldgeneratie, videogeneratie, web-fetch en webzoekopdrachten naast tekstinferentie registreren. OpenClaw classificeert dit als een hybrid-capability-Plugin - het aanbevolen patroon voor bedrijfs-Plugins (een Plugin per leverancier). Zie Internals: Eigendom van capabilities.

    Registreer elke capability binnen register(api) naast je bestaande api.registerProvider(...)-aanroep. Kies alleen de tabbladen die je nodig hebt:

    Speech (TTS)

    typescript
    import {  assertOkOrThrowProviderError,  postJsonRequest,} from "openclaw/plugin-sdk/provider-http"; api.registerSpeechProvider({  id: "acme-ai",  label: "Acme Speech",  isConfigured: ({ config }) => Boolean(config.messages?.tts),  synthesize: async (req) => {    const { response, release } = await postJsonRequest({      url: "https://api.example.com/v1/speech",      headers: new Headers({ "Content-Type": "application/json" }),      body: { text: req.text },      timeoutMs: req.timeoutMs,      fetchFn: fetch,      auditContext: "acme speech",    });    try {      await assertOkOrThrowProviderError(response, "Acme Speech API error");      return {        audioBuffer: Buffer.from(await response.arrayBuffer()),        outputFormat: "mp3",        fileExtension: ".mp3",        voiceCompatible: false,      };    } finally {      await release();    }  },});

    Gebruik assertOkOrThrowProviderError(...) voor HTTP-fouten van providers, zodat Plugins gedeelde begrensde reads van foutbody's, JSON-foutparsing en request-id-achtervoegsels gebruiken.

    Realtime transcription

    Geef de voorkeur aan createRealtimeTranscriptionWebSocketSession(...) - de gedeelde helper handelt proxyvastlegging, reconnect-backoff, close-flushing, ready- handshakes, audiowachtrijen en diagnostiek van close-events af. Je Plugin koppelt alleen upstream-events.

    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-providers die multipart-audio POSTen, moeten buildAudioTranscriptionFormData(...) uit openclaw/plugin-sdk/provider-http gebruiken. De helper normaliseert upload- bestandsnamen, inclusief AAC-uploads die een M4A-achtige bestandsnaam nodig hebben voor compatibele transcriptie-API's.

    Realtime voice

    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,    supportsToolCalls: true,  },  isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),  createBridge: (req) => ({    // Set this only if the provider accepts multiple tool responses for    // one call, for example an immediate "working" response followed by    // the final result.    supportsToolResultContinuation: false,    connect: async () => {},    sendAudio: () => {},    setMediaTimestamp: () => {},    handleBargeIn: () => {},    submitToolResult: () => {},    acknowledgeMark: () => {},    close: () => {},    isConnected: () => true,  }),});

    Declareer capabilities zodat talk.catalog geldige modi, transports, audioformaten en featureflags kan tonen aan browser- en native Talk- clients. Implementeer handleBargeIn wanneer een transport kan detecteren dat een mens de audioweergave van de assistent onderbreekt en de provider het afkappen of wissen van de actieve audiorespons ondersteunt.

    Media understanding

    typescript
    api.registerMediaUnderstandingProvider({  id: "acme-ai",  capabilities: ["image", "audio"],  describeImage: async (req) => ({ text: "A photo of..." }),  transcribeAudio: async (req) => ({ text: "Transcript..." }),});

    Image and video generation

    Videocapabilities gebruiken een modusbewuste vorm: generate, imageToVideo en videoToVideo. Platte aggregaatvelden zoals maxInputImages / maxInputVideos / maxDurationSeconds zijn niet genoeg om ondersteuning voor transformatiemodi of uitgeschakelde modi duidelijk te adverteren. Muziekgeneratie volgt hetzelfde patroon met expliciete generate / edit-blokken.

    typescript
    api.registerImageGenerationProvider({  id: "acme-ai",  label: "Acme Images",  generate: async (req) => ({ /* image result */ }),}); api.registerVideoGenerationProvider({  id: "acme-ai",  label: "Acme Video",  capabilities: {    generate: { maxVideos: 1, maxDurationSeconds: 10, supportsResolution: true },    imageToVideo: {      enabled: true,      maxVideos: 1,      maxInputImages: 1,      maxInputImagesByModel: { "acme/reference-to-video": 9 },      maxDurationSeconds: 5,    },    videoToVideo: { enabled: false },  },  generateVideo: async (req) => ({ videos: [] }),});

    Web fetch and search

    typescript
    api.registerWebFetchProvider({  id: "acme-ai-fetch",  label: "Acme Fetch",  hint: "Fetch pages through Acme's rendering backend.",  envVars: ["ACME_FETCH_API_KEY"],  placeholder: "acme-...",  signupUrl: "https://acme.example.com/fetch",  credentialPath: "plugins.entries.acme.config.webFetch.apiKey",  getCredentialValue: (fetchConfig) => fetchConfig?.acme?.apiKey,  setCredentialValue: (fetchConfigTarget, value) => {    const acme = (fetchConfigTarget.acme ??= {});    acme.apiKey = value;  },  createTool: () => ({    description: "Fetch a page through Acme Fetch.",    parameters: {},    execute: async (args) => ({ content: [] }),  }),}); api.registerWebSearchProvider({  id: "acme-ai-search",  label: "Acme Search",  search: async (req) => ({ content: [] }),});
  • Test

    Stap 6: Test

    src/provider.test.ts
    import { describe, it, expect } from "vitest";// Export your provider config object from index.ts or a dedicated fileimport { acmeProvider } from "./provider.js"; describe("acme-ai provider", () => {  it("resolves dynamic models", () => {    const model = acmeProvider.resolveDynamicModel!({      modelId: "acme-beta-v3",    } as any);    expect(model.id).toBe("acme-beta-v3");    expect(model.provider).toBe("acme-ai");  });   it("returns catalog when key is available", async () => {    const result = await acmeProvider.catalog!.run({      resolveProviderApiKey: () => ({ apiKey: "test-key" }),    } as any);    expect(result?.provider?.models).toHaveLength(2);  });   it("returns null catalog when no key", async () => {    const result = await acmeProvider.catalog!.run({      resolveProviderApiKey: () => ({ apiKey: undefined }),    } as any);    expect(result).toBeNull();  });});
  • Publiceren naar ClawHub

    Provider-Plugins publiceren op dezelfde manier als elke andere externe code-Plugin:

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

    Gebruik hier niet de verouderde alias voor alleen skills; Plugin-pakketten moeten clawhub package publish gebruiken.

    Bestandsstructuur

    Code
    <bundled-plugin-root>/acme-ai/├── package.json              # openclaw.providers metadata├── openclaw.plugin.json      # Manifest with provider auth metadata├── index.ts                  # definePluginEntry + registerProvider└── src/    ├── provider.test.ts      # Tests    └── usage.ts              # Usage endpoint (optional)

    Referentie voor catalogusvolgorde

    catalog.order bepaalt wanneer je catalogus samenvoegt ten opzichte van ingebouwde providers:

    Volgorde Wanneer Usecase
    simple Eerste pass Gewone API-key-providers
    profile Na simple Providers afgeschermd via auth profiles
    paired Na profile Meerdere gerelateerde items synthetiseren
    late Laatste pass Bestaande providers overschrijven (wint bij botsing)

    Volgende stappen

    Gerelateerd

    Was this useful?