Appearance
本页是 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.compat 和 openclaw.build 字段必填。
步骤 2:注册 Provider
最小化 Provider 需要 id、label、auth 和 catalog。catalog 是 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 <key> 并选择 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-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 |
SDK seams 支持家族 builder
每个家族 builder 由同一包中导出的底层公共 helpers 组成,当 Provider 需要偏离常见模式时可以直接使用:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily、buildProviderReplayFamilyHooks(...)和原始回放 builder(buildOpenAICompatibleReplayPolicy、buildAnthropicReplayPolicyForModel、buildGoogleGeminiReplayPolicy、buildHybridAnthropicOrOpenAIReplayPolicy)。还导出了 Gemini 回放 helper(sanitizeGoogleGeminiReplayHistory、resolveTaggedReasoningOutputMode)和端点/模型 helper(resolveProviderEndpoint、normalizeProviderId、normalizeGooglePreviewModelId)。openclaw/plugin-sdk/provider-stream-ProviderStreamFamily、buildProviderStreamFamilyHooks(...)、composeProviderStreamWrappers(...),加上共享的 OpenAI/Codex 包装器(createOpenAIAttributionHeadersWrapper、createOpenAIFastModeWrapper、createOpenAIServiceTierWrapper、createOpenAIResponsesContextManagementWrapper、createCodexNativeWebSearchWrapper)、DeepSeek V4 OpenAI 兼容包装器(createDeepSeekV4OpenAICompatibleThinkingWrapper)、Anthropic Messages thinking prefill 清理(createAnthropicThinkingPrefillPayloadWrapper)和共享 proxy/provider 包装器(createOpenRouterWrapper、createToolStreamWrapper、createMinimaxFastModeWrapper)。openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily、buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")和底层 Provider schema helpers。
有些 stream helpers 特意保持 Provider 本地化。@openclaw/anthropic-provider 将 wrapAnthropicProviderStream、resolveAnthropicBetas、resolveAnthropicFastMode、resolveAnthropicServiceTier 和底层的 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.capabilities 和 suppressBuiltInModel)此处未列出。
| 序号 | Hook | 使用时机 |
|---|---|---|
| 1 | catalog | 模型目录或 baseUrl 默认值 |
| 2 | applyConfigDefaults | 配置材料化时的 Provider 全局默认值 |
| 3 | normalizeModelId | 旧版/预览模型 id 别名查找前的清理 |
| 4 | normalizeTransport | Provider 家族 api/baseUrl 在通用模型组装前的清理 |
| 5 | normalizeConfig | 规范化 models.providers.<id> 配置 |
| 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(...) 调用并列。只选择你需要的选项卡:
语音 (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(...)。
图像和视频生成
视频能力使用 模式感知 形状:generate、imageToVideo 和 videoToVideo。扁平聚合字段如 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 |
profile | simple 之后 | 依赖 auth profile 的 Provider |
paired | profile 之后 | 合成多个相关条目 |
late | 最后一轮 | 覆盖已有 Provider(碰撞时胜出) |
下一步
- 渠道插件开发 — 如果插件同时提供渠道
- SDK 运行时 —
api.runtimehelpers(TTS、搜索、subagent) - SDK 概览 — 完整子路径导入参考
- 插件内部机制 — hook 详情和内置示例
相关文档
常见问题
我的 Provider 是 OpenAI 兼容的代理,用哪个 replay 家族最合适?
通常选 openai-compatible,它提供共享的 OpenAI 风格回放策略,含 tool-call-id 清理和 assistant-first 排序修复,适配大多数 OpenAI 兼容端点。如果代理后面跑的是 Gemini 模型,加上 passthrough-gemini 处理 thought-signature 清理。
defineSingleProviderPluginEntry 和 definePluginEntry + registerProvider 有什么区别?
defineSingleProviderPluginEntry 是更窄的 helper,专门用于"只注册一个文本 Provider + API-key 认证 + catalog-backed runtime"的简单场景,少写很多样板代码。如果插件还需要注册语音、图像生成或其他附加能力,用 definePluginEntry。
resolveDynamicModel 和 prepareDynamicModel 分别适合什么场景?
resolveDynamicModel 是同步的,适合直接返回模型元数据的场景(如代理透传任意 id)。prepareDynamicModel 是异步的,用于解析前需要网络请求的场景(如从上游 API 拉取模型信息),完成后 resolveDynamicModel 会再次执行拿到完整数据。