Перейти до основного вмісту

Створення плагінів провайдерів

Цей посібник пояснює, як створити плагін провайдера, який додає до OpenClaw провайдера моделей (LLM). Наприкінці у вас буде провайдер із каталогом моделей, автентифікацією за API-ключем і динамічним визначенням моделей.
Якщо ви ще не створювали жодного плагіна OpenClaw, спочатку прочитайте Getting Started про базову структуру пакунка та налаштування маніфесту.

Покроковий посібник

1
2

Пакунок і маніфест

{
  "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"
    }
  }
}
Маніфест оголошує providerAuthEnvVars, щоб OpenClaw міг визначати облікові дані без завантаження runtime вашого плагіна. modelSupport є необов’язковим і дозволяє OpenClaw автоматично завантажувати ваш плагін провайдера за скороченими ідентифікаторами моделей на кшталт acme-large ще до появи runtime-хуків. Якщо ви публікуєте провайдера в ClawHub, поля openclaw.compat і openclaw.build є обов’язковими в package.json.
3

Зареєструйте провайдера

Мінімальному провайдеру потрібні id, label, auth і catalog:
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,
                },
              ],
            },
          };
        },
      },
    });
  },
});
Це вже робочий провайдер. Тепер користувачі можуть виконати openclaw onboard --acme-ai-api-key <key> і вибрати acme-ai/acme-large як свою модель.Для вбудованих провайдерів, які реєструють лише одного текстового провайдера з автентифікацією за 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" }],
      }),
    },
  },
});
Якщо для вашого потоку автентифікації також потрібно змінювати models.providers.*, псевдоніми та типову модель агента під час onboarding, використовуйте попередньо налаштовані допоміжні засоби з openclaw/plugin-sdk/provider-onboard. Найвужчими допоміжними засобами є createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) і createModelCatalogPresetAppliers(...).Коли нативна кінцева точка провайдера підтримує потокові блоки usage у звичайному транспорті openai-completions, надавайте перевагу спільним допоміжним засобам каталогу в openclaw/plugin-sdk/provider-catalog-shared замість жорсткого кодування перевірок provider-id. Функції supportsNativeStreamingUsageCompat(...) і applyProviderNativeStreamingUsageCompat(...) визначають підтримку за картою можливостей кінцевої точки, тому нативні кінцеві точки на кшталт Moonshot/DashScope все одно підключаються, навіть коли плагін використовує власний provider id.
4

Додайте динамічне визначення моделі

Якщо ваш провайдер приймає довільні ідентифікатори моделей (як проксі або маршрутизатор), додайте 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 виконується знову.
5

Додайте runtime-хуки (за потреби)

Більшості провайдерів потрібні лише catalog + resolveDynamicModel. Додавайте хуки поступово, відповідно до потреб вашого провайдера.Спільні побудовники допоміжних засобів тепер охоплюють найпоширеніші сімейства replay/tool-compat, тому плагінам зазвичай не потрібно вручну під’єднувати кожен хук окремо:
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:
FamilyЩо воно підключає
openai-compatibleСпільна політика replay у стилі OpenAI для OpenAI-сумісних транспортів, зокрема очищення tool-call-id, виправлення порядку assistant-first і загальну перевірку Gemini-turn там, де це потрібно транспорту
anthropic-by-modelПолітика replay з урахуванням Claude, що вибирається за modelId, тому транспорти повідомлень Anthropic отримують очищення thinking-block, специфічне для Claude, лише тоді, коли визначена модель справді є ідентифікатором Claude
google-geminiНативна політика replay для Gemini плюс очищення bootstrap replay і тегований режим reasoning-output
passthrough-geminiОчищення thought-signature для Gemini-моделей, що працюють через OpenAI-сумісні проксі-транспорти; не вмикає нативну перевірку Gemini replay або переписування bootstrap
hybrid-anthropic-openaiГібридна політика для провайдерів, які поєднують поверхні моделей Anthropic-message і OpenAI-compatible в одному плагіні; необов’язкове відкидання thinking-block лише для Claude залишається обмеженим стороною Anthropic
Реальні вбудовані приклади:
  • google: google-gemini
  • openrouter, kilocode, opencode і opencode-go: passthrough-gemini
  • amazon-bedrock і anthropic-vertex: anthropic-by-model
  • minimax: hybrid-anthropic-openai
  • moonshot, ollama, xai і zai: openai-compatible
Доступні на сьогодні сімейства stream:
FamilyЩо воно підключає
google-thinkingНормалізація payload thinking Gemini на спільному шляху stream
kilocode-thinkingОбгортка Kilo reasoning на спільному шляху proxy stream, де kilo/auto і непідтримувані ідентифікатори reasoning проксі пропускають ін’єкцію thinking
moonshot-thinkingВідображення бінарного payload native-thinking Moonshot із конфігурації + рівня /think
minimax-fast-modeПереписування моделі MiniMax fast-mode на спільному шляху stream
openai-responses-defaultsСпільні нативні обгортки OpenAI/Codex Responses: заголовки атрибуції, /fast/serviceTier, текстова деталізація, нативний вебпошук Codex, формування payload для сумісності reasoning і керування контекстом Responses
openrouter-thinkingОбгортка reasoning OpenRouter для маршрутів proxy, з централізованою обробкою пропусків для непідтримуваних моделей/auto
tool-stream-default-onОбгортка tool_stream, увімкнена типово, для провайдерів на кшталт Z.AI, яким потрібен потоковий режим інструментів, якщо його явно не вимкнено
Реальні вбудовані приклади:
  • google: google-thinking
  • kilocode: kilocode-thinking
  • moonshot: moonshot-thinking
  • minimax і minimax-portal: minimax-fast-mode
  • openai і openai-codex: openai-responses-defaults
  • openrouter: openrouter-thinking
  • zai: tool-stream-default-on
openclaw/plugin-sdk/provider-model-shared також експортує enum сімейств replay разом зі спільними допоміжними засобами, з яких ці сімейства побудовані. Поширені публічні експорти містять:
  • ProviderReplayFamily
  • buildProviderReplayFamilyHooks(...)
  • спільні побудовники replay, як-от buildOpenAICompatibleReplayPolicy(...), buildAnthropicReplayPolicyForModel(...), buildGoogleGeminiReplayPolicy(...) і buildHybridAnthropicOrOpenAIReplayPolicy(...)
  • допоміжні засоби replay для Gemini, як-от sanitizeGoogleGeminiReplayHistory(...) і resolveTaggedReasoningOutputMode()
  • допоміжні засоби для кінцевих точок/моделей, як-от resolveProviderEndpoint(...), normalizeProviderId(...), normalizeGooglePreviewModelId(...) і normalizeNativeXaiModelId(...)
openclaw/plugin-sdk/provider-stream надає і побудовник сімейств, і публічні допоміжні засоби обгорток, які ці сімейства повторно використовують. Поширені публічні експорти містять:
  • ProviderStreamFamily
  • buildProviderStreamFamilyHooks(...)
  • composeProviderStreamWrappers(...)
  • спільні обгортки OpenAI/Codex, як-от createOpenAIAttributionHeadersWrapper(...), createOpenAIFastModeWrapper(...), createOpenAIServiceTierWrapper(...), createOpenAIResponsesContextManagementWrapper(...) і createCodexNativeWebSearchWrapper(...)
  • спільні обгортки proxy/провайдерів, як-от createOpenRouterWrapper(...), createToolStreamWrapper(...) і createMinimaxFastModeWrapper(...)
Деякі допоміжні засоби stream навмисно залишаються локальними для провайдера. Поточний вбудований приклад: @openclaw/anthropic-provider експортує wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier і низькорівневі побудовники обгорток Anthropic зі свого публічного шва api.ts / contract-api.ts. Ці допоміжні засоби залишаються специфічними для Anthropic, оскільки вони також кодують обробку бета-версій Claude OAuth і обмеження context1m.Інші вбудовані провайдери також зберігають транспортно-специфічні обгортки локально, коли поведінка не ділиться чисто між сімействами. Поточний приклад: вбудований плагін xAI зберігає власне формування native xAI Responses у своєму wrapStreamFn, зокрема переписування псевдонімів /fast, типовий tool_stream, очищення непідтримуваного strict-tool і видалення payload reasoning, специфічного для xAI.openclaw/plugin-sdk/provider-tools наразі надає одне спільне сімейство схем сумісності з інструментами плюс спільні допоміжні засоби схем/сумісності:
  • ProviderToolCompatFamily документує поточний перелік спільних сімейств.
  • buildProviderToolCompatFamilyHooks("gemini") підключає очищення схем Gemini + діагностику для провайдерів, яким потрібні безпечні для Gemini схеми інструментів.
  • normalizeGeminiToolSchemas(...) і inspectGeminiToolSchemas(...) є базовими публічними допоміжними засобами схем Gemini.
  • resolveXaiModelCompatPatch() повертає вбудований патч сумісності xAI: toolSchemaProfile: "xai", непідтримувані ключові слова схеми, нативну підтримку web_search і декодування аргументів tool-call із HTML-сутностей.
  • applyXaiModelCompat(model) застосовує той самий патч сумісності xAI до визначеної моделі до того, як вона потрапить до runner.
Реальний вбудований приклад: плагін xAI використовує normalizeResolvedModel плюс contributeResolvedModelCompat, щоб метадані сумісності залишалися у власності провайдера, а не правила xAI були жорстко закодовані в ядрі.Той самий шаблон кореня пакунка також лежить в основі інших вбудованих провайдерів:
  • @openclaw/openai-provider: api.ts експортує побудовники провайдерів, допоміжні засоби типової моделі та побудовники провайдерів realtime
  • @openclaw/openrouter-provider: api.ts експортує побудовник провайдера плюс допоміжні засоби onboarding/config
Для провайдерів, яким потрібен обмін токенів перед кожним викликом inference:
prepareRuntimeAuth: async (ctx) => {
  const exchanged = await exchangeToken(ctx.apiKey);
  return {
    apiKey: exchanged.token,
    baseUrl: exchanged.baseUrl,
    expiresAt: exchanged.expiresAt,
  };
},
OpenClaw викликає хуки в такому порядку. Більшість провайдерів використовують лише 2-3:
#HookКоли використовувати
1catalogКаталог моделей або типові значення base URL
2applyConfigDefaultsГлобальні типові значення, що належать провайдеру, під час матеріалізації конфігурації
3normalizeModelIdОчищення застарілих/preview псевдонімів model-id перед пошуком
4normalizeTransportОчищення api / baseUrl для сімейства провайдера перед загальним складанням моделі
5normalizeConfigНормалізація конфігурації models.providers.<id>
6applyNativeStreamingUsageCompatПереписування сумісності native streaming-usage для конфігурованих провайдерів
7resolveConfigApiKeyВизначення автентифікації за маркером env, що належить провайдеру
8resolveSyntheticAuthСинтетична автентифікація для локальних/self-hosted або конфігурованих варіантів
9shouldDeferSyntheticProfileAuthРозміщення синтетичних плейсхолдерів збереженого профілю нижче від env/config auth
10resolveDynamicModelПрийом довільних upstream ідентифікаторів моделей
11prepareDynamicModelАсинхронне отримання метаданих перед визначенням
12normalizeResolvedModelПереписування транспорту перед runner
Примітки щодо runtime fallback:
  • normalizeConfig спочатку перевіряє відповідний провайдер, а потім інші плагіни провайдерів із підтримкою хуків, доки якийсь із них справді не змінить конфігурацію. Якщо жоден provider hook не перепише підтримуваний запис конфігурації сімейства Google, усе одно буде застосовано вбудований нормалізатор конфігурації Google.
  • resolveConfigApiKey використовує provider hook, якщо його надано. Вбудований шлях amazon-bedrock також має тут вбудований AWS env-marker resolver, хоча сама runtime auth Bedrock усе ще використовує стандартний ланцюжок AWS SDK. | 13 | contributeResolvedModelCompat | Прапорці сумісності для моделей вендора за іншим сумісним транспортом | | 14 | capabilities | Застарілий статичний набір можливостей; лише для сумісності | | 15 | normalizeToolSchemas | Очищення схем інструментів, що належить провайдеру, перед реєстрацією | | 16 | inspectToolSchemas | Діагностика схем інструментів, що належить провайдеру | | 17 | resolveReasoningOutputMode | Контракт тегованого або нативного reasoning-output | | 18 | prepareExtraParams | Типові параметри запиту | | 19 | createStreamFn | Повністю власний транспорт StreamFn | | 20 | wrapStreamFn | Обгортки власних заголовків/тіла на звичайному шляху stream | | 21 | resolveTransportTurnState | Нативні заголовки/метадані для кожного turn | | 22 | resolveWebSocketSessionPolicy | Нативні заголовки сеансу WS/cool-down | | 23 | formatApiKey | Власна форма runtime-токена | | 24 | refreshOAuth | Власне оновлення OAuth | | 25 | buildAuthDoctorHint | Підказка для виправлення auth | | 26 | matchesContextOverflowError | Визначення переповнення, що належить провайдеру | | 27 | classifyFailoverReason | Класифікація rate-limit/overload, що належить провайдеру | | 28 | isCacheTtlEligible | Контроль TTL кешу prompt | | 29 | buildMissingAuthMessage | Власна підказка про відсутню auth | | 30 | suppressBuiltInModel | Приховування застарілих upstream-рядків | | 31 | augmentModelCatalog | Синтетичні рядки для сумісності вперед | | 32 | isBinaryThinking | Бінарне вмикання/вимикання thinking | | 33 | supportsXHighThinking | Підтримка reasoning xhigh | | 34 | resolveDefaultThinkingLevel | Типова політика /think | | 35 | isModernModelRef | Відповідність live/smoke моделей | | 36 | prepareRuntimeAuth | Обмін токенів перед inference | | 37 | resolveUsageAuth | Власний розбір облікових даних usage | | 38 | fetchUsageSnapshot | Власна кінцева точка usage | | 39 | createEmbeddingProvider | Адаптер embedding, що належить провайдеру, для memory/search | | 40 | buildReplayPolicy | Власна політика replay/ущільнення транскрипту | | 41 | sanitizeReplayHistory | Переписування replay, специфічні для провайдера, після загального очищення | | 42 | validateReplayTurns | Сувора перевірка replay-turn перед вбудованим runner | | 43 | onModelSelected | Зворотний виклик після вибору моделі (наприклад, телеметрія) | Примітка щодо налаштування prompt:
    • resolveSystemPromptContribution дає змогу провайдеру додавати cache-aware інструкції для system prompt для сімейства моделей. Надавайте їй перевагу над before_prompt_build, коли така поведінка належить одному провайдеру/сімейству моделей і має зберігати стабільний/динамічний поділ кешу.
    Докладні описи та реальні приклади див. у Internals: Provider Runtime Hooks.
6

Додайте додаткові можливості (необов’язково)

Плагін провайдера може реєструвати speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch і web search разом із текстовим inference:
register(api) {
  api.registerProvider({ id: "acme-ai", /* ... */ });

  api.registerSpeechProvider({
    id: "acme-ai",
    label: "Acme Speech",
    isConfigured: ({ config }) => Boolean(config.messages?.tts),
    synthesize: async (req) => ({
      audioBuffer: Buffer.from(/* PCM data */),
      outputFormat: "mp3",
      fileExtension: ".mp3",
      voiceCompatible: false,
    }),
  });

  api.registerRealtimeTranscriptionProvider({
    id: "acme-ai",
    label: "Acme Realtime Transcription",
    isConfigured: () => true,
    createSession: (req) => ({
      connect: async () => {},
      sendAudio: () => {},
      close: () => {},
      isConnected: () => true,
    }),
  });

  api.registerRealtimeVoiceProvider({
    id: "acme-ai",
    label: "Acme Realtime Voice",
    isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),
    createBridge: (req) => ({
      connect: async () => {},
      sendAudio: () => {},
      setMediaTimestamp: () => {},
      submitToolResult: () => {},
      acknowledgeMark: () => {},
      close: () => {},
      isConnected: () => true,
    }),
  });

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

  api.registerImageGenerationProvider({
    id: "acme-ai",
    label: "Acme Images",
    generate: async (req) => ({ /* image result */ }),
  });

  api.registerVideoGenerationProvider({
    id: "acme-ai",
    label: "Acme Video",
    capabilities: {
      maxVideos: 1,
      maxDurationSeconds: 10,
      supportsResolution: true,
    },
    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: [] }),
  });
}
OpenClaw класифікує це як плагін hybrid-capability. Це рекомендований шаблон для корпоративних плагінів (один плагін на вендора). Див. Internals: Capability Ownership.
7

Протестуйте

src/provider.test.ts
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();
  });
});

Публікація в ClawHub

Плагіни провайдерів публікуються так само, як і будь-який інший зовнішній кодовий плагін:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
Не використовуйте тут застарілий псевдонім публікації лише для Skills; пакунки плагінів мають використовувати clawhub package publish.

Структура файлів

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

Довідник щодо порядку каталогу

catalog.order визначає, коли ваш каталог об’єднується відносно вбудованих провайдерів:
ПорядокКолиВипадок використання
simpleПерший прохідЗвичайні провайдери з API-ключем
profileПісля simpleПровайдери, обмежені профілями auth
pairedПісля profileСинтез кількох пов’язаних записів
lateОстанній прохідПеревизначення наявних провайдерів (перемагає при колізії)

Наступні кроки