跳轉到主要內容
建置提供者外掛,將模型提供者(LLM)加入 OpenClaw:模型 目錄、API 金鑰驗證,以及動態模型解析。
第一次使用 OpenClaw 外掛?請先閱讀入門, 了解套件結構與 manifest 設定。
提供者外掛會將模型加入 OpenClaw 的一般推論迴圈。如果 模型必須透過擁有執行緒、壓縮或工具事件的原生代理程式常駐程式執行, 請將提供者搭配代理程式 harness,而不是把常駐程式協定 細節放進核心。

逐步說明

1

Package and manifest

步驟 1:套件與 manifest

{
  "name": "@myorg/openclaw-acme-ai",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "providers": ["acme-ai"],
    "compat": {
      "pluginApi": ">=2026.3.24-beta.2",
      "minGatewayVersion": "2026.3.24-beta.2"
    },
    "build": {
      "openclawVersion": "2026.3.24-beta.2",
      "pluginSdkVersion": "2026.3.24-beta.2"
    }
  }
}
{
  "id": "acme-ai",
  "name": "Acme AI",
  "description": "Acme AI model provider",
  "providers": ["acme-ai"],
  "modelSupport": {
    "modelPrefixes": ["acme-"]
  },
  "setup": {
    "providers": [
      {
        "id": "acme-ai",
        "envVars": ["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
  }
}
setup.providers[].envVars 讓 OpenClaw 不必載入你的外掛執行階段, 就能偵測憑證。當某個提供者變體應重用另一個提供者 ID 的驗證時, 請加入 providerAuthAliasesmodelSupport 是選用項目,可讓 OpenClaw 在執行階段 hook 存在之前,從像 acme-large 這樣的簡寫模型 ID 自動載入你的提供者外掛。package.json 中的 openclaw.compatopenclaw.build 是發布到 ClawHub 的必要項目 (openclaw.compat.pluginApiopenclaw.build.openclawVersion 是兩個必要欄位;省略 minGatewayVersion 時,會回退到 openclaw.install.minHostVersion)。
2

Register the provider

最小文字提供者需要 idlabelauthcatalogcatalog 是提供者擁有的執行階段/設定 hook;它可以呼叫即時 廠商 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 是較新的控制平面目錄介面, 用於清單/說明/選擇器 UI,涵蓋 textvoiceimage_generationvideo_generationmusic_generation 列。請將廠商端點呼叫 與回應對應保留在外掛中;OpenClaw 擁有共用列形狀、 來源標籤和說明呈現。這就是可運作的提供者。使用者現在可以執行 openclaw onboard --acme-ai-api-key <key>,並選取 acme-ai/acme-large 作為模型。

即時模型探索

如果你的提供者公開 /models 風格的 API,請將提供者專屬 端點與列投影保留在外掛中,並使用 openclaw/plugin-sdk/provider-catalog-live-runtime 來處理共用擷取 生命週期。這個輔助工具提供受保護的 HTTP 擷取、提供者驗證標頭、 結構化 HTTP 錯誤、TTL 快取和靜態回退行為,而不必把 提供者政策放進 OpenClaw 核心。當即時 API 只告訴你目前可用的提供者自有靜態目錄列時, 使用 buildLiveModelProviderConfig
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],
          },
        }),
      },
    });
  },
});
當提供者 API 回傳更豐富的中繼資料,且外掛需要自行將列投影成 OpenClaw 模型定義時,使用 getCachedLiveProviderModelRows
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。 保留離線 staticRun 或靜態回退,讓設定、文件、測試和選擇器介面 不依賴即時網路存取。使用適合模型清單新鮮度的 TTL, 避免請求期間的檔案系統輪詢,且只有在上游回應不是 OpenAI 相容的 { data: [{ id, object }] } 形狀時,才傳入提供者專屬的 readRows / readModelId如果上游提供者使用的控制 token 與 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 解析自己的控制標記或傳遞到頻道之前, 重寫助理文字 delta 和最終文字。對於只註冊一個文字提供者、使用 API 金鑰驗證且搭配單一 目錄支援執行階段的內建提供者,請優先使用範圍較窄的 defineSingleProviderPluginEntry(...) 輔助工具:
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";

export default defineSingleProviderPluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI 模型提供者",
  provider: {
    label: "Acme AI",
    docsPath: "/providers/acme-ai",
    auth: [
      {
        methodId: "api-key",
        label: "Acme AI API 金鑰",
        hint: "來自你的 Acme AI 儀表板的 API 金鑰",
        optionKey: "acmeAiApiKey",
        flagName: "--acme-ai-api-key",
        envVar: "ACME_AI_API_KEY",
        promptMessage: "輸入你的 Acme AI API 金鑰",
        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 用於在設定驗證前可安全顯示的離線列;它不得需要憑證或發出網路請求。OpenClaw 的 models list --all 顯示目前只會針對內建提供者外掛執行靜態目錄,且使用空白設定、空白環境,以及沒有代理程式/工作區路徑。如果你的驗證流程在導覽設定期間也需要修補 models.providers.*、別名,以及代理程式預設模型,請使用 openclaw/plugin-sdk/provider-onboard 的預設輔助工具。範圍最窄的輔助工具是 createDefaultModelPresetAppliers(...)createDefaultModelsPresetAppliers(...)createModelCatalogPresetAppliers(...)當提供者的原生端點在一般 openai-completions 傳輸上支援串流用量區塊時,請優先使用 openclaw/plugin-sdk/provider-catalog-shared 中的共用目錄輔助工具,而不是硬編碼提供者 ID 檢查。supportsNativeStreamingUsageCompat(...)applyProviderNativeStreamingUsageCompat(...) 會從端點能力對應偵測支援,因此即使外掛使用自訂提供者 ID,原生 Moonshot/DashScope 風格端點仍會選擇加入。上方的即時探索範例涵蓋 /models 風格的提供者 API。請將該探索保留在 catalog.run 內,以可用的驗證資訊作為閘門,並保持 staticRun 不使用網路,以產生離線目錄。
3

新增動態模型解析

如果你的提供者接受任意模型 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

新增執行階段鉤子(視需要)

多數提供者只需要 catalog + resolveDynamicModel。請依你的提供者需求逐步新增鉤子。共用輔助建構器現在涵蓋最常見的重播/工具相容系列,因此外掛通常不需要逐一手動連接每個鉤子:
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 清理、助理優先排序修正,以及傳輸需要時的通用 Gemini 回合驗證moonshot, ollama, xai, zai
anthropic-by-modelmodelId 選擇的 Claude 感知重播政策,因此 Anthropic 訊息傳輸只會在解析後的模型實際上是 Claude ID 時取得 Claude 特定的思考區塊清理amazon-bedrock
native-anthropic-by-modelanthropic-by-model 相同的依 Claude 模型政策,加上工具呼叫 ID 清理,以及針對必須保留廠商原生 ID 的傳輸保留原生 Anthropic 工具使用 IDanthropic-vertex, clawrouter
google-gemini原生 Gemini 重播政策加上啟動重播清理。共用系列讓文字輸出 Gemini 命令列介面維持標記式推理;直接的 google 提供者會將 resolveReasoningOutputMode 覆寫為 native,因為 Gemini API 思考會以原生 thought parts 送達。google, google-gemini-cli
passthrough-gemini針對透過 OpenAI 相容代理傳輸執行的 Gemini 模型進行 Gemini thought-signature 清理;不啟用原生 Gemini 重播驗證或啟動重寫openrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openai供在一個外掛中混合 Anthropic 訊息與 OpenAI 相容模型表面的提供者使用的混合政策;選用的僅限 Claude 思考區塊丟棄會保持限定於 Anthropic 端minimax
目前可用的串流系列:
系列連接內容內建範例
google-thinking共用串流路徑上的 Gemini 思考酬載正規化google, google-gemini-cli
kilocode-thinking共用代理串流路徑上的 Kilo 推理包裝器,且 kilo/auto 與不支援的代理推理 ID 會略過注入式思考kilocode
moonshot-thinking從設定 + /think 層級對應 Moonshot 二進位原生思考酬載moonshot
minimax-fast-mode共用串流路徑上的 MiniMax 快速模式模型重寫minimax, minimax-portal
openai-responses-defaults共用原生 OpenAI/Codex Responses 包裝器:歸因標頭、/fast/serviceTier、文字詳細程度、原生 Codex 網頁搜尋、推理相容酬載塑形,以及 Responses 脈絡管理openai
openrouter-thinking代理路由的 OpenRouter 推理包裝器,不支援模型/auto 略過會集中處理openrouter
tool-stream-default-on針對像 Z.AI 這類除非明確停用否則想要工具串流的提供者,預設啟用的 tool_stream 包裝器zai
每個系列建構器都由同一套件匯出的較低階公開輔助工具組成,當提供者需要偏離常見模式時可以使用:
  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamilybuildProviderReplayFamilyHooks(...),以及原始重播建構器(buildOpenAICompatibleReplayPolicybuildAnthropicReplayPolicyForModelbuildGoogleGeminiReplayPolicybuildHybridAnthropicOrOpenAIReplayPolicy)。也匯出 Gemini 重播輔助工具(sanitizeGoogleGeminiReplayHistoryresolveTaggedReasoningOutputMode)與端點/模型輔助工具(resolveProviderEndpointnormalizeProviderIdnormalizeGooglePreviewModelId)。
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamilybuildProviderStreamFamilyHooks(...)composeProviderStreamWrappers(...),以及共用 OpenAI/Codex 包裝器(createOpenAIAttributionHeadersWrappercreateOpenAIFastModeWrappercreateOpenAIServiceTierWrappercreateOpenAIResponsesContextManagementWrappercreateCodexNativeWebSearchWrapper)、DeepSeek V4 OpenAI 相容包裝器(createDeepSeekV4OpenAICompatibleThinkingWrapper)、Anthropic Messages 思考預填清理(createAnthropicThinkingPrefillPayloadWrapper)、純文字工具呼叫相容(createPlainTextToolCallCompatWrapper),以及共用代理/提供者包裝器(createOpenRouterWrappercreateToolStreamWrappercreateMinimaxFastModeWrapper)。
  • openclaw/plugin-sdk/provider-stream-shared - 用於熱門提供者路徑的輕量酬載與事件包裝器,包括 createOpenAICompatibleCompletionsThinkingOffWrappercreatePayloadPatchStreamWrappercreatePlainTextToolCallCompatWrappernormalizeOpenAICompatibleReasoningPayload(...)setQwenChatTemplateThinking(...)
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamilybuildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai"),以及底層提供者結構描述輔助工具。
對於 Gemini 系列提供者,請讓推理輸出模式與傳輸保持一致。直接的 Google Gemini API 提供者應使用 native 推理輸出,讓 OpenClaw 消耗原生 thought parts,而不新增 <think> / <final> 提示指令。僅文字的 Gemini 命令列介面風格後端若會解析最終 JSON/文字回應,可以保留共用的 google-gemini 標記式合約。有些串流輔助工具刻意保持提供者本地。@openclaw/anthropic-provider 會將 wrapAnthropicProviderStreamresolveAnthropicBetasresolveAnthropicFastModeresolveAnthropicServiceTier,以及較低階 Anthropic 包裝器建構器保留在自己的公開 api.ts / contract-api.ts 邊界中,因為它們編碼 Claude OAuth beta 處理與 context1m 閘門。xAI 外掛同樣在自己的 wrapStreamFn 中保留原生 xAI Responses 塑形(/fast 別名、預設 tool_stream、不支援 strict-tool 清理、xAI 特定推理酬載移除)。相同的套件根模式也支援 @openclaw/openai-provider(提供者建構器、預設模型輔助工具、即時提供者建構器)與 @openclaw/openrouter-provider(提供者建構器加上導覽設定/設定輔助工具)。
對於需要在每次推論呼叫前交換權杖的提供者:
prepareRuntimeAuth: async (ctx) => {
  const exchanged = await exchangeToken(ctx.apiKey);
  return {
    apiKey: exchanged.token,
    baseUrl: exchanged.baseUrl,
    expiresAt: exchanged.expiresAt,
  };
},
OpenClaw 對 model/provider 外掛大致依照以下順序呼叫 hook。 多數提供者只使用 2-3 個。這不是完整的 ProviderPlugin contract - 請參閱內部:提供者執行階段 Hooks,以取得完整且目前準確的 hook 清單與後援註記。 OpenClaw 不再呼叫的僅相容性提供者欄位,例如 ProviderPlugin.capabilitiessuppressBuiltInModel,未列於此處。
Hook使用時機
catalog模型目錄或基底 URL 預設值
applyConfigDefaultsconfig materialization 期間由提供者擁有的全域預設值
normalizeModelId查找前清理舊版/預覽 model-id 別名
normalizeTransport通用模型組裝前清理提供者家族 api / baseUrl
normalizeConfig正規化 models.providers.<id> config
applyNativeStreamingUsageCompatconfig providers 的原生串流用量相容性重寫
resolveConfigApiKey提供者擁有的 env-marker auth 解析
resolveSyntheticAuth本機/自託管或 config-backed 合成 auth
resolveExternalAuthProfiles為命令列介面/應用程式管理憑證疊加提供者擁有的外部 auth profiles
shouldDeferSyntheticProfileAuth將合成 stored-profile placeholders 降到 env/config auth 後面
resolveDynamicModel接受任意上游模型 ID
prepareDynamicModel解析前非同步擷取中繼資料
normalizeResolvedModelrunner 前的傳輸重寫
normalizeToolSchemas註冊前由提供者擁有的 tool-schema 清理
inspectToolSchemas由提供者擁有的 tool-schema 診斷
resolveReasoningOutputMode標記式與原生 reasoning-output contract
prepareExtraParams預設請求參數
createStreamFn完全自訂的 StreamFn 傳輸
wrapStreamFn一般串流路徑上的自訂 headers/body wrappers
resolveTransportTurnState原生每回合 headers/metadata
resolveWebSocketSessionPolicy原生 WS session headers/cool-down
formatApiKey自訂執行階段 token 形狀
refreshOAuth自訂 OAuth refresh
buildAuthDoctorHintauth 修復指引
matchesContextOverflowError提供者擁有的 overflow 偵測
classifyFailoverReason提供者擁有的 rate-limit/overload 分類
isCacheTtlEligiblePrompt cache TTL 閘控
buildMissingAuthMessage自訂 missing-auth 提示
augmentModelCatalog合成 forward-compat rows(已棄用 - 請優先使用 registerModelCatalogProvider
resolveThinkingProfile模型特定 /think option set
isBinaryThinking二元思考開/關相容性(已棄用 - 請優先使用 resolveThinkingProfile
supportsXHighThinkingxhigh reasoning 支援相容性(已棄用 - 請優先使用 resolveThinkingProfile
resolveDefaultThinkingLevel預設 /think policy 相容性(已棄用 - 請優先使用 resolveThinkingProfile
isModernModelReflive/smoke model matching
prepareRuntimeAuth推論前的 token exchange
resolveUsageAuth自訂用量憑證解析
fetchUsageSnapshot自訂用量 endpoint
createEmbeddingProvider由提供者擁有、用於 memory/search 的 embedding adapter
buildReplayPolicy自訂 transcript replay/壓縮 policy
sanitizeReplayHistory通用清理後的提供者特定 replay 重寫
validateReplayTurnsembedded runner 前的嚴格 replay-turn 驗證
onModelSelected選取後 callback(例如 telemetry)
執行階段後援註記:
  • normalizeConfig 會為每個提供者 ID 解析一個擁有它的外掛(先是 bundled providers,再是相符的 runtime plugin),並且只呼叫該 hook - 不會掃描其他提供者。Google 自己的 normalizeConfig hook 會正規化 google / google-vertex / google-antigravity config entries;它不是獨立的核心後援。
  • resolveConfigApiKey 會在公開時使用提供者 hook。Amazon Bedrock 將 AWS env-marker resolution 保留在其提供者外掛中;runtime auth 本身在設定為 auth: "aws-sdk" 時仍使用 AWS SDK default chain。
  • resolveThinkingProfile(ctx) 會接收選取的 providermodelId、可選的合併 reasoning catalog hint,以及可選的合併模型 compat facts。僅使用 compat 來選擇提供者的 thinking UI/profile。
  • resolveSystemPromptContribution 讓提供者能為模型家族注入具 cache-awareness 的 system-prompt guidance。當行為屬於單一 provider/model family 且應保留 stable/dynamic cache split 時,請優先使用它,而不是舊版 plugin-wide before_prompt_build hook。
5

新增額外能力(選用)

步驟 5:新增額外能力

提供者外掛可以在文字推論旁註冊 embeddings、speech、realtime transcription、 realtime voice、media understanding、image generation、video generation、 web fetch 與 web search。OpenClaw 將此分類為 hybrid-capability 外掛 - 這是公司外掛的建議模式 (每個供應商一個外掛)。請參閱 內部:能力擁有權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 錯誤解析,以及 request-id suffixes。
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

提供者外掛的發布方式與任何其他外部程式碼外掛相同:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
clawhub skill publish <path> 是用來發布 skill 資料夾的不同命令,不是外掛套件,請勿在這裡使用。

檔案結構

<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 金鑰提供者
profilesimple 之後以驗證設定檔作為閘門的提供者
pairedprofile 之後合成多個相關項目
late最後一輪覆寫既有提供者(衝突時勝出)

後續步驟

相關