Skip to content

本页是 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

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

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 条目。

typescript
// 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 不同,可以添加双向文本变换,而不是替换流路径:

typescript
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:

typescript
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

typescript
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:

typescript
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-blockamazon-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-thinkingGemini thinking payload 在共享流路径上的规范化google, google-gemini-cli
kilocode-thinkingKilo reasoning 包装器(proxy stream 路径),kilo/auto 和不支持的 proxy reasoning id 自动跳过注入的 thinkingkilocode
moonshot-thinkingMoonshot 二进制原生 thinking payload 映射(来自 config + /think 级别)moonshot
minimax-fast-modeMiniMax 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-thinkingOpenRouter reasoning 包装器(proxy 路由),不支持的模型和 auto 跳过集中处理openrouter
tool-stream-default-on默认开启 tool_stream 包装器(如 Z.AI),除非显式禁用zai
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 交换(每次推理前):

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

自定义请求头:

typescript
// 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);
  };
},

原生传输身份标识:

typescript
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,
}),

用量和计费:

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

运行时回退说明:

  • 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(...) 调用并列。只选择你需要的选项卡:

语音 (TTS)
typescript
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 后缀。

实时转录

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

typescript
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 上传。

实时语音
typescript
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

媒体理解
typescript
api.registerMediaUnderstandingProvider({
  id: "acme-ai",
  capabilities: ["image", "audio"],
  describeImage: async (req) => ({ text: "A photo of..." }),
  transcribeAudio: async (req) => ({ text: "Transcript..." }),
});
嵌入
typescript
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(...)

图像和视频生成

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

typescript
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: [] }),
});
网页抓取和搜索
typescript
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:测试

typescript
// 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 插件与任何其他外部代码插件的发布方式相同:

bash
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
profilesimple 之后依赖 auth profile 的 Provider
pairedprofile 之后合成多个相关条目
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 会再次执行拿到完整数据。