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

Створення плагінів постачальників

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

Покроковий приклад

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 вашого плагіна. Додавайте providerAuthAliases, коли варіант постачальника має повторно використовувати автентифікацію іншого id постачальника. modelSupport є необов’язковим і дозволяє OpenClaw автоматично завантажувати ваш плагін постачальника зі скорочених id моделей, таких як 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 як свою модель.Якщо постачальник вище за потоком використовує інші керівні токени, ніж 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 переписує фінальний системний промпт і текстовий вміст повідомлення перед транспортуванням. 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" }],
      }),
    },
  },
});
Якщо вашому потоку автентифікації також потрібно змінювати models.providers.*, псевдоніми та стандартну модель агента під час онбордингу, використовуйте preset-хелпери з openclaw/plugin-sdk/provider-onboard. Найвужчі хелпери: createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) і createModelCatalogPresetAppliers(...).Коли нативний endpoint постачальника підтримує потокові блоки використання в межах звичайного транспорту openai-completions, віддавайте перевагу спільним хелперам каталогу з openclaw/plugin-sdk/provider-catalog-shared замість жорстко закодованих перевірок id постачальника. supportsNativeStreamingUsageCompat(...) і applyProviderNativeStreamingUsageCompat(...) визначають підтримку за мапою можливостей endpoint, тому нативні endpoint-и у стилі Moonshot/DashScope також зможуть увімкнути цю функцію, навіть якщо плагін використовує власний id постачальника.
4

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

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

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

Плагін постачальника може реєструвати speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch і web search поряд із текстовим інференсом:
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: {
      generate: {
        maxVideos: 1,
        maxDurationSeconds: 10,
        supportsResolution: true,
      },
      imageToVideo: {
        enabled: true,
        maxVideos: 1,
        maxInputImages: 1,
        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: [] }),
  });
}
OpenClaw класифікує це як плагін hybrid-capability. Це рекомендований шаблон для корпоративних плагінів (один плагін на одного постачальника). Див. Внутрішні механізми: Володіння можливостями.Для генерації відео віддавайте перевагу показаній вище структурі можливостей з урахуванням режимів: generate, imageToVideo і videoToVideo. Плоских агрегованих полів на кшталт maxInputImages, maxInputVideos і maxDurationSeconds недостатньо, щоб коректно оголосити підтримку режиму трансформації або вимкнених режимів.Постачальники генерації музики мають дотримуватися того самого шаблону: generate для генерації лише за промптом і edit для генерації на основі референсного зображення. Плоских агрегованих полів на кшталт maxInputImages, supportsLyrics і supportsFormat недостатньо для оголошення підтримки редагування; очікуваним контрактом є явні блоки generate / edit.
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              # metadata openclaw.providers
├── openclaw.plugin.json      # Маніфест із metadata автентифікації постачальника
├── index.ts                  # definePluginEntry + registerProvider
└── src/
    ├── provider.test.ts      # Тести
    └── usage.ts              # Endpoint usage (необов’язково)

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

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

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