Skip to main content

Building Provider Plugins

本指南通過構建 provider 外掛來為 OpenClaw 添加模型 provider(LLM)。完成後,你會得到一個具有模型目錄、API key auth 與動態模型解析的 provider。
如果你從未建置過任何 OpenClaw 外掛,請先閱讀 入門指南,以了解基本的套件結構與 manifest 設定。

逐步指南

1

套件與 manifest

{
  "name": "@myorg/openclaw-acme-ai",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "providers": ["acme-ai"]
  }
}
Manifest 宣告 providerAuthEnvVars,使 OpenClaw 可以在不載入外掛 runtime 的情況下偵測認證。
2

註冊 provider

最小的 provider 需要 idlabelauthcatalog
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 模型 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 來自你的 Acme AI 儀表板",
          optionKey: "acmeAiApiKey",
          flagName: "--acme-ai-api-key",
          envVar: "ACME_AI_API_KEY",
          promptMessage: "輸入你的 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,
                },
              ],
            },
          };
        },
      },
    });
  },
});
這是一個可以運作的 provider。使用者現在可以執行 openclaw onboard --acme-ai-api-key <key> 並選擇 acme-ai/acme-large 作為他們的模型。對於只註冊一個文字 provider 搭配 API-key auth 與單一 catalog-based runtime 的捆綁 provider,優先使用較窄的 defineSingleProviderPluginEntry(...) 輔助函式:
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";

export default defineSingleProviderPluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI 模型 provider",
  provider: {
    label: "Acme AI",
    docsPath: "/providers/acme-ai",
    auth: [
      {
        methodId: "api-key",
        label: "Acme AI API key",
        hint: "API key 來自你的 Acme AI 儀表板",
        optionKey: "acmeAiApiKey",
        flagName: "--acme-ai-api-key",
        envVar: "ACME_AI_API_KEY",
        promptMessage: "輸入你的 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" }],
      }),
    },
  },
});
如果 auth flow 還需要在 onboarding 期間修補 models.providers.*、aliases 與 agent 預設模型,使用來自 openclaw/plugin-sdk/provider-onboard 的預設輔助函式。最窄的輔助函式為 createDefaultModelPresetAppliers(...)createDefaultModelsPresetAppliers(...)createModelCatalogPresetAppliers(...)
3

加入動態模型解析

如果你的 provider 接受任意模型 ID(如代理或路由),加入 resolveDynamicModel
api.registerProvider({
  // ... 上面的 id、label、auth、catalog

  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

加入 runtime hooks(視需要)

大多數 provider 只需要 catalog + resolveDynamicModel。根據 provider 的需求逐步加入 hooks。
針對在每次推理呼叫之前需要 token exchange 的 provider:
prepareRuntimeAuth: async (ctx) => {
  const exchanged = await exchangeToken(ctx.apiKey);
  return {
    apiKey: exchanged.token,
    baseUrl: exchanged.baseUrl,
    expiresAt: exchanged.expiresAt,
  };
},
OpenClaw 按此順序呼叫 hooks。大多數 provider 只使用 2-3 個:
#Hook使用時機
1catalog模型目錄或基礎 URL 預設值
2resolveDynamicModel接受任意上游模型 ID
3prepareDynamicModel解析前非同步中繼資料取得
4normalizeResolvedModel執行器前的傳輸重寫
5capabilitiesTranscript/工具設定中繼資料(資料,非可呼叫)
6prepareExtraParams預設請求參數
7wrapStreamFn自訂標頭/請求內容包裝器
8formatApiKey自訂 runtime token 形狀
9refreshOAuth自訂 OAuth 重新整理
10buildAuthDoctorHintAuth 修復指導
11isCacheTtlEligiblePrompt cache TTL 控制
12buildMissingAuthMessage自訂 missing-auth 提示
13suppressBuiltInModel隱藏過時上游列
14augmentModelCatalog合成前向相容列
15isBinaryThinkingBinary thinking 開/關
16supportsXHighThinkingxhigh reasoning 支持
17resolveDefaultThinkingLevel預設 /think 策略
18isModernModelRefLive/smoke 模型匹配
19prepareRuntimeAuth推理前的 token exchange
20resolveUsageAuth自訂使用情況認證解析
21fetchUsageSnapshot自訂使用情況端點
22onModelSelected後選擇回調(例如遙測)
如需詳細說明與真實範例,參考 內部:Provider Runtime Hooks
5

加入額外能力(可選)

Provider 外掛可以與文字推理一併註冊語音、media understanding、影像生成與網路搜尋:
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 資料 */),
      outputFormat: "mp3",
      fileExtension: ".mp3",
      voiceCompatible: false,
    }),
  });

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

  api.registerImageGenerationProvider({
    id: "acme-ai",
    label: "Acme Images",
    generate: async (req) => ({ /* 圖像結果 */ }),
  });
}
OpenClaw 將此分類為 hybrid-capability 外掛。這是公司外掛的推薦模式(每家供應商一個外掛)。參考 內部:能力所有權
6

測試

src/provider.test.ts
import { describe, it, expect } from "vitest";
// 從 index.ts 或專用檔案匯出你的 provider config 物件
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();
  });
});

檔案結構

extensions/acme-ai/
├── package.json              # openclaw.providers 中繼資料
├── openclaw.plugin.json      # Manifest 搭配 providerAuthEnvVars
├── index.ts                  # definePluginEntry + registerProvider
└── src/
    ├── provider.test.ts      # 測試
    └── usage.ts              # 使用情況端點(可選)

Catalog order 參考

catalog.order 控制你的 catalog 何時與內建 provider 合併:
Order何時使用情況
simple第一遍純 API-key provider
profile簡單後Gated 於 auth profiles 的 provider
pairedProfile 後合成多個相關項目
late最後一遍覆蓋現有 provider(碰撞時勝出)

後續步驟