Building plugins
Створення Plugin постачальників
Цей посібник показує, як створити Plugin провайдера, який додає провайдера моделей (LLM) до OpenClaw. Наприкінці у вас буде провайдер із каталогом моделей, автентифікацією через API-ключ і динамічним визначенням моделей.
Покроковий посібник
Пакет і маніфест
Крок 1: Пакет і маніфест
{"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}}Маніфест оголошує providerAuthEnvVars, щоб OpenClaw міг виявляти
облікові дані без завантаження runtime вашого Plugin. Додайте
providerAuthAliases, коли варіант провайдера має повторно використовувати
автентифікацію ідентифікатора іншого провайдера. modelSupport
є необов’язковим і дає OpenClaw змогу автоматично завантажувати Plugin
вашого провайдера за скороченими ідентифікаторами моделей на кшталт
acme-large ще до появи runtime-хуків. Якщо ви публікуєте провайдера
на ClawHub, ці поля openclaw.compat і openclaw.build є обов’язковими
в package.json.
Зареєструйте провайдера
Мінімальному текстовому провайдеру потрібні id, label, auth і catalog.
catalog — це runtime/config-хук, яким володіє провайдер; він може викликати
live API постачальника та повертати записи models.providers.
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 — це новіша поверхня каталогу рівня керування
для інтерфейсу списків, довідки та вибору. Використовуйте її для рядків
text, image-generation, video-generation і music-generation. Залишайте
виклики endpoint постачальника та мапінг відповідей у Plugin; OpenClaw
володіє спільною формою рядків, мітками джерел і рендерингом довідки.
Це вже робочий провайдер. Тепер користувачі можуть виконати
openclaw onboard --acme-ai-api-key <key> і вибрати
acme-ai/acme-large як свою модель.
Якщо upstream-провайдер використовує інші керівні токени, ніж OpenClaw, додайте невелике двонапрямне текстове перетворення замість заміни шляху стримінгу:
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 переписує фінальний системний prompt і вміст текстових повідомлень
перед передаванням. output переписує текстові дельти асистента та фінальний
текст до того, як OpenClaw розбере власні керівні маркери або доставку
каналом.
Для вбудованих провайдерів, які реєструють лише одного текстового провайдера
з автентифікацією через API-ключ і одним runtime на базі каталогу, віддавайте
перевагу вужчому допоміжному методу 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 — це шлях live-каталогу, який використовується, коли OpenClaw
може визначити реальну автентифікацію провайдера. Він може виконувати
специфічне для провайдера виявлення. Використовуйте buildStaticProvider
лише для офлайн-рядків, які безпечно показувати до налаштування
автентифікації; він не повинен вимагати облікових даних або виконувати
мережеві запити. Відображення OpenClaw models list --all наразі виконує
статичні каталоги лише для вбудованих Plugin-ів провайдерів, із порожньою
конфігурацією, порожнім env і без шляхів агента/робочого простору.
Якщо вашому потоку автентифікації також потрібно змінювати
models.providers.*, псевдоніми та модель агента за замовчуванням під час
onboarding, використовуйте preset-допоміжні методи з
openclaw/plugin-sdk/provider-onboard. Найвужчі допоміжні методи:
createDefaultModelPresetAppliers(...),
createDefaultModelsPresetAppliers(...) і
createModelCatalogPresetAppliers(...).
Коли нативний endpoint провайдера підтримує блоки використання у стримінгу
на звичайному транспорті openai-completions, віддавайте перевагу спільним
допоміжним методам каталогу в
openclaw/plugin-sdk/provider-catalog-shared замість жорстко закодованих
перевірок ідентифікатора провайдера. supportsNativeStreamingUsageCompat(...)
і applyProviderNativeStreamingUsageCompat(...) визначають підтримку з мапи
можливостей endpoint, тож нативні endpoint-и у стилі Moonshot/DashScope усе
ще вмикаються, навіть коли Plugin використовує власний ідентифікатор
провайдера.
Додайте динамічне визначення моделей
Якщо ваш провайдер приймає довільні ідентифікатори моделей (як proxy або
router), додайте 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, }),});Якщо для визначення потрібен мережевий виклик, використовуйте
prepareDynamicModel для асинхронного прогрівання — resolveDynamicModel
запускається знову після його завершення.
Додайте runtime-хуки (за потреби)
Більшості провайдерів потрібні лише catalog + resolveDynamicModel.
Додавайте хуки поступово, коли вони потрібні вашому провайдеру.
Спільні допоміжні builder-и тепер покривають найпоширеніші сімейства replay/tool-compat, тож Plugin-ам зазвичай не потрібно вручну під’єднувати кожен хук окремо:
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,});Доступні сьогодні сімейства replay:
| Сімейство | Що підключає | Вбудовані приклади |
|---|---|---|
openai-compatible |
Спільна політика повторного відтворення у стилі OpenAI для OpenAI-сумісних транспортів, зокрема очищення tool-call-id, виправлення порядку assistant-first і загальна валідація ходів Gemini там, де вона потрібна транспорту | moonshot, ollama, xai, zai |
anthropic-by-model |
Політика повторного відтворення з урахуванням Claude, вибрана за modelId, щоб транспорти Anthropic-message отримували очищення блоків мислення, специфічне для Claude, лише коли розв’язана модель справді має ідентифікатор Claude |
amazon-bedrock, anthropic-vertex |
google-gemini |
Нативна політика повторного відтворення Gemini, а також очищення початкового повторного відтворення і режим позначеного виводу reasoning | google, google-gemini-cli |
passthrough-gemini |
Очищення thought-signature Gemini для моделей Gemini, що працюють через OpenAI-сумісні проксі-транспорти; не вмикає нативну валідацію повторного відтворення Gemini або переписування початкового завантаження | openrouter, kilocode, opencode, opencode-go |
hybrid-anthropic-openai |
Гібридна політика для провайдерів, які поєднують поверхні моделей Anthropic-message і OpenAI-сумісні поверхні моделей в одному plugin; необов’язкове скидання блоків мислення лише для Claude залишається обмеженим стороною Anthropic | minimax |
Доступні сьогодні сімейства потоків:
| Сімейство | Що підключає | Вбудовані приклади |
|---|---|---|
google-thinking |
Нормалізація payload мислення Gemini на спільному шляху потоку | google, google-gemini-cli |
kilocode-thinking |
Обгортка reasoning Kilo на спільному шляху проксі-потоку, де kilo/auto і непідтримувані ідентифікатори proxy reasoning пропускають ін’єктоване мислення |
kilocode |
moonshot-thinking |
Відображення бінарного нативного payload мислення Moonshot з конфігурації + рівня /think |
moonshot |
minimax-fast-mode |
Переписування моделі fast-mode MiniMax на спільному шляху потоку | minimax, minimax-portal |
openai-responses-defaults |
Спільні нативні обгортки OpenAI/Codex Responses: заголовки атрибуції, /fast/serviceTier, докладність тексту, нативний вебпошук Codex, формування payload сумісності reasoning і керування контекстом Responses |
openai, openai-codex |
openrouter-thinking |
Обгортка reasoning OpenRouter для проксі-маршрутів, із централізованою обробкою пропусків для непідтримуваних моделей/auto |
openrouter |
tool-stream-default-on |
Обгортка tool_stream, увімкнена за замовчуванням, для провайдерів на кшталт Z.AI, які хочуть потокове передавання інструментів, якщо його явно не вимкнено |
zai |
Шви SDK, що забезпечують роботу побудовників сімейств
Кожен побудовник сімейства складений із нижчорівневих публічних допоміжних функцій, експортованих із того самого пакета, до яких можна звернутися, коли провайдеру потрібно відійти від спільного шаблону:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily,buildProviderReplayFamilyHooks(...), і сирі побудовники повторного відтворення (buildOpenAICompatibleReplayPolicy,buildAnthropicReplayPolicyForModel,buildGoogleGeminiReplayPolicy,buildHybridAnthropicOrOpenAIReplayPolicy). Також експортує допоміжні функції повторного відтворення Gemini (sanitizeGoogleGeminiReplayHistory,resolveTaggedReasoningOutputMode) і допоміжні функції endpoint/model (resolveProviderEndpoint,normalizeProviderId,normalizeGooglePreviewModelId).openclaw/plugin-sdk/provider-stream-ProviderStreamFamily,buildProviderStreamFamilyHooks(...),composeProviderStreamWrappers(...), а також спільні обгортки OpenAI/Codex (createOpenAIAttributionHeadersWrapper,createOpenAIFastModeWrapper,createOpenAIServiceTierWrapper,createOpenAIResponsesContextManagementWrapper,createCodexNativeWebSearchWrapper), OpenAI-сумісна обгортка DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), очищення префілу мислення Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper) і спільні проксі/провайдерські обгортки (createOpenRouterWrapper,createToolStreamWrapper,createMinimaxFastModeWrapper).openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily,buildProviderToolCompatFamilyHooks("gemini")і базові допоміжні функції схем Gemini (normalizeGeminiToolSchemas,inspectGeminiToolSchemas).
Деякі допоміжні функції потоків навмисно залишаються локальними для провайдера. @openclaw/anthropic-provider тримає wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier і нижчорівневі побудовники обгорток Anthropic у власному публічному шві api.ts / contract-api.ts, тому що вони кодують обробку Claude OAuth beta і gating context1m. Plugin xAI так само тримає нативне формування xAI Responses у власному wrapStreamFn (аліаси /fast, стандартний tool_stream, очищення непідтримуваного strict-tool, вилучення reasoning-payload, специфічне для xAI).
Такий самий шаблон package-root також підтримує @openclaw/openai-provider (побудовники провайдера, допоміжні функції моделі за замовчуванням, побудовники realtime-провайдера) і @openclaw/openrouter-provider (побудовник провайдера плюс допоміжні функції onboarding/config).
Обмін токенів
Для провайдерів, яким потрібен обмін токена перед кожним викликом inference:
prepareRuntimeAuth: async (ctx) => { const exchanged = await exchangeToken(ctx.apiKey); return { apiKey: exchanged.token, baseUrl: exchanged.baseUrl, expiresAt: exchanged.expiresAt, };},Власні заголовки
Для провайдерів, яким потрібні власні заголовки запиту або модифікації тіла:
// 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); };},Нативна ідентичність транспорту
Для провайдерів, яким потрібні нативні заголовки запиту/сеансу або метадані на загальних HTTP- чи WebSocket-транспортах:
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,}),Використання та білінг
Для провайдерів, які надають дані використання/білінгу:
resolveUsageAuth: async (ctx) => { const auth = await ctx.resolveOAuthToken(); return auth ? { token: auth.token } : null;},fetchUsageSnapshot: async (ctx) => { return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);},Усі доступні hooks провайдера
OpenClaw викликає hooks у такому порядку. Більшість провайдерів використовують лише 2-3:
Поля провайдера лише для сумісності, які OpenClaw більше не викликає, як-от
ProviderPlugin.capabilities і suppressBuiltInModel, тут не перелічені.
| # | Hook | Коли використовувати |
|---|---|---|
| 1 | catalog |
Каталог моделей або стандартні значення базового URL |
| 2 | applyConfigDefaults |
Глобальні стандартні значення, якими володіє провайдер, під час матеріалізації конфігурації |
| 3 | normalizeModelId |
Очищення застарілих/preview псевдонімів model-id перед пошуком |
| 4 | normalizeTransport |
Очищення api / baseUrl сімейства провайдера перед загальною збіркою моделі |
| 5 | normalizeConfig |
Нормалізація конфігурації models.providers.<id> |
| 6 | applyNativeStreamingUsageCompat |
Переписування сумісності нативного streaming-usage для конфігураційних провайдерів |
| 7 | resolveConfigApiKey |
Розв’язання auth env-marker, яким володіє провайдер |
| 8 | resolveSyntheticAuth |
Синтетичний auth для локальних/self-hosted або config-backed випадків |
| 9 | shouldDeferSyntheticProfileAuth |
Пониження synthetic stored-profile placeholders після env/config auth |
| 10 | resolveDynamicModel |
Приймати довільні ідентифікатори upstream моделей |
| 11 | prepareDynamicModel |
Асинхронне отримання метаданих перед розв’язанням |
| 12 | normalizeResolvedModel |
Переписування транспорту перед runner |
| 13 | contributeResolvedModelCompat |
Прапорці сумісності для vendor моделей за іншим сумісним транспортом |
| 14 | normalizeToolSchemas |
Очищення tool-schema, яким володіє провайдер, перед реєстрацією |
| 15 | inspectToolSchemas |
Діагностика tool-schema, якою володіє провайдер |
| 16 | resolveReasoningOutputMode |
Контракт tagged vs native reasoning-output |
| 17 | prepareExtraParams |
Стандартні параметри запиту |
| 18 | createStreamFn |
Повністю власний транспорт StreamFn |
| 19 | wrapStreamFn |
Власні обгортки заголовків/тіла на звичайному шляху потоку |
| 20 | resolveTransportTurnState |
Нативні заголовки/метадані для кожного ходу |
| 21 | resolveWebSocketSessionPolicy |
Нативні заголовки сеансу WS/cool-down |
| 22 | formatApiKey |
Власна форма runtime-токена |
| 23 | refreshOAuth |
Власне оновлення OAuth |
| 24 | buildAuthDoctorHint |
Підказки щодо відновлення auth |
| 25 | matchesContextOverflowError |
Виявлення overflow, яким володіє провайдер |
| 26 | classifyFailoverReason |
Класифікація rate-limit/overload, якою володіє провайдер |
| 27 | isCacheTtlEligible |
Gating TTL кешу prompt |
| 28 | buildMissingAuthMessage |
Власна підказка щодо відсутнього auth |
| 29 | augmentModelCatalog |
Синтетичні рядки forward-compat |
| 30 | resolveThinkingProfile |
Набір опцій /think, специфічний для моделі |
| 31 | isBinaryThinking |
Сумісність двійкового увімкнення/вимкнення мислення |
| 32 | supportsXHighThinking |
Сумісність підтримки reasoning xhigh |
| 33 | resolveDefaultThinkingLevel |
Сумісність стандартної політики /think |
| 34 | isModernModelRef |
Зіставлення моделей live/smoke |
| 35 | prepareRuntimeAuth |
Обмін токена перед inference |
| 36 | resolveUsageAuth |
Власний розбір облікових даних usage |
| 37 | fetchUsageSnapshot |
Власний endpoint usage |
| 38 | createEmbeddingProvider |
Адаптер embeddings для memory/search, яким володіє провайдер |
| 39 | buildReplayPolicy |
Власна політика повторного відтворення/Compaction transcript |
| 40 | sanitizeReplayHistory |
Специфічні для провайдера переписування replay після загального очищення |
| 41 | validateReplayTurns |
Сувора валідація replay-turn перед вбудованим runner |
| 42 | onModelSelected |
Callback після вибору (наприклад, telemetry) |
Примітки щодо runtime fallback:
normalizeConfigспершу перевіряє зіставленого провайдера, а потім інші provider plugins із підтримкою hooks, доки один із них справді не змінить конфігурацію. Якщо жоден hook провайдера не переписує підтримуваний запис конфігурації сімейства Google, вбудований нормалізатор конфігурації Google усе одно застосовується.resolveConfigApiKeyвикористовує hook провайдера, коли його надано. Вбудований шляхamazon-bedrockтакож має тут вбудований resolver AWS env-marker, хоча сам runtime auth Bedrock усе ще використовує стандартний ланцюг AWS SDK.resolveSystemPromptContributionдає провайдеру змогу вставити cache-aware підказки system-prompt для сімейства моделей. Віддавайте йому перевагу надbefore_prompt_build, коли поведінка належить одному провайдеру/сімейству моделей і має зберегти стабільне/динамічне розділення кешу.
Докладні описи та реальні приклади див. у Внутрішня архітектура: Provider Runtime Hooks.
Додайте додаткові можливості (необов’язково)
Крок 5: Додайте додаткові можливості
Плагін провайдера може реєструвати мовлення, транскрипцію в реальному часі, голос у реальному часі, розуміння медіа, генерацію зображень, генерацію відео, веботримання та вебпошук разом із текстовим інференсом. OpenClaw класифікує це як плагін із гібридними можливостями — рекомендований патерн для плагінів компаній (один плагін на постачальника). Див. Внутрішнє: володіння можливостями.
Реєструйте кожну можливість у register(api) поруч із наявним викликом
api.registerProvider(...). Виберіть лише потрібні вкладки:
Мовлення (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(); } },});Використовуйте assertOkOrThrowProviderError(...) для HTTP-збоїв провайдера, щоб
плагіни спільно використовували обмежене читання тіла помилки, парсинг помилок JSON і
суфікси ідентифікаторів запитів.
Транскрипція в реальному часі
Надавайте перевагу createRealtimeTranscriptionWebSocketSession(...) — спільний
помічник обробляє захоплення проксі, затримку повторного підключення, скидання під час закриття, готові
рукостискання, постановку аудіо в чергу та діагностику подій закриття. Ваш плагін
лише зіставляє події вищого рівня.
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" }); }, }); },});Пакетні STT-провайдери, які надсилають multipart-аудіо через POST, повинні використовувати
buildAudioTranscriptionFormData(...) з
openclaw/plugin-sdk/provider-http. Помічник нормалізує імена файлів для завантаження,
зокрема завантаження AAC, яким потрібна назва файлу в стилі M4A для
сумісних API транскрипції.
Голос у реальному часі
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, }),});Оголошуйте capabilities, щоб talk.catalog міг надавати чинні режими,
транспорти, аудіоформати та прапорці функцій клієнтам Talk у браузері та нативних застосунках.
Реалізуйте handleBargeIn, коли транспорт може визначити, що
людина перериває відтворення асистента, а провайдер підтримує
обрізання або очищення активної аудіовідповіді.
Розуміння медіа
api.registerMediaUnderstandingProvider({ id: "acme-ai", capabilities: ["image", "audio"], describeImage: async (req) => ({ text: "A photo of..." }), transcribeAudio: async (req) => ({ text: "Transcript..." }),});Генерація зображень і відео
Можливості відео використовують форму, залежну від режиму: generate,
imageToVideo і videoToVideo. Плоских агрегованих полів на кшталт
maxInputImages / maxInputVideos / maxDurationSeconds недостатньо,
щоб коректно оголосити підтримку режиму трансформації або вимкнені режими.
Генерація музики використовує такий самий патерн із явними блоками 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: [] }),});Веботримання та пошук
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: [] }),});Тест
Крок 6: Тест
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(); });});Публікація в ClawHub
Плагіни провайдерів публікуються так само, як і будь-який інший зовнішній кодовий плагін:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginНе використовуйте тут застарілий псевдонім публікації лише для skill; пакети плагінів мають використовувати
clawhub package publish.
Структура файлів
<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)Довідник порядку каталогу
catalog.order контролює, коли ваш каталог об’єднується відносно вбудованих
провайдерів:
| Порядок | Коли | Випадок використання |
|---|---|---|
simple |
Перший прохід | Звичайні провайдери API-ключів |
profile |
Після simple | Провайдери, обмежені профілями автентифікації |
paired |
Після profile | Синтез кількох пов’язаних записів |
late |
Останній прохід | Перевизначення наявних провайдерів (перемагає в разі конфлікту) |
Наступні кроки
- Плагіни каналів — якщо ваш плагін також надає канал
- SDK Runtime — помічники
api.runtime(TTS, пошук, субагент) - Огляд SDK — повний довідник імпорту підшляхів
- Внутрішнє Plugin — подробиці хуків і вбудовані приклади