本页是 OpenClaw Provider 插件(模型 Provider)的完整开发指南,适合需要将自定义 LLM、OpenAI 兼容代理或私有推理端点接入 OpenClaw 的开发者。从创建 package.json 和 openclaw.plugin.json 开始,到注册 Provider、添加动态模型解析、挂载 runtime hooks,再到附加语音/图像等能力,六步完成一个可用的 Provider 插件。关键概念:replay 家族(openai-compatible、anthropic-by-model、google-gemini)、catalog.order(simple/profile/paired/late)、definePluginEntry 与 defineSingleProviderPluginEntry 的区别。

OpenClaw Provider 插件开发:接入自定义 LLM 与模型厂商

本指南带你一步步为 OpenClaw 构建 Provider 插件,将任意 LLM 接入模型选择体系。

如果你此前没有构建过任何 OpenClaw 插件,请先阅读插件开发入门了解基础包结构和 manifest 配置。

开发步骤

步骤 1:创建包和 manifest

package.json

{
  "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"
    }
  }
}

openclaw.plugin.json

{
  "id": "acme-ai",
  "name": "Acme AI",
  "description": "Acme AI model provider",
  "providers": ["acme-ai"],
  "modelSupport": {
    "modelPrefixes": ["acme-"]
  },
  "providerAuthEnvVars": {
    "acme-ai": ["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
  }
}

manifest 中 providerAuthEnvVars 让 OpenClaw 无需加载插件代码即可探测认证状态。modelSupport 是可选的,它让 OpenClaw 在 runtime hooks 启动前就能从简写模型 id(如 acme-large)自动激活插件。providerAuthAliases 用于一个 provider 变体复用另一个 provider id 的认证。发布到 ClawHub 时,openclaw.compatopenclaw.build 字段必填。

步骤 2:注册 Provider

最小化 Provider 需要 idlabelauthcatalogcatalog 是 Provider 拥有的运行时/配置挂钩,可以调用厂商 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。可用于 text、image-generation、video-generation 和 music-generation 行。将厂商端点调用和响应映射放在插件中;OpenClaw 负责共享的行形状、source 标签和帮助渲染。

用户现在可以 openclaw onboard --acme-ai-api-key &lt;key&gt; 并选择 acme-ai/acme-large 作为模型。

如果上游 Provider 使用的 control 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 在传输前改写最终 system prompt 和文本消息内容。output 在 OpenClaw 解析自己的控制标记或渠道投递前改写 assistant 文本增量和最终文本。

对于只注册一个文本 Provider 加 API-key 认证和单一 catalog-backed runtime 的场景,可以用更窄的 defineSingleProviderPluginEntry helper:

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" }],
      }),
    },
  },
});

buildProvider 是实时目录路径,当 OpenClaw 能解析真实的 Provider 认证时使用。它可能执行特定于 Provider 的发现。buildStaticProvider 仅用于离线行,这些行在配置认证前可以安全显示;它不能要求凭证或发起网络请求。OpenClaw 的 models list --all 显示目前只对内置 Provider 插件执行静态目录,使用空配置、空环境和无智能体/工作区路径。

如果你的认证流还需要在配置过程中修补 models.providers.*、别名和智能体默认模型,可以使用 openclaw/plugin-sdk/provider-onboard 中的预设 helper。最窄的 helpers 是 createDefaultModelPresetAppliers(...)createDefaultModelsPresetAppliers(...)createModelCatalogPresetAppliers(...)

当 Provider 的原生端点在标准 openai-completions 传输上支持流式用量块时,优先使用 openclaw/plugin-sdk/provider-catalog-shared 中的共享目录 helper,而不是硬编码 Provider-id 检查。supportsNativeStreamingUsageCompat(...)applyProviderNativeStreamingUsageCompat(...) 从端点能力映射中检测支持情况,因此即使插件使用自定义 Provider id,原生 Moonshot/DashScope 风格的端点仍会启用。

步骤 3:添加动态模型解析

如果 Provider 接受任意模型 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:添加 Runtime Hooks(按需)

大多数 Provider 只需要 catalog + resolveDynamicModel。共享 helper builder 覆盖了最常见的 replay/tool-compat 家族,插件通常不需要逐一手动配置每个 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 共享 OpenAI 风格回放策略,含 tool-call-id 清理、assistant-first 排序修复,以及必要时通用 Gemini-turn 验证 moonshot, ollama, xai, zai
anthropic-by-model 按 modelId 选择 Claude 感知回放策略,仅在解析的模型是 Claude id 时才清理 Claude 特定的 thinking-block amazon-bedrock, anthropic-vertex
google-gemini 原生 Gemini 回放策略加 bootstrap 回放清理和标记推理输出模式 google, google-gemini-cli
passthrough-gemini 经 OpenAI 兼容代理传输运行的 Gemini 模型的 thought-signature 清理,不启用原生 Gemini 回放验证或 bootstrap 重写 openrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openai 在单个插件中混合 Anthropic-message 和 OpenAI-compatible 模型面的策略,Option 1 的 Claude 专属 thinking-block 丢弃仅限于 Anthropic 侧 minimax

可用 Stream 家族

家族 接线内容 内置示例
google-thinking Gemini thinking payload 在共享流路径上的规范化 google, google-gemini-cli
kilocode-thinking Kilo reasoning 包装器(proxy stream 路径),kilo/auto 和不支持的 proxy reasoning id 自动跳过注入的 thinking kilocode
moonshot-thinking Moonshot 二进制原生 thinking payload 映射(来自 config + /think 级别) moonshot
minimax-fast-mode MiniMax fast-mode 模型在共享流路径上的改写 minimax, minimax-portal
openai-responses-defaults 共享的原生 OpenAI/Codex Responses 包装器:attribution header、/fast/serviceTier、text verbosity、原生 Codex 网页搜索、reasoning-compat payload 整形和 Responses context 管理 openai, openai-codex
openrouter-thinking OpenRouter reasoning 包装器(proxy 路由),不支持的模型和 auto 跳过集中处理 openrouter
tool-stream-default-on 默认开启 tool_stream 包装器(如 Z.AI),除非显式禁用 zai

:::details SDK seams 支持家族 builder 每个家族 builder 由同一包中导出的底层公共 helpers 组成,当 Provider 需要偏离常见模式时可以直接使用:

  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamilybuildProviderReplayFamilyHooks(...) 和原始回放 builder(buildOpenAICompatibleReplayPolicybuildAnthropicReplayPolicyForModelbuildGoogleGeminiReplayPolicybuildHybridAnthropicOrOpenAIReplayPolicy)。还导出了 Gemini 回放 helper(sanitizeGoogleGeminiReplayHistoryresolveTaggedReasoningOutputMode)和端点/模型 helper(resolveProviderEndpointnormalizeProviderIdnormalizeGooglePreviewModelId)。
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamilybuildProviderStreamFamilyHooks(...)composeProviderStreamWrappers(...),加上共享的 OpenAI/Codex 包装器(createOpenAIAttributionHeadersWrappercreateOpenAIFastModeWrappercreateOpenAIServiceTierWrappercreateOpenAIResponsesContextManagementWrappercreateCodexNativeWebSearchWrapper)、DeepSeek V4 OpenAI 兼容包装器(createDeepSeekV4OpenAICompatibleThinkingWrapper)、Anthropic Messages thinking prefill 清理(createAnthropicThinkingPrefillPayloadWrapper)和共享 proxy/provider 包装器(createOpenRouterWrappercreateToolStreamWrappercreateMinimaxFastModeWrapper)。
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamilybuildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai") 和底层 Provider schema helpers。

有些 stream helpers 特意保持 Provider 本地化。@openclaw/anthropic-providerwrapAnthropicProviderStreamresolveAnthropicBetasresolveAnthropicFastModeresolveAnthropicServiceTier 和底层的 Anthropic 包装器 builder 保留在自身的公共 api.ts / contract-api.ts 层中,因为它们编码了 Claude OAuth beta 处理和 context1m 门控。xAI 插件同样将原生的 xAI Responses 整形保留在自身的 wrapStreamFn 中(/fast 别名、默认 tool_stream、不支持的 strict-tool 清理、xAI 特定的 reasoning-payload 移除)。

相同的包根模式也支持 @openclaw/openai-provider(Provider builder、默认模型 helper、实时 Provider builder)和 @openclaw/openrouter-provider(Provider builder 加配置/ onboarding helper)。 :::

其他常用 Hooks

Token 交换(每次推理前):

prepareRuntimeAuth: async (ctx) => {
  const exchanged = await exchangeToken(ctx.apiKey);
  return {
    apiKey: exchanged.token,
    baseUrl: exchanged.baseUrl,
    expiresAt: exchanged.expiresAt,
  };
},

自定义请求头:

// wrapStreamFn 返回一个从 ctx.streamFn 派生的 StreamFn
wrapStreamFn: (ctx) => {
  if (!ctx.streamFn) return undefined;
  const inner = ctx.streamFn;
  return async (params) => {
    params.headers = {
      ...params.headers,
      "X-Acme-Version": "2",
    };
    return inner(params);
  };
},

原生传输身份标识:

resolveTransportTurnState: (ctx) => ({
  headers: {
    "x-request-id": ctx.turnId,
  },
  metadata: {
    session_id: ctx.sessionId ?? "",
    turn_id: ctx.turnId,
  },
}),
resolveWebSocketSessionPolicy: (ctx) => ({
  headers: {
    "x-session-id": ctx.sessionId ?? "",
  },
  degradeCooldownMs: 60_000,
}),

用量和计费:

resolveUsageAuth: async (ctx) => {
  const auth = await ctx.resolveOAuthToken();
  return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
  return await fetchAcmeUsage(ctx.token, ctx.timeoutMs);
},

全部 Provider Hooks 一览

OpenClaw 按以下顺序调用 hooks。大多数 Provider 只用 2-3 个。OpenClaw 不再调用的兼容性 Provider 字段(如 ProviderPlugin.capabilitiessuppressBuiltInModel)此处未列出。

序号 Hook 使用时机
1 catalog 模型目录或 baseUrl 默认值
2 applyConfigDefaults 配置材料化时的 Provider 全局默认值
3 normalizeModelId 旧版/预览模型 id 别名查找前的清理
4 normalizeTransport Provider 家族 api/baseUrl 在通用模型组装前的清理
5 normalizeConfig 规范化 models.providers.&lt;id&gt; 配置
6 applyNativeStreamingUsageCompat 配置 Provider 的原生流式用量兼容性改写
7 resolveConfigApiKey Provider 拥有的环境标记认证解析
8 resolveSyntheticAuth 本地/自托管或配置支持的综合认证
9 shouldDeferSyntheticProfileAuth 将综合存储配置文件占位符降低到环境/配置认证后面
10 resolveDynamicModel 接受任意上游模型 id
11 prepareDynamicModel 解析前的异步元数据预取
12 normalizeResolvedModel runner 前的传输改写
13 contributeResolvedModelCompat 为另一个兼容传输后面的厂商模型提供兼容标志
14 normalizeToolSchemas 注册前 Provider 拥有的工具模式清理
15 inspectToolSchemas Provider 拥有的工具模式诊断
16 resolveReasoningOutputMode 标记 vs 原生推理输出契约
17 prepareExtraParams 默认请求参数
18 createStreamFn 完全自定义 StreamFn 传输
19 wrapStreamFn 普通流式路径上的自定义头/body 包装
20 resolveTransportTurnState 原生每次轮次的头/元数据
21 resolveWebSocketSessionPolicy 原生 WS 会话头/冷却
22 formatApiKey 自定义运行时 token 形状
23 refreshOAuth 自定义 OAuth 刷新
24 buildAuthDoctorHint 认证修复指导
25 matchesContextOverflowError Provider 拥有的溢出检测
26 classifyFailoverReason Provider 拥有的限流/过载分类
27 isCacheTtlEligible 提示缓存 TTL 门控
28 buildMissingAuthMessage 自定义缺失认证提示
29 augmentModelCatalog 综合前向兼容行
30 resolveThinkingProfile 模型特定的 /think 选项集
31 isBinaryThinking 二进制 thinking 开/关兼容性
32 supportsXHighThinking xhigh reasoning 支持兼容性
33 resolveDefaultThinkingLevel 默认 /think 策略兼容性
34 isModernModelRef 实时/测试模型匹配
35 prepareRuntimeAuth 推理前的 token 交换
36 resolveUsageAuth 自定义用量凭证解析
37 fetchUsageSnapshot 自定义用量端点
38 createEmbeddingProvider Provider 拥有的内存/搜索嵌入适配器
39 buildReplayPolicy 自定义转录回放/压缩策略
40 sanitizeReplayHistory 通用清理后 Provider 特定的回放改写
41 validateReplayTurns 嵌入式 runner 前的严格回放轮次验证
42 onModelSelected 选择后回调(如遥测)

运行时回退说明:

  • normalizeConfig 首先检查匹配的 Provider,然后检查其他有 hook 能力的 Provider 插件,直到有一个实际更改了配置。如果没有 Provider hook 改写了支持的 Google 家族配置条目,绑定的 Google 配置规范化器仍然会应用。
  • resolveConfigApiKey 在暴露时使用 Provider hook。绑定的 amazon-bedrock 路径在这里也有内置的 AWS 环境标记解析器,尽管 Bedrock 运行时认证本身仍使用 AWS SDK 默认链。
  • resolveSystemPromptContribution 让 Provider 为一个模型家族注入缓存感知的系统提示指导。当行为属于一个 Provider/模型家族并且应保持稳定/动态缓存拆分时,优先使用它而非 before_prompt_build

有关详细说明和真实示例,请参见 Internals: Provider Runtime Hooks

步骤 5:添加附加能力(可选)

Provider 插件可以在文本推理之外注册嵌入、语音、实时转录、实时语音、媒体理解、图像生成、视频生成、网页抓取和网页搜索能力。OpenClaw 将此归类为 hybrid-capability 插件——这是公司插件(每个厂商一个插件)的推荐模式。参见 Internals: Capability Ownership

register(api) 内部注册每个能力,与你现有的 api.registerProvider(...) 调用并列。只选择你需要的选项卡:

:::details 语音 (TTS)

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(...) 处理 Provider HTTP 失败,以便插件共享 capped 错误体读取、JSON 错误解析和 request-id 后缀。 :::

:::details 实时转录 优先使用 createRealtimeTranscriptionWebSocketSession(...)——共享 helper 处理 proxy 捕获、重连退避、close flushing、ready handshake、音频排队和 close-event 诊断。你的插件只需映射上游事件。

api.registerRealtimeTranscriptionProvider({
  id: "acme-ai",
  label: "Acme Realtime Transcription",
  isConfigured: () => true,
  createSession: (req) => {
    const apiKey = String(req.providerConfig.apiKey ?? "");
    return createRealtimeTranscriptionWebSocketSession({
      providerId: "acme-ai",
      callbacks: req,
      url: "wss://api.example.com/v1/realtime-transcription",
      headers: { Authorization: `Bearer ${apiKey}` },
      onMessage: (event, transport) => {
        if (event.type === "session.created") {
          transport.sendJson({ type: "session.update" });
          transport.markReady();
          return;
        }
        if (event.type === "transcript.final") {
          req.onTranscript?.(event.text);
        }
      },
      sendAudio: (audio, transport) => {
        transport.sendJson({
          type: "audio.append",
          audio: audio.toString("base64"),
        });
      },
      onClose: (transport) => {
        transport.sendJson({ type: "audio.end" });
      },
    });
  },
});

批量 STT Provider 如果 POST multipart audio,应使用 openclaw/plugin-sdk/provider-http 中的 buildAudioTranscriptionFormData(...)。该 helper 规范化上传文件名,包括为兼容的转录 API 需要 M4A 风格文件名的 AAC 上传。 :::

:::details 实时语音

api.registerRealtimeVoiceProvider({
  id: "acme-ai",
  label: "Acme Realtime Voice",
  capabilities: {
    transports: ["gateway-relay"],
    inputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
    outputAudioFormats: [{ encoding: "pcm16", sampleRateHz: 24000, channels: 1 }],
    supportsBargeIn: true,
    supportsToolCalls: true,
  },
  isConfigured: ({ providerConfig }) => Boolean(providerConfig.apiKey),
  createBridge: (req) => ({
    // 仅当 Provider 接受对一次调用的多个工具响应时设置此项,
    // 例如,一个即时的 "working" 响应后跟最终结果。
    supportsToolResultContinuation: false,
    connect: async () => {},
    sendAudio: () => {},
    setMediaTimestamp: () => {},
    handleBargeIn: () => {},
    submitToolResult: () => {},
    acknowledgeMark: () => {},
    close: () => {},
    isConnected: () => true,
  }),
});

声明 capabilities 以便 talk.catalog 可以向浏览器和原生 Talk 客户端暴露有效模式、传输、音频格式和功能标志。当传输可以检测到人类在中断助手播放,且 Provider 支持截断或清除活动音频响应时,实现 handleBargeIn。 :::

:::details 媒体理解

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

:::

:::details 嵌入

api.registerEmbeddingProvider({
  id: "acme-ai",
  defaultModel: "acme-embed",
  transport: "remote",
  authProviderId: "acme-ai",
  create: async ({ model }) => ({
    provider: {
      id: "acme-ai",
      model,
      dimensions: 1536,
      embed: async (input) => {
        const text = typeof input === "string" ? input : input.text;
        return fetchAcmeEmbedding(text);
      },
      embedBatch: async (inputs) =>
        Promise.all(
          inputs.map((input) =>
            fetchAcmeEmbedding(typeof input === "string" ? input : input.text),
          ),
        ),
    },
  }),
});

contracts.embeddingProviders 中声明相同的 id。这是可重用向量生成的通用嵌入契约。仅对内存引擎特定的适配器使用 registerMemoryEmbeddingProvider(...)。 :::

:::details 图像和视频生成 视频能力使用 模式感知 形状:generateimageToVideovideoToVideo。扁平聚合字段如 maxInputImages/maxInputVideos/maxDurationSeconds 不足以清晰地通告转换模式支持或禁用模式。音乐生成遵循相同的模式,带有显式的 generate/edit 块。

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

api.registerVideoGenerationProvider({
  id: "acme-ai",
  label: "Acme Video",
  defaultTimeoutMs: 600_000,
  capabilities: {
    generate: { maxVideos: 1, maxDurationSeconds: 10, supportsResolution: true },
    imageToVideo: {
      enabled: true,
      maxVideos: 1,
      maxInputImages: 1,
      maxInputImagesByModel: { "acme/reference-to-video": 9 },
      maxDurationSeconds: 5,
    },
    videoToVideo: { enabled: false },
  },
  generateVideo: async (req) => ({ videos: [] }),
});

:::

:::details 网页抓取和搜索

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: [] }),
});

:::

步骤 6:测试

// src/provider.test.ts
import { describe, it, expect } from "vitest";
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

Provider 插件与任何其他外部代码插件的发布方式相同:

clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin

发布插件包时不要使用旧版 skill-only 发布别名,应使用 clawhub package publish

文件结构

<bundled-plugin-root>/acme-ai/
├── package.json              # openclaw.providers 元数据
├── openclaw.plugin.json      # Manifest,包含 provider auth 元数据
├── index.ts                  # definePluginEntry + registerProvider
└── src/
    ├── provider.test.ts      # 测试
    └── usage.ts              # 用量端点(可选)

Catalog Order 参考

catalog.order 控制目录相对于内置 Provider 的合并顺序:

顺序 时机 使用场景
simple 第一轮 普通 API-key Provider
profile simple 之后 依赖 auth profile 的 Provider
paired profile 之后 合成多个相关条目
late 最后一轮 覆盖已有 Provider(碰撞时胜出)

下一步

相关文档

常见问题

我的 Provider 是 OpenAI 兼容的代理,用哪个 replay 家族最合适?

通常选 openai-compatible,它提供共享的 OpenAI 风格回放策略,含 tool-call-id 清理和 assistant-first 排序修复,适配大多数 OpenAI 兼容端点。如果代理后面跑的是 Gemini 模型,加上 passthrough-gemini 处理 thought-signature 清理。

defineSingleProviderPluginEntrydefinePluginEntry + registerProvider 有什么区别?

defineSingleProviderPluginEntry 是更窄的 helper,专门用于"只注册一个文本 Provider + API-key 认证 + catalog-backed runtime"的简单场景,少写很多样板代码。如果插件还需要注册语音、图像生成或其他附加能力,用 definePluginEntry

resolveDynamicModelprepareDynamicModel 分别适合什么场景?

resolveDynamicModel 是同步的,适合直接返回模型元数据的场景(如代理透传任意 id)。prepareDynamicModel 是异步的,用于解析前需要网络请求的场景(如从上游 API 拉取模型信息),完成后 resolveDynamicModel 会再次执行拿到完整数据。