Перейти к основному содержанию
Это руководство пошагово показывает, как создать Plugin провайдера, который добавляет провайдера моделей (LLM) в OpenClaw. К концу у вас будет провайдер с каталогом моделей, аутентификацией по ключу API и динамическим разрешением моделей.
Если вы раньше не создавали Plugin для OpenClaw, сначала прочитайте Начало работы, чтобы разобраться с базовой структурой пакета и настройкой манифеста.
Plugins провайдеров добавляют модели в обычный цикл вывода OpenClaw. Если модель должна запускаться через собственный демон агента, который управляет потоками, compaction или событиями инструментов, используйте провайдера вместе с агентным harness, вместо того чтобы помещать детали протокола демона в ядро.

Пошаговое руководство

1

Пакет и манифест

Шаг 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"
    }
  }
}
Манифест объявляет setup.providers[].envVars, чтобы OpenClaw мог обнаруживать учетные данные без загрузки runtime вашего Plugin. Добавьте providerAuthAliases, когда вариант провайдера должен повторно использовать аутентификацию id другого провайдера. modelSupport необязателен и позволяет OpenClaw автоматически загрузить Plugin вашего провайдера по сокращенным id моделей, таким как acme-large, еще до появления runtime-хуков. Если вы публикуете провайдера в ClawHub, эти поля openclaw.compat и openclaw.build обязательны в package.json.
2

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

Минимальному текстовому провайдеру нужны id, label, auth и catalog. catalog — это runtime/config-хук, принадлежащий провайдеру; он может вызывать живые API поставщика и возвращает записи models.providers.
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";

export default definePluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI model provider",
  register(api) {
    api.registerProvider({
      id: "acme-ai",
      label: "Acme AI",
      docsPath: "/providers/acme-ai",
      envVars: ["ACME_AI_API_KEY"],

      auth: [
        createProviderApiKeyAuthMethod({
          providerId: "acme-ai",
          methodId: "api-key",
          label: "Acme AI API key",
          hint: "API key from your Acme AI dashboard",
          optionKey: "acmeAiApiKey",
          flagName: "--acme-ai-api-key",
          envVar: "ACME_AI_API_KEY",
          promptMessage: "Enter your Acme AI API key",
          defaultModel: "acme-ai/acme-large",
        }),
      ],

      catalog: {
        order: "simple",
        run: async (ctx) => {
          const apiKey =
            ctx.resolveProviderApiKey("acme-ai").apiKey;
          if (!apiKey) return null;
          return {
            provider: {
              baseUrl: "https://api.acme-ai.com/v1",
              apiKey,
              api: "openai-completions",
              models: [
                {
                  id: "acme-large",
                  name: "Acme Large",
                  reasoning: true,
                  input: ["text", "image"],
                  cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
                  contextWindow: 200000,
                  maxTokens: 32768,
                },
                {
                  id: "acme-small",
                  name: "Acme Small",
                  reasoning: false,
                  input: ["text"],
                  cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
                  contextWindow: 128000,
                  maxTokens: 8192,
                },
              ],
            },
          };
        },
      },
    });

    api.registerModelCatalogProvider({
      provider: "acme-ai",
      kinds: ["text"],
      liveCatalog: async (ctx) => {
        const apiKey = ctx.resolveProviderApiKey("acme-ai").apiKey;
        if (!apiKey) return null;
        return [
          {
            kind: "text",
            provider: "acme-ai",
            model: "acme-large",
            label: "Acme Large",
            source: "live",
          },
        ];
      },
    });
  },
});
registerModelCatalogProvider — это более новая поверхность каталога control-plane для UI списков, справки и выбора. Используйте ее для строк text, image-generation, video-generation и music-generation. Оставляйте вызовы endpoint поставщика и сопоставление ответов в Plugin; OpenClaw владеет общей формой строк, метками источников и отрисовкой справки.Это рабочий провайдер. Теперь пользователи могут выполнить openclaw onboard --acme-ai-api-key <key> и выбрать acme-ai/acme-large в качестве модели.

Живое обнаружение моделей

Если ваш провайдер предоставляет API в стиле /models, оставьте специфичный для провайдера endpoint и проекцию строк в вашем Plugin и используйте openclaw/plugin-sdk/provider-catalog-live-runtime для общего lifecycle выборки. Этот helper дает защищенные HTTP-запросы, заголовки аутентификации провайдера, структурированные HTTP-ошибки, TTL-кэширование и поведение статического fallback без помещения политики провайдера в ядро OpenClaw.Используйте buildLiveModelProviderConfig, когда live API сообщает только, какие принадлежащие провайдеру строки статического каталога сейчас доступны:
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import {
  buildLiveModelProviderConfig,
  type LiveModelCatalogFetchGuard,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

const STATIC_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,
  },
] as const;

async function buildAcmeLiveProvider(params: {
  apiKey: string;
  discoveryApiKey?: string;
  fetchGuard?: LiveModelCatalogFetchGuard;
}) {
  return await buildLiveModelProviderConfig({
    providerId: "acme-ai",
    endpoint: "https://api.acme-ai.com/v1/models",
    providerConfig: {
      baseUrl: "https://api.acme-ai.com/v1",
      api: "openai-completions",
    },
    models: STATIC_MODELS,
    apiKey: params.apiKey,
    discoveryApiKey: params.discoveryApiKey,
    fetchGuard: params.fetchGuard,
    ttlMs: 60_000,
    auditContext: "acme-ai-model-discovery",
  });
}

export default definePluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  register(api) {
    api.registerProvider({
      id: "acme-ai",
      label: "Acme AI",
      catalog: {
        order: "simple",
        run: async (ctx) => {
          const auth = ctx.resolveProviderAuth("acme-ai");
          const apiKey =
            auth.apiKey ?? ctx.resolveProviderApiKey("acme-ai").apiKey;
          if (!apiKey) return null;
          return {
            provider: await buildAcmeLiveProvider({
              apiKey,
              discoveryApiKey: auth.discoveryApiKey,
            }),
          };
        },
      },
      staticCatalog: {
        order: "simple",
        run: async () => ({
          provider: {
            baseUrl: "https://api.acme-ai.com/v1",
            api: "openai-completions",
            models: [...STATIC_MODELS],
          },
        }),
      },
    });
  },
});
Используйте getCachedLiveProviderModelRows, когда API провайдера возвращает более богатые метаданные, а Plugin должен сам проецировать строки в определения моделей OpenClaw:
index.ts
import {
  getCachedLiveProviderModelRows,
  LiveModelCatalogHttpError,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";

async function discoverAcmeModels(apiKey: string) {
  try {
    const rows = await getCachedLiveProviderModelRows({
      providerId: "acme-ai",
      endpoint: "https://api.acme-ai.com/v1/models",
      apiKey,
      ttlMs: 60_000,
      auditContext: "acme-ai-model-discovery",
    });
    return rows
      .map((row) => projectAcmeModel(row))
      .filter((model) => model !== null);
  } catch (error) {
    if (error instanceof LiveModelCatalogHttpError) {
      return STATIC_MODELS;
    }
    throw error;
  }
}
run должен оставаться закрытым аутентификацией и возвращать null, когда нет пригодных учетных данных. Сохраняйте offline staticRun или статический fallback, чтобы setup, docs, tests и поверхности выбора не зависели от живого доступа к сети. Используйте TTL, подходящий для свежести списка моделей, избегайте опроса файловой системы во время запроса и передавайте специфичные для провайдера readRows / readModelId только тогда, когда ответ upstream не имеет OpenAI-совместимую форму { data: [{ id, object }] }.Если 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 и содержимое текстовых сообщений перед transport. output переписывает текстовые дельты assistant и финальный текст до того, как OpenClaw разбирает собственные управляющие маркеры или доставку в канал.Для встроенных провайдеров, которые регистрируют только одного текстового провайдера с аутентификацией по ключу API и единственным runtime на основе каталога, предпочитайте более узкий helper 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 — это путь живого каталога, используемый, когда OpenClaw может разрешить реальную аутентификацию поставщика. Он может выполнять обнаружение, специфичное для поставщика. Используйте buildStaticProvider только для офлайн-строк, которые безопасно показывать до настройки аутентификации; он не должен требовать учетных данных или выполнять сетевые запросы. Отображение models list --all в OpenClaw сейчас выполняет статические каталоги только для встроенных Plugin поставщиков, с пустой конфигурацией, пустым окружением и без путей агента/рабочей области.Если вашему потоку аутентификации также нужно изменять models.providers.*, псевдонимы и модель агента по умолчанию во время onboarding, используйте готовые вспомогательные функции из openclaw/plugin-sdk/provider-onboard. Самые узкие вспомогательные функции: createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) и createModelCatalogPresetAppliers(...).Когда нативная конечная точка поставщика поддерживает потоковые блоки usage поверх обычного транспорта openai-completions, предпочитайте общие вспомогательные функции каталога в openclaw/plugin-sdk/provider-catalog-shared вместо жестко заданных проверок id поставщика. supportsNativeStreamingUsageCompat(...) и applyProviderNativeStreamingUsageCompat(...) определяют поддержку по карте возможностей конечной точки, поэтому нативные конечные точки в стиле Moonshot/DashScope все равно включаются, даже если Plugin использует пользовательский id поставщика.Примеры живого обнаружения выше покрывают API поставщиков в стиле /models. Держите это обнаружение внутри catalog.run, с ограничением по пригодной аутентификации, и держите staticRun без сетевых запросов для генерации офлайн-каталога.
3

Add dynamic model resolution

Если ваш поставщик принимает произвольные 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 запустится снова после его завершения.
4

Add runtime hooks (as needed)

Большинству поставщиков нужны только catalog + resolveDynamicModel. Добавляйте hooks постепенно, по мере необходимости для вашего поставщика.Общие сборщики вспомогательных функций теперь покрывают самые распространенные семейства replay/tool-compat, поэтому Plugin обычно не нужно вручную подключать каждый hook по одному:
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Общая политика replay в стиле OpenAI для OpenAI-совместимых транспортов, включая очистку tool-call-id, исправления порядка с первым сообщением assistant и общую валидацию ходов Gemini там, где это нужно транспортуmoonshot, ollama, xai, zai
anthropic-by-modelПолитика replay с учетом Claude, выбираемая по modelId, чтобы транспорты Anthropic-message получали очистку thinking-block, специфичную для Claude, только когда разрешенная модель действительно имеет id Claudeamazon-bedrock, anthropic-vertex
google-geminiНативная политика replay Gemini плюс очистка bootstrap replay. Общее семейство сохраняет текстовый вывод Gemini CLI на tagged reasoning; прямой поставщик google переопределяет resolveReasoningOutputMode на native, потому что thinking в Gemini API приходит как нативные thought parts.google, google-gemini-cli
passthrough-geminiОчистка thought-signature Gemini для моделей Gemini, работающих через OpenAI-совместимые прокси-транспорты; не включает нативную валидацию replay Gemini или bootstrap-перезаписиopenrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openaiГибридная политика для поставщиков, которые смешивают поверхности моделей Anthropic-message и OpenAI-совместимые поверхности моделей в одном Plugin; опциональное удаление thinking-block только для Claude остается ограниченным стороной Anthropicminimax
Доступные сегодня семейства stream:
СемействоЧто подключаетсяВстроенные примеры
google-thinkingНормализация thinking payload Gemini на общем stream-путиgoogle, google-gemini-cli
kilocode-thinkingОбертка Kilo reasoning на общем stream-пути прокси, при этом kilo/auto и неподдерживаемые id reasoning прокси пропускают внедренный thinkingkilocode
moonshot-thinkingМаппинг бинарного payload нативного thinking Moonshot из конфигурации + уровня /thinkmoonshot
minimax-fast-modeПерезапись модели MiniMax fast-mode на общем stream-путиminimax, minimax-portal
openai-responses-defaultsОбщие обертки нативных OpenAI/Codex Responses: заголовки атрибуции, /fast/serviceTier, подробность текста, нативный веб-поиск Codex, формирование reasoning-compat payload и управление контекстом Responsesopenai
openrouter-thinkingОбертка reasoning OpenRouter для прокси-маршрутов, с централизованной обработкой пропусков unsupported-model/autoopenrouter
tool-stream-default-onВключенная по умолчанию обертка tool_stream для поставщиков вроде Z.AI, которым нужен tool streaming, если он явно не отключенzai
Каждый сборщик семейства составлен из низкоуровневых публичных вспомогательных функций, экспортируемых из того же пакета, к которым можно обратиться, когда поставщику нужно отойти от общего шаблона:
  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...) и необработанные сборщики replay (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). Также экспортирует вспомогательные функции replay Gemini (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) и вспомогательные функции конечных точек/моделей (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId).
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), а также общие обертки OpenAI/Codex (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), OpenAI-совместимая обертка DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), очистка thinking prefill для Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper), совместимость plain-text tool-call (createPlainTextToolCallCompatWrapper) и общие обертки прокси/поставщиков (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
  • openclaw/plugin-sdk/provider-stream-shared - легковесные обертки payload и событий для горячих путей поставщиков, включая createOpenAICompatibleCompletionsThinkingOffWrapper, createPayloadPatchStreamWrapper, createPlainTextToolCallCompatWrapper, normalizeOpenAICompatibleReasoningPayload(...) и setQwenChatTemplateThinking(...).
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai") и базовые вспомогательные функции схем поставщиков.
Для поставщиков семейства Gemini держите режим reasoning-output согласованным с транспортом. Прямые поставщики Google Gemini API должны использовать native reasoning output, чтобы OpenClaw потреблял нативные thought parts без добавления prompt-директив <think> / <final>. Текстовые backends в стиле Gemini CLI, которые разбирают финальный JSON/текстовый ответ, могут сохранять общий tagged-контракт google-gemini.Некоторые stream-вспомогательные функции намеренно остаются локальными для поставщика. @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,
  };
},
OpenClaw вызывает хуки в таком порядке. Большинство провайдеров используют только 2-3: Поля провайдера только для совместимости, которые OpenClaw больше не вызывает, например ProviderPlugin.capabilities и suppressBuiltInModel, здесь не перечислены.
#ХукКогда использовать
1catalogКаталог моделей или значения по умолчанию для базового URL
2applyConfigDefaultsГлобальные значения по умолчанию, принадлежащие провайдеру, при материализации конфигурации
3normalizeModelIdОчистка устаревших/предварительных псевдонимов ID модели перед поиском
4normalizeTransportОчистка api / baseUrl для семейства провайдера перед общей сборкой модели
5normalizeConfigНормализация конфигурации models.providers.<id>
6applyNativeStreamingUsageCompatПерезаписи совместимости нативного потокового учета использования для конфигурационных провайдеров
7resolveConfigApiKeyРазрешение авторизации по env-маркерам, принадлежащее провайдеру
8resolveSyntheticAuthЛокальная/самостоятельно размещенная или основанная на конфигурации синтетическая авторизация
9shouldDeferSyntheticProfileAuthПонижение приоритета синтетических заполнителей сохраненного профиля за env/config-авторизацией
10resolveDynamicModelПрием произвольных ID моделей upstream
11prepareDynamicModelАсинхронная загрузка метаданных перед разрешением
12normalizeResolvedModelПерезаписи транспорта перед runner
13normalizeToolSchemasОчистка схем инструментов, принадлежащая провайдеру, перед регистрацией
14inspectToolSchemasДиагностика схем инструментов, принадлежащая провайдеру
15resolveReasoningOutputModeКонтракт tagged vs native reasoning-output
16prepareExtraParamsПараметры запроса по умолчанию
17createStreamFnПолностью пользовательский транспорт StreamFn
19wrapStreamFnПользовательские обертки headers/body на обычном пути потока
20resolveTransportTurnStateНативные headers/metadata для каждого turn
21resolveWebSocketSessionPolicyНативные headers/cool-down для WS-сеанса
22formatApiKeyПользовательская форма runtime-токена
23refreshOAuthПользовательское обновление OAuth
24buildAuthDoctorHintПодсказка по исправлению авторизации
25matchesContextOverflowErrorОбнаружение переполнения, принадлежащее провайдеру
26classifyFailoverReasonКлассификация rate-limit/overload, принадлежащая провайдеру
27isCacheTtlEligibleОграничение TTL кеша промптов
28buildMissingAuthMessageПользовательская подсказка об отсутствующей авторизации
29augmentModelCatalogСинтетические строки forward-compat
30resolveThinkingProfileНабор параметров /think, специфичный для модели
31isBinaryThinkingСовместимость бинарного включения/выключения thinking
32supportsXHighThinkingСовместимость поддержки reasoning xhigh
33resolveDefaultThinkingLevelСовместимость политики /think по умолчанию
34isModernModelRefСопоставление моделей live/smoke
35prepareRuntimeAuthОбмен токена перед inference
36resolveUsageAuthПользовательский разбор учетных данных использования
37fetchUsageSnapshotПользовательский endpoint использования
38createEmbeddingProviderАдаптер embedding, принадлежащий провайдеру, для memory/search
39buildReplayPolicyПользовательская политика replay/compaction транскрипта
40sanitizeReplayHistoryСпецифичные для провайдера перезаписи replay после общей очистки
41validateReplayTurnsСтрогая валидация replay-turn перед встроенным runner
42onModelSelectedCallback после выбора (например, telemetry)
Примечания о резервном поведении runtime:
  • normalizeConfig сначала проверяет совпавшего провайдера, затем другие provider plugins с поддержкой хуков, пока один из них действительно не изменит конфигурацию. Если ни один хук провайдера не переписывает поддерживаемую запись конфигурации семейства Google, встроенный нормализатор конфигурации Google все равно применяется.
  • resolveConfigApiKey использует хук провайдера, когда он предоставлен. Amazon Bedrock держит разрешение AWS env-маркеров в своем provider plugin; сама runtime-авторизация все равно использует цепочку AWS SDK по умолчанию, когда настроена с auth: "aws-sdk".
  • resolveThinkingProfile(ctx) получает выбранные provider, modelId, необязательную объединенную подсказку каталога reasoning и необязательные объединенные факты compat модели. Используйте compat только для выбора thinking UI/profile провайдера.
  • resolveSystemPromptContribution позволяет провайдеру внедрять cache-aware guidance для system-prompt для семейства моделей. Предпочитайте его вместо before_prompt_build, когда поведение принадлежит одному провайдеру/семейству моделей и должно сохранять разделение stable/dynamic cache.
Подробные описания и реальные примеры см. в Внутреннее устройство: хуки runtime провайдера.
5

Add extra capabilities (optional)

Шаг 5: Добавьте дополнительные возможности

Provider plugin может регистрировать embeddings, speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch и web search вместе с text inference. OpenClaw классифицирует это как плагин с гибридными возможностями - рекомендуемый шаблон для плагинов компаний (один плагин на поставщика). См. Внутреннее устройство: владение возможностями.Регистрируйте каждую возможность внутри register(api) рядом с существующим вызовом api.registerProvider(...). Выберите только нужные вкладки:
import {
  assertOkOrThrowProviderError,
  postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";

api.registerSpeechProvider({
  id: "acme-ai",
  label: "Acme Speech",
  defaultTimeoutMs: 120_000,
  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-ошибок и suffixes request-id.
6

Тестирование

Шаг 6: Тестирование

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

Plugin провайдеров публикуются так же, как и любой другой внешний кодовый Plugin:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
Не используйте здесь устаревший псевдоним публикации только для Skills; пакеты Plugin должны использовать 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Последний проходПереопределение существующих провайдеров (побеждает при конфликте)

Следующие шаги

Связанные материалы