构建提供商插件
本指南将详细介绍如何构建一个提供商插件,该插件用于将模型提供商 (LLM) 添加到 OpenClaw。完成后,您将拥有一个包含模型目录、API 密钥身份验证和动态模型解析的提供商。
Package and manifest
步骤 1:包和清单
Section titled “步骤 1:包和清单”{"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-"]},"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”, “cliDescription”: “Acme AI API key” } ], “configSchema”: { “type”: “object”, “additionalProperties”: false } } ```
清单声明了
providerAuthEnvVarsOpenClaw,以便 OpenClaw 可以在 不加载插件运行时的情况下检测凭据。当提供商变体应重用另一个提供商 ID 的身份验证时,添加providerAuthAliases。modelSupportOpenClaw 是可选的,它允许 OpenClaw 在运行时钩子存在之前,从简写的模型 ID(如acme-largeClawHub)自动加载您的提供商插件。如果您在 ClawHub 上发布 提供商,则package.json中需要这些openclaw.compat和openclaw.build字段。注册提供商
一个最小的文本提供商需要一个
id、label、auth和catalog。catalog是提供商拥有的运行时/配置钩子;它可以调用实时的 供应商 API 并返回models.providers条目。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 的较新的控制平面目录表面。 将其用于文本、图像生成、视频生成和音乐生成行。将供应商端点调用和 响应映射保留在插件中;OpenClaw 拥有共享的行形状、源 标签和帮助渲染。这就是一个可工作的提供商。用户现在可以 `openclaw onboard —acme-ai-api-key
并选择acme-ai/acme-large` 作为其模型。如果上游提供商使用的控制标记与 OpenClaw 不同,请添加一个小的双向文本转换,而不是替换流路径:```typescriptapi.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 解析其自己的控制标记或渠道传递之前重写助手文本增量和最终文本。对于仅注册一个带有 API 密钥身份验证和单个支持目录的运行时的打包提供商,请优先使用更窄的`defineSingleProviderPluginEntry(...)` 辅助函数:```typescriptimport { 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" }],}),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 风格的端点仍然会选择加入。添加动态模型解析
如果您的提供商接受任意的模型 ID(例如代理或路由器), 添加
resolveDynamicModel:api.registerProvider({// ... id, label, auth, catalog from aboveresolveDynamicModel: (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会在完成后再次运行。添加运行时钩子(根据需要)
大多数提供商只需要
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、zaianthropic-by-model由 modelId选择的 Claude 感知重放策略,因此 Anthropic 消息传输仅在解析的模型实际上是 Claude ID 时才获得 Claude 特定的思维块清理amazon-bedrock、anthropic-vertexgoogle-gemini原生 Gemini 重放策略加上引导重放清理和标记推理输出模式 google、google-gemini-clipassthrough-gemini通过 OpenAI 兼容代理传输运行的 Gemini 模型的 Gemini 思维签名清理;不启用原生 Gemini 重放验证或引导重写 openrouter、kilocode、opencode、opencode-gohybrid-anthropic-openai混合策略,适用于在一个插件中混合 Anthropic 消息和 OpenAI 兼容模型表面的提供商;可选的仅 Claude 思维块删除保持限定在 Anthropic 一侧 minimax目前可用的流式系列:
系列 连接内容 内置示例 google-thinking共享流路径上的 Gemini 思维载荷标准化 google、google-gemini-clikilocode-thinking共享代理流路径上的 Kilo 推理包装器,具有 kilo/auto和不支持的代理推理 ID 跳过注入的思维kilocodemoonshot-thinking来自配置 + /think级别的 Moonshot 二进制原生思维载荷映射moonshotminimax-fast-mode共享流路径上的 MiniMax 快速模式模型重写 minimax、minimax-portalopenai-responses-defaults共享原生 OpenAI/Codex Responses 包装器:归属标头、 /fast/serviceTier、文本详细程度、原生 Codex 网络搜索、推理兼容载荷整形以及 Responses 上下文管理openai、openai-codexopenrouter-thinking代理路由的 OpenRouter 推理包装器,具有不支持的模型/ auto跳过集中处理openroutertool-stream-default-on适用于像 Z.AI 这样希望进行工具流式传输除非明确禁用的提供商的默认启用 tool_stream包装器zai支持系列构建器的 SDK 接缝
每个系列构建器都是由从同一包导出的较低级别的公共辅助函数组成的,当提供商需要偏离通用模式时,您可以使用它们:
openclaw/plugin-sdk/provider-model-shared-ProviderReplayFamily、buildProviderReplayFamilyHooks(...)和原始重放构建器(buildOpenAICompatibleReplayPolicy、buildAnthropicReplayPolicyForModel、buildGoogleGeminiReplayPolicy、buildHybridAnthropicOrOpenAIReplayPolicy)。还导出 Gemini 重放辅助函数(sanitizeGoogleGeminiReplayHistory、resolveTaggedReasoningOutputMode)和端点/模型辅助函数(resolveProviderEndpoint、normalizeProviderId、normalizeGooglePreviewModelId)。openclaw/plugin-sdk/provider-stream-ProviderStreamFamily、buildProviderStreamFamilyHooks(...)、composeProviderStreamWrappers(...),以及共享 OpenAI/Codex 包装器(createOpenAIAttributionHeadersWrapper、createOpenAIFastModeWrapper、createOpenAIServiceTierWrapper、createOpenAIResponsesContextManagementWrapper、createCodexNativeWebSearchWrapper)、DeepSeek V4 OpenAI 兼容包装器(createDeepSeekV4OpenAICompatibleThinkingWrapper)、Anthropic Messages 思维预填充清理(createAnthropicThinkingPrefillPayloadWrapper)和共享代理/提供商包装器(createOpenRouterWrapper、createToolStreamWrapper、createMinimaxFastModeWrapper)。openclaw/plugin-sdk/provider-tools-ProviderToolCompatFamily、buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")和底层提供商模式辅助函数。
一些流辅助函数有意保持提供商本地化。
@openclaw/anthropic-provider将wrapAnthropicProviderStream、resolveAnthropicBetas、resolveAnthropicFastMode、resolveAnthropicServiceTier和较低级别的 Anthropic 包装器构建器保存在其自己的公共api.ts/contract-api.ts接缝中,因为它们编码了 Claude OAuth beta 处理和context1m闸。xAI 插件同样在其自己的wrapStreamFn中保留原生 xAI Responses 整形(/fast别名、默认tool_stream、不支持的严格工具清理、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,};},对于需要自定义请求标头或正文修改的提供商:
// wrapStreamFn returns a StreamFn derived from ctx.streamFnwrapStreamFn: (ctx) => {if (!ctx.streamFn) return undefined;const inner = ctx.streamFn;return async (params) => {params.headers = {...params.headers,"X-Acme-Version": "2",};return inner(params);};},对于需要通用 HTTP 或 WebSocket 传输上的原生请求/会话标头或元数据的提供商:
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);},所有可用的提供商钩子
OpenClaw 按此顺序调用钩子。大多数提供商仅使用 2-3 个: OpenClaw 不再调用的仅兼容性提供商字段(如
ProviderPlugin.capabilities和suppressBuiltInModel)未在此列出。# 钩子 何时使用 1 catalog模型目录或基础 URL 默认值 2 applyConfigDefaults配置具体化期间的提供商拥有的全局默认值 3 normalizeModelId查找前的旧版/预览模型 ID 别名清理 4 normalizeTransport通用模型组装前的提供商系列 api/baseUrl清理5 normalizeConfig标准化 `models.providers. 配置 | | 6 |applyNativeStreamingUsageCompat| 配置提供商的原生流使用兼容重写 | | 7 |resolveConfigApiKey| 提供商拥有的环境标记身份验证解析 | | 8 |resolveSyntheticAuth| 本地/自托管或配置支持的合成身份验证 | | 9 |shouldDeferSyntheticProfileAuth| 在环境/配置身份验证之后降低合成存储配置文件占位符 | | 10 |resolveDynamicModel| 接受任意上游模型 ID | | 11 |prepareDynamicModel| 解析前的异步元数据获取 | | 12 |normalizeResolvedModel| 运行程序之前的传输重写 | | 13 |contributeResolvedModelCompat| 另一个兼容传输后面的供应商模型的兼容标志 | | 14 |normalizeToolSchemas| 注册前的提供商拥有的工具模式清理 | | 15 |inspectToolSchemas| 提供商拥有的工具模式诊断 | | 16 |resolveReasoningOutputMode| 标记与原生推理输出契约 | | 17 |prepareExtraParams| 默认请求参数 | | 18 |createStreamFn| 完全自定义的 StreamFn 传输 | | 19 |wrapStreamFn| 正常流路径上的自定义标头/正文包装器 | | 20 |resolveTransportTurnState| 原生每轮标头/元数据 | | 21 |resolveWebSocketSessionPolicy| 原生 WS 会话标头/冷却 | | 22 |formatApiKey| 自定义运行时令牌形状 | | 23 |refreshOAuth| 自定义 OAuth 刷新 | | 24 |buildAuthDoctorHint| 身份验证修复指导 | | 25 |matchesContextOverflowError| 提供商拥有的溢出检测 | | 26 |classifyFailoverReason| 提供商拥有的速率限制/过载分类 | | 27 |isCacheTtlEligible| 提示缓存 TTL 闸 | | 28 |buildMissingAuthMessage| 自定义缺少身份验证提示 | | 29 |augmentModelCatalog| 合成向前兼容行 | | 30 |resolveThinkingProfile| 模型特定的/think选项集 | | 31 |isBinaryThinking| 二进制思维开/关兼容性 | | 32 |supportsXHighThinking|xhigh推理支持兼容性 | | 33 |resolveDefaultThinkingLevel| 默认/think策略兼容性 | | 34 |isModernModelRef| 实时/冒烟模型匹配 | | 35 |prepareRuntimeAuth| 推理前的令牌交换 | | 36 |resolveUsageAuth| 自定义使用凭据解析 | | 37 |fetchUsageSnapshot| 自定义使用端点 | | 38 |createEmbeddingProvider| 提供商拥有的用于内存/搜索的嵌入适配器 | | 39 |buildReplayPolicy| 自定义脚本重放/压缩策略 | | 40 |sanitizeReplayHistory| 通用清理后的提供商特定重放重写 | | 41 |validateReplayTurns| 嵌入式运行程序之前的严格重放轮验证 | | 42 |onModelSelected` | 选择后回调(例如遥测) |运行时回退说明:- `normalizeConfig` 首先检查匹配的提供商,然后检查其他具有钩子能力的提供商插件,直到有一个实际更改配置。如果没有提供商钩子重写受支持的 Google 系列配置条目,捆绑的 Google 配置标准化器仍然适用。- `resolveConfigApiKey` 在公开时使用提供商钩子。捆绑的 `amazon-bedrock` 路径在此处也有一个内置 AWS 环境标记解析器,尽管 Bedrock 运行时身份验证本身仍使用 AWS SDK 默认链。- `resolveSystemPromptContribution` 允许提供商为模型系列注入缓存感知的系统提示指导。当行为属于一个提供商/模型系列并且应保留稳定/动态缓存拆分时,优先于 `before_prompt_build` 使用它。有关详细说明和实际示例,请参阅 [内部:提供商运行时钩子](/zh/plugins/architecture-internals#provider-runtime-hooks)。添加额外功能(可选)
步骤 5:添加额外功能
Section titled “步骤 5:添加额外功能”提供商插件可以在文本推理之外注册语音、实时转录、实时语音、媒体理解、图像生成、视频生成、Web 获取和 Web 搜索。OpenClaw 将其归类为 混合功能 插件——这是公司插件(每个供应商一个插件)的推荐模式。请参阅 内部原理:功能所有权。
在
register(api)内部注册每个功能,并与你现有的api.registerProvider(...)调用并列。仅选择你需要的标签页:import {assertOkOrThrowProviderError,postJsonRequest,} from "openclaw/plugin-sdk/provider-http";api.registerSpeechProvider({id: "acme-ai",label: "Acme Speech",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();}},});对提供商 HTTP 失败使用
assertOkOrThrowProviderError(...),以便 插件共享受限的错误主体读取、JSON 错误解析和 请求 ID 后缀。优先使用
createRealtimeTranscriptionWebSocketSession(...)—— 这个共享 助手会处理代理捕获、重连退避、关闭刷新、就绪 握手、音频排队和关闭事件诊断。你的插件 只需映射上游事件。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" });},});},});POST 多部分音频的批处理 STT 提供商应使用
buildAudioTranscriptionFormData(...)(来自openclaw/plugin-sdk/provider-http)。该助手会标准化上传 文件名,包括需要 M4A 风格文件名的 AAC 上传, 以适配转录 API。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) => ({// Set this only if the provider accepts multiple tool responses for// one call, for example an immediate "working" response followed by// the final result.supportsToolResultContinuation: false,connect: async () => {},sendAudio: () => {},setMediaTimestamp: () => {},handleBargeIn: () => {},submitToolResult: () => {},acknowledgeMark: () => {},close: () => {},isConnected: () => true,}),});声明
capabilities,以便talk.catalog能够向浏览器和原生 Talk 客户端暴露有效的模式、传输方式、音频格式和功能标志。当传输方式可以检测到 人类正在打断助手播放,并且提供商支持 截断或清除活动音频响应时,实现handleBargeIn。api.registerMediaUnderstandingProvider({id: "acme-ai",capabilities: ["image", "audio"],describeImage: async (req) => ({ text: "A photo of..." }),transcribeAudio: async (req) => ({ text: "Transcript..." }),});视频功能使用 模式感知 形状:
generate、imageToVideo和videoToVideo。像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",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: [] }),});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:测试
Section titled “步骤 6:测试”import { describe, it, expect } from "vitest";// Export your provider config object from index.ts or a dedicated fileimport { 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
Section titled “发布到 ClawHub”提供商插件的发布方式与任何其他外部代码插件相同:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-plugin此处不要使用旧版的仅技能发布别名;插件包应使用
clawhub package publish。
<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)目录顺序参考
Section titled “目录顺序参考”catalog.order 控制您的目录相对于内置提供商的合并时机:
| 顺序 | 时机 | 用例 |
|---|---|---|
simple | 第一遍 | 纯 API 密钥提供商 |
profile | 简单之后 | 基于认证配置文件的提供商 |
paired | 配置文件之后 | 综合多个相关条目 |
late | 最后一遍 | 覆盖现有提供商(冲突时获胜) |