Plugins
Creazione di Plugin provider
Questa guida illustra come creare un Plugin provider che aggiunge un provider di modelli (LLM) a OpenClaw. Alla fine avrai un provider con un catalogo di modelli, autenticazione tramite chiave API e risoluzione dinamica dei modelli.
Procedura dettagliata
Pacchetto e manifest
Passaggio 1: pacchetto e manifest
{
"name": "@myorg/openclaw-acme-ai",
"version": "1.0.0",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"providers": ["acme-ai"],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
},
"build": {
"openclawVersion": "2026.3.24-beta.2",
"pluginSdkVersion": "2026.3.24-beta.2"
}
}
}
{
"id": "acme-ai",
"name": "Acme AI",
"description": "Acme AI model provider",
"providers": ["acme-ai"],
"modelSupport": {
"modelPrefixes": ["acme-"]
},
"providerAuthEnvVars": {
"acme-ai": ["ACME_AI_API_KEY"]
},
"providerAuthAliases": {
"acme-ai-coding": "acme-ai"
},
"providerAuthChoices": [
{
"provider": "acme-ai",
"method": "api-key",
"choiceId": "acme-ai-api-key",
"choiceLabel": "Acme AI API key",
"groupId": "acme-ai",
"groupLabel": "Acme AI",
"cliFlag": "--acme-ai-api-key",
"cliOption": "--acme-ai-api-key <key>",
"cliDescription": "Acme AI API key"
}
],
"configSchema": {
"type": "object",
"additionalProperties": false
}
}
Il manifest dichiara providerAuthEnvVars così OpenClaw può rilevare
le credenziali senza caricare il runtime del tuo Plugin. Aggiungi providerAuthAliases
quando una variante di provider deve riutilizzare l'autenticazione dell'id di un altro provider. modelSupport
è facoltativo e consente a OpenClaw di caricare automaticamente il tuo Plugin provider da id
modello abbreviati come acme-large prima che esistano gli hook di runtime. Se pubblichi il
provider su ClawHub, quei campi openclaw.compat e openclaw.build
sono obbligatori in package.json.
Registra il provider
Un provider minimale richiede un id, una label, auth e catalog:
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,
},
],
},
};
},
},
});
},
});
Questo è un provider funzionante. Ora gli utenti possono eseguire
openclaw onboard --acme-ai-api-key <key> e selezionare
acme-ai/acme-large come modello.
Se il provider upstream usa token di controllo diversi da quelli di OpenClaw, aggiungi una piccola trasformazione testuale bidirezionale invece di sostituire il percorso dello stream:
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 riscrive il prompt di sistema finale e il contenuto dei messaggi di testo prima
del trasporto. output riscrive i delta di testo dell'assistente e il testo finale prima che
OpenClaw analizzi i propri marcatori di controllo o la consegna al canale.
Per i provider integrati che registrano solo un provider testuale con autenticazione tramite chiave API
più un singolo runtime supportato da catalogo, preferisci l'helper più specifico
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 è il percorso del catalogo live usato quando OpenClaw può risolvere una reale
autenticazione del provider. Può eseguire discovery specifica del provider. Usa
buildStaticProvider solo per righe offline che sono sicure da mostrare prima che l'autenticazione
sia configurata; non deve richiedere credenziali né effettuare richieste di rete.
La visualizzazione models list --all di OpenClaw attualmente esegue cataloghi statici
solo per i Plugin provider integrati, con una configurazione vuota, env vuoto e nessun
percorso agente/workspace.
Se il tuo flusso di autenticazione deve anche applicare patch a models.providers.*, alias e
al modello predefinito dell'agente durante l'onboarding, usa gli helper preset da
openclaw/plugin-sdk/provider-onboard. Gli helper più specifici sono
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) e
createModelCatalogPresetAppliers(...).
Quando l'endpoint nativo di un provider supporta blocchi di utilizzo in streaming sul
normale trasporto openai-completions, preferisci gli helper di catalogo condivisi in
openclaw/plugin-sdk/provider-catalog-shared invece di codificare controlli
basati sull'id del provider. supportsNativeStreamingUsageCompat(...) e
applyProviderNativeStreamingUsageCompat(...) rilevano il supporto dalla
mappa delle capacità dell'endpoint, quindi gli endpoint nativi in stile Moonshot/DashScope continuano
ad aderire anche quando un Plugin usa un id provider personalizzato.
Aggiungi la risoluzione dinamica dei modelli
Se il tuo provider accetta ID modello arbitrari (come un proxy o un router),
aggiungi resolveDynamicModel:
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,
}),
});
Se la risoluzione richiede una chiamata di rete, usa prepareDynamicModel per il warm-up
asincrono: resolveDynamicModel viene eseguito di nuovo dopo il completamento.
Aggiungi hook di runtime (se necessario)
La maggior parte dei provider richiede solo catalog + resolveDynamicModel. Aggiungi hook
in modo incrementale man mano che il tuo provider li richiede.
I builder helper condivisi ora coprono le famiglie più comuni di compatibilità replay/strumenti, quindi di solito i Plugin non devono collegare manualmente ogni hook uno per uno:
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,
});
Famiglie di replay disponibili oggi:
| Famiglia | Cosa collega | Esempi integrati |
|---|---|---|
openai-compatible |
Policy di replay condivisa in stile OpenAI per trasporti compatibili con OpenAI, inclusa la sanificazione degli id delle chiamate agli strumenti, correzioni dell'ordinamento con assistente per primo e validazione generica dei turni Gemini dove il trasporto ne ha bisogno | moonshot, ollama, xai, zai |
anthropic-by-model |
Policy di replay consapevole di Claude scelta da modelId, quindi i trasporti con messaggi Anthropic ricevono la pulizia dei blocchi di ragionamento specifica di Claude solo quando il modello risolto è effettivamente un id Claude |
amazon-bedrock, anthropic-vertex |
google-gemini |
Policy di replay Gemini nativa più sanificazione del replay di bootstrap e modalità di output di ragionamento con tag | google, google-gemini-cli |
passthrough-gemini |
Sanificazione delle firme di pensiero Gemini per modelli Gemini eseguiti tramite trasporti proxy compatibili con OpenAI; non abilita la validazione di replay Gemini nativa né le riscritture di bootstrap | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Policy ibrida per provider che combinano superfici di modelli con messaggi Anthropic e compatibili con OpenAI in un unico Plugin; l'eliminazione facoltativa dei blocchi di pensiero solo Claude resta limitata al lato Anthropic | minimax |
Famiglie di stream disponibili oggi:
| Famiglia | Cosa collega | Esempi inclusi |
|---|---|---|
google-thinking |
Normalizzazione del payload di pensiero Gemini sul percorso di stream condiviso | google, google-gemini-cli |
kilocode-thinking |
Wrapper di ragionamento Kilo sul percorso di stream proxy condiviso, con kilo/auto e gli ID di ragionamento proxy non supportati che saltano il pensiero iniettato |
kilocode |
moonshot-thinking |
Mappatura del payload native-thinking binario di Moonshot dalla configurazione + livello /think |
moonshot |
minimax-fast-mode |
Riscrittura del modello fast-mode MiniMax sul percorso di stream condiviso | minimax, minimax-portal |
openai-responses-defaults |
Wrapper Responses OpenAI/Codex nativi condivisi: header di attribuzione, /fast/serviceTier, verbosità del testo, ricerca web nativa Codex, modellazione del payload per compatibilità del ragionamento e gestione del contesto Responses |
openai, openai-codex |
openrouter-thinking |
Wrapper di ragionamento OpenRouter per route proxy, con salti per modelli non supportati/auto gestiti centralmente |
openrouter |
tool-stream-default-on |
Wrapper tool_stream attivo per impostazione predefinita per provider come Z.AI che vogliono lo streaming degli strumenti salvo disabilitazione esplicita |
zai |
Seam SDK che alimentano i builder delle famiglie
Ogni builder di famiglia è composto da helper pubblici di livello inferiore esportati dallo stesso package, che puoi usare quando un provider deve discostarsi dal pattern comune:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...)e i builder replay grezzi (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Esporta anche helper replay Gemini (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) e helper per endpoint/modello (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId,normalizeNativeXaiModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...), più i wrapper OpenAI/Codex condivisi (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), il wrapper compatibile con OpenAI DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), la pulizia del prefill thinking di Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper) e i wrapper proxy/provider condivisi (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini"), helper di schema Gemini sottostanti (normalizeGeminiToolSchemas,inspectGeminiToolSchemas) e helper di compatibilità xAI (resolveXaiModelCompatPatch(),applyXaiModelCompat(model)). Il Plugin xAI incluso usanormalizeResolvedModel+contributeResolvedModelCompatcon questi helper per mantenere le regole xAI di proprietà del provider.
Alcuni helper di stream restano intenzionalmente locali al provider. @openclaw/anthropic-provider mantiene wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier e i builder wrapper Anthropic di livello inferiore nella propria seam pubblica api.ts / contract-api.ts perché codificano la gestione beta OAuth di Claude e il gating context1m. Il Plugin xAI mantiene analogamente la modellazione nativa xAI Responses nel proprio wrapStreamFn (alias /fast, tool_stream predefinito, pulizia strict-tool non supportata, rimozione del payload di ragionamento specifica di xAI).
Lo stesso pattern alla radice del package supporta anche @openclaw/openai-provider (builder provider, helper per il modello predefinito, builder provider realtime) e @openclaw/openrouter-provider (builder provider più helper di onboarding/configurazione).
Scambio token
Per i provider che richiedono uno scambio token prima di ogni chiamata di inferenza:
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},
Header personalizzati
Per i provider che richiedono header di richiesta personalizzati o modifiche al body:
// wrapStreamFn returns a StreamFn derived from ctx.streamFn
wrapStreamFn: (ctx) => {
if (!ctx.streamFn) return undefined;
const inner = ctx.streamFn;
return async (params) => {
params.headers = {
...params.headers,
"X-Acme-Version": "2",
};
return inner(params);
};
},
Identità del trasporto nativo
Per i provider che richiedono header o metadati nativi di richiesta/sessione su trasporti HTTP o WebSocket generici:
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,
}),
Utilizzo e fatturazione
Per i provider che espongono dati di utilizzo/fatturazione:
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);
},
Tutti gli hook provider disponibili
OpenClaw chiama gli hook in questo ordine. La maggior parte dei provider ne usa solo 2-3:
I campi provider solo per compatibilità che OpenClaw non chiama più, come
ProviderPlugin.capabilities e suppressBuiltInModel, non sono elencati
qui.
| # | Hook | Quando usarlo |
|---|---|---|
| 1 | catalog |
Catalogo modelli o valori predefiniti dell'URL di base |
| 2 | applyConfigDefaults |
Valori predefiniti globali di proprietà del provider durante la materializzazione della configurazione |
| 3 | normalizeModelId |
Pulizia degli alias legacy/preview degli ID modello prima della lookup |
| 4 | normalizeTransport |
Pulizia api / baseUrl della famiglia provider prima dell'assemblaggio generico del modello |
| 5 | normalizeConfig |
Normalizza la configurazione models.providers.<id> |
| 6 | applyNativeStreamingUsageCompat |
Riscritture di compatibilità native per streaming-usage per provider di configurazione |
| 7 | resolveConfigApiKey |
Risoluzione auth env-marker di proprietà del provider |
| 8 | resolveSyntheticAuth |
Auth sintetica locale/self-hosted o basata su configurazione |
| 9 | shouldDeferSyntheticProfileAuth |
Abbassa i placeholder sintetici di profili salvati dietro auth env/config |
| 10 | resolveDynamicModel |
Accetta ID modello upstream arbitrari |
| 11 | prepareDynamicModel |
Recupero asincrono dei metadati prima della risoluzione |
| 12 | normalizeResolvedModel |
Riscritture del trasporto prima del runner |
| 13 | contributeResolvedModelCompat |
Flag di compatibilità per modelli vendor dietro un altro trasporto compatibile |
| 14 | normalizeToolSchemas |
Pulizia degli schemi degli strumenti di proprietà del provider prima della registrazione |
| 15 | inspectToolSchemas |
Diagnostica degli schemi degli strumenti di proprietà del provider |
| 16 | resolveReasoningOutputMode |
Contratto dell'output di ragionamento tagged vs nativo |
| 17 | prepareExtraParams |
Parametri di richiesta predefiniti |
| 18 | createStreamFn |
Trasporto StreamFn completamente personalizzato |
| 19 | wrapStreamFn |
Wrapper di header/body personalizzati sul normale percorso di stream |
| 20 | resolveTransportTurnState |
Header/metadati nativi per turno |
| 21 | resolveWebSocketSessionPolicy |
Header/cool-down di sessione WS nativi |
| 22 | formatApiKey |
Forma token runtime personalizzata |
| 23 | refreshOAuth |
Refresh OAuth personalizzato |
| 24 | buildAuthDoctorHint |
Indicazioni di riparazione auth |
| 25 | matchesContextOverflowError |
Rilevamento overflow di proprietà del provider |
| 26 | classifyFailoverReason |
Classificazione rate-limit/overload di proprietà del provider |
| 27 | isCacheTtlEligible |
Gating TTL della cache dei prompt |
| 28 | buildMissingAuthMessage |
Suggerimento missing-auth personalizzato |
| 29 | augmentModelCatalog |
Righe sintetiche di compatibilità futura |
| 30 | resolveThinkingProfile |
Set di opzioni /think specifico del modello |
| 31 | isBinaryThinking |
Compatibilità thinking binario on/off |
| 32 | supportsXHighThinking |
Compatibilità del supporto al ragionamento xhigh |
| 33 | resolveDefaultThinkingLevel |
Compatibilità della policy /think predefinita |
| 34 | isModernModelRef |
Matching dei modelli live/smoke |
| 35 | prepareRuntimeAuth |
Scambio token prima dell'inferenza |
| 36 | resolveUsageAuth |
Parsing personalizzato delle credenziali di utilizzo |
| 37 | fetchUsageSnapshot |
Endpoint di utilizzo personalizzato |
| 38 | createEmbeddingProvider |
Adapter embedding di proprietà del provider per memoria/ricerca |
| 39 | buildReplayPolicy |
Policy personalizzata di replay/Compaction della trascrizione |
| 40 | sanitizeReplayHistory |
Riscritture replay specifiche del provider dopo la pulizia generica |
| 41 | validateReplayTurns |
Validazione rigorosa dei turni replay prima del runner incorporato |
| 42 | onModelSelected |
Callback post-selezione (ad es. telemetria) |
Note sul fallback runtime:
normalizeConfigcontrolla prima il provider corrispondente, poi altri Plugin provider capaci di hook finché uno non modifica effettivamente la configurazione. Se nessun hook provider riscrive una voce di configurazione supportata della famiglia Google, si applica comunque il normalizzatore di configurazione Google incluso.resolveConfigApiKeyusa l'hook provider quando esposto. Anche il percorsoamazon-bedrockincluso ha qui un resolver env-marker AWS integrato, anche se l'auth runtime di Bedrock stessa usa ancora la catena predefinita dell'AWS SDK.resolveSystemPromptContributionconsente a un provider di iniettare indicazioni cache-aware per il prompt di sistema per una famiglia di modelli. Preferiscilo abefore_prompt_buildquando il comportamento appartiene a una famiglia provider/modello e deve preservare la separazione stabile/dinamica della cache.
Per descrizioni dettagliate ed esempi reali, consulta Internals: Hook runtime provider.
Aggiungi funzionalità extra (opzionale)
Passaggio 5: Aggiungi funzionalità extra
Un Plugin provider può registrare sintesi vocale, trascrizione realtime, voce realtime, comprensione dei media, generazione di immagini, generazione di video, fetch web e ricerca web insieme all'inferenza testuale. OpenClaw classifica questo come un Plugin hybrid-capability: il pattern consigliato per i Plugin aziendali (un Plugin per vendor). Consulta Internals: Proprietà delle funzionalità.
Registra ogni funzionalità dentro register(api) insieme alla tua chiamata
api.registerProvider(...) esistente. Scegli solo le schede necessarie:
Discorso (TTS)
import {
assertOkOrThrowProviderError,
postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";
api.registerSpeechProvider({
id: "acme-ai",
label: "Acme Speech",
isConfigured: ({ config }) => Boolean(config.messages?.tts),
synthesize: async (req) => {
const { response, release } = await postJsonRequest({
url: "https://api.example.com/v1/speech",
headers: new Headers({ "Content-Type": "application/json" }),
body: { text: req.text },
timeoutMs: req.timeoutMs,
fetchFn: fetch,
auditContext: "acme speech",
});
try {
await assertOkOrThrowProviderError(response, "Acme Speech API error");
return {
audioBuffer: Buffer.from(await response.arrayBuffer()),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
};
} finally {
await release();
}
},
});
Usa assertOkOrThrowProviderError(...) per gli errori HTTP del provider, così
i plugin condividono letture limitate del corpo dell'errore, parsing degli errori JSON e
suffissi con ID richiesta.
Trascrizione in tempo reale
Preferisci createRealtimeTranscriptionWebSocketSession(...): l'helper condiviso
gestisce acquisizione del proxy, backoff di riconnessione, flush alla chiusura, handshake
di pronto, accodamento audio e diagnostica degli eventi di chiusura. Il tuo plugin
mappa solo gli eventi upstream.
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" });
},
});
},
});
I provider STT batch che inviano audio multipart tramite POST devono usare
buildAudioTranscriptionFormData(...) da
openclaw/plugin-sdk/provider-http. L'helper normalizza i nomi file di upload,
inclusi gli upload AAC che richiedono un nome file in stile M4A per
API di trascrizione compatibili.
Voce in tempo reale
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,
}),
});
Dichiara capabilities così talk.catalog può esporre modalità valide,
trasporti, formati audio e flag di funzionalità ai client Talk browser e nativi.
Implementa handleBargeIn quando un trasporto può rilevare che una
persona sta interrompendo la riproduzione dell'assistente e il provider supporta
il troncamento o la cancellazione della risposta audio attiva.
Comprensione dei media
api.registerMediaUnderstandingProvider({
id: "acme-ai",
capabilities: ["image", "audio"],
describeImage: async (req) => ({ text: "A photo of..." }),
transcribeAudio: async (req) => ({ text: "Transcript..." }),
});
Generazione di immagini e video
Le funzionalità video usano una forma consapevole della modalità: generate,
imageToVideo e videoToVideo. Campi aggregati piatti come
maxInputImages / maxInputVideos / maxDurationSeconds non sono
sufficienti per dichiarare in modo chiaro il supporto alla modalità di trasformazione o le modalità disabilitate.
La generazione musicale segue lo stesso schema con blocchi espliciti generate /
edit.
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: [] }),
});
Recupero e ricerca web
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
Passaggio 6: Test
import { describe, it, expect } from "vitest";
// Export your provider config object from index.ts or a dedicated file
import { 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();
});
});
Pubblicare su ClawHub
I plugin provider si pubblicano nello stesso modo di qualsiasi altro plugin di codice esterno:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
Non usare qui l'alias di pubblicazione legacy solo per Skills; i pacchetti plugin devono usare
clawhub package publish.
Struttura dei file
<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)
Riferimento dell'ordine del catalogo
catalog.order controlla quando il tuo catalogo viene unito rispetto ai provider
integrati:
| Ordine | Quando | Caso d'uso |
|---|---|---|
simple |
Primo passaggio | Provider con semplice chiave API |
profile |
Dopo simple | Provider vincolati ai profili di autenticazione |
paired |
Dopo profile | Sintetizzare più voci correlate |
late |
Ultimo passaggio | Sovrascrivere provider esistenti (vince in caso di collisione) |
Passaggi successivi
- Plugin di canale - se il tuo plugin fornisce anche un canale
- Runtime SDK - helper
api.runtime(TTS, ricerca, subagent) - Panoramica SDK - riferimento completo agli import dei sottopercorsi
- Interni del plugin - dettagli sugli hook ed esempi integrati