提供商插件
构建一个提供者插件,为 OpenClaw 添加模型提供者(LLM):包含模型目录、API 密钥认证和动态模型解析。
Acme AI 是本指南及其子页面中使用的虚构供应商。示例中名为 fetchAcme* 的辅助函数是你自己的供应商 API 调用的占位符,而非 OpenClaw 导出的函数。
Info
刚接触 OpenClaw 插件?请先阅读入门指南,了解包结构和清单(manifest)设置。
Tip
提供者插件将模型添加到 OpenClaw 的常规推理循环中。如果模型必须通过一个管理线程、压缩或工具事件的原生代理守护进程来运行,请将提供者与 agent harness 配对,而不是将守护进程协议细节放入核心。
在登录期间导入现有凭据¶
认证方法可以通过 credentialImport 声明 migrationProviderId、精确的 itemId 和 credentialKind(api_key、oauth 或 token)。models auth login 会在开始交互式登录之前,向该迁移所有者请求仅认证方案。--force、--profile-id 和 --set-default 会跳过导入。--set-default 通过正常的登录流程使用认证方法推荐的模型。
迁移插件在 contracts.migrationProviders 中声明其 ID,并且可以从顶层 migration-provider-api.ts 公共产物中导出 buildMigrationProvider()。保持该入口轻量。内置插件和已启用的已安装插件可以提供该入口,而无需替换正在运行的插件注册表。被显式禁用或拒绝的迁移所有者无法执行其产物。现有的内置迁移兼容性规则仍然适用。
登录调用方仅选择已声明的认证条目。其详细信息必须包含匹配的 provider 和 credentialKind。迁移结果还会提供已保存的 profileId。所有者必须尊重取消请求,在持久化之前重新读取所选来源,并拒绝已更改的凭据。登录传递 configPatchMode: "none",以便导入保留模型默认值和限制。如果存储不可用,或匹配的 OAuth 配置文件不可用,则继续交互式登录。仅凭匹配的账户身份并不能使过期的凭据可用。所选导入失败会停止操作,而不是静默地开始另一次登录。
回环 OAuth 回调¶
内置提供者使用 openclaw/plugin-sdk/provider-auth-runtime 中的 startProviderOAuthLoopbackCallbackServer,在打开浏览器之前绑定其回调。waitForCallback() 返回 OAuth 错误或经过验证的 code/state 对,其中包含用于提供者特定字段的 parameters: URLSearchParams。重复的参数仍然可供提供者进行验证。
默认响应会确认回调并关闭监听器。设置 deferResponse: true 可在调用 complete({ status, body, contentType }) 之前完成令牌交换和身份检查。中止、可选的 timeoutMs 以及浏览器断开连接仍然会关闭延迟响应;迟到的 complete() 调用是空操作(no-op)。始终在 finally 中调用 close()。调用方的 signal 和权限检查负责令牌请求和持久化;监听器的截止时间不会取消该工作。
默认情况下,监听器会绑定为重定向主机名解析出的每个回环地址。bindHostname 添加一个回环主机。改用 bindOnlyHostname 以保留提供者的确切 Node 绑定主机(localhost、127.0.0.1 或 ::1)。
在登录后处理模型访问¶
使用 openclaw/plugin-sdk/provider-auth-login-flow-runtime 中 runModelsAuthLoginFlow 的现有使用者必须在凭据保存后处理一个选择。当有效限制可能隐藏提供者的模型时,现有的 prompter.select 会收到以下选项:
| 值 | 标签 | 效果 |
|---|---|---|
all |
Show all <Provider> models |
将该提供者的通配符添加到现有策略所有者。 |
keep |
Keep current restrictions |
保持限制不变。 |
渲染提供的消息和选项,并返回所选选项的值。不要假设每次 select 调用都会选择提供者或认证方法。这两个选项都不会激活新的默认模型。当限制不存在或已经允许整个提供者时,不会请求选择。
取消或拒绝此保存后选择不会撤销已保存的凭据。该流程会抛出 ProviderAuthConfigApplyError,它继承自 ProviderCredentialsSavedError;应报告凭据已保存,而不是将其视为一次失败的凭据交换。在选择阶段取消会保持限制不变。后续的应用失败可能使策略已保存,但在运行中的 Gateway 中未生效。请将凭据持久化的结果与模型可见性的结果区分开来。
将选择推迟到稍后的回复¶
对于聊天按钮,请传递同步的 onModelAccessRequested 回调。它接收一个 PreparedProviderModelAccess 请求,并取代保存后的 select 调用;它不会应用该选择。保留该准备好的请求,直到登录完成。使用 createProviderLoginFlowRegistry 和 reserveProviderLoginFlow 仅保留凭据交换。
登录完成后,使用 flows、flowKey、prepared 和 terminalMessage 调用 offerProviderLoginModelAccess。传递其结构化回复。始终在 finally 中使用 releaseProviderLoginFlow({ flows, flowKey, record }) 释放登录。待处理的模型访问问题有自己的生命周期,在释放之后仍然可以回答;它不会阻塞另一次登录。
将后续命令传递给 answerProviderLoginModelAccess,并附带 flows、flowKey、agentId、command、runtime、readConfig 和 assertCurrent;signal 是可选的。readConfig 必须返回宿主的当前配置。所有者验证答案、应用选择,并消费该问题。过期或冲突的选择会基于当前限制收到一个新的问题。使用 cancelProviderLoginFlow({ flows, flowKey }) 取消任一待处理阶段。不要根据按钮文本重建通配符写入,也不要为新的登录复用已准备好的请求。
保持托管写入已授权¶
托管调用方提供 signal 和 assertCurrent,用于在产生副作用之前以及等待的工作完成之后,检查当前登录状态、发送方权限以及所选的 provider/method。仅凭中止信号或匹配的登录标识符并不构成当前授权。beforePersistentEffect 仍然是凭证持久化准备回调。浏览器授权在凭证阶段结束后终止;后续的模型选择使用当前会话或向导权限。
对于延迟选择,请将应答命令的当前权限检查作为 answerProviderLoginModelAccess.assertCurrent 传入。如果提供了其 config 参数,请使用它:它是策略写入方的当前配置。否则读取宿主当前的配置。原始登录回调不会授权后续命令。让共享所有者报告可见性结果:已保存的策略并不能证明正在运行的 Gateway 已应用该策略。
演练¶
1. 打包和清单
### 步骤 1:打包和清单 {#step-1-package-and-manifest}
{
"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-"]
},
"setup": {
"providers": [
{
"id": "acme-ai",
"envVars": ["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
}
}
setup.providers[].envVars 让 OpenClaw 无需加载你的插件运行时即可检测凭证。当某个 provider 变体需要复用另一个 provider id 的认证时,添加 providerAuthAliases。modelSupport 是可选的,它让 OpenClaw 在运行时钩子存在之前,从类似 acme-large 的简写 model id 自动加载你的 provider 插件。package.json 中的 openclaw.compat 和 openclaw.build 是 ClawHub 发布所必需的(openclaw.compat.pluginApi 和 openclaw.build.openclawVersion 是两个必需字段。省略 minGatewayVersion 时,它会回退到 openclaw.install.minHostVersion)。
示例清单中的版本字符串是占位符。请将其固定为你的插件构建和测试所针对的 OpenClaw 版本。
2. 注册 provider
最简文本 provider 需要一个 id、label、auth 和 catalog。
catalog 是 provider 拥有的运行时/配置钩子。它可以调用实时 vendor 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`、`voice`、`image_generation`、`video_generation` 和 `music_generation` 行。请将供应商端点调用和响应映射保留在插件中。OpenClaw 负责共享行结构、来源标签和帮助渲染。
这是一个可用的提供方。用户现在可以运行
`openclaw onboard --acme-ai-api-key <key>`,并选择
`acme-ai/acme-large` 作为其模型。
对于从已加载的认证存储中查找和选择提供方密钥,请从
`openclaw/plugin-sdk/provider-auth` 导入 `findNormalizedProviderValue` 和
`resolveAuthProfileOrder`。这样可以避免提供方入口仅为选择一个凭据而加载完整的 agent 运行时。已弃用的
`agent-runtime` 导出仍可用于兼容性。在新代码中请使用更窄的
`provider-auth` 路径。有关管理本页及其子页面中列出的已弃用接口的日期和门禁,请参阅[移除时间线](/plugins/sdk-migration/removal-timeline)。
捆绑的自定义 API 密钥方法可以在供应商提示或验证需要在认证步骤之间保留时,使用来自 `openclaw/plugin-sdk/provider-auth-api-key` 的 `captureProviderApiKey` 和
`persistProviderApiKey`。`captureProviderApiKey(ctx, options)` 接受现有的令牌/提供方、环境和提示选项。它会返回用于验证的已解析 `apiKey`,同时返回原始存储 `input` 和 `mode`,而不会保存凭据。请基于 `input` 和 `mode` 构建返回的配置档案,以便 SecretRefs 保持为引用。该辅助函数会保留上下文中暂存的工作区和 secret-storage 提示偏好。
`persistProviderApiKey(ctx, profileId, { provider, resolved, metadata })`
接受一个已解析的非交互式密钥。它会保持来自配置档案的凭据不变,如果凭据转换失败则返回 `false`,并传播持久化错误。请在此调用之前保留供应商检查;之后通过其现有所有者应用认证配置档案的配置和模型默认值。两个辅助函数都不会选择端点或模型。交互式认证方法可以使用 `ctx.existingProfiles` 与主机授权的 `{ profileId, credential }` 候选项重新连接。CLI 登录和引导会为所选提供方提供已存储的配置档案;`--profile-id` 将 CLI 登录缩小到该配置档案。个人账号流程仅提供当前用户所选的私有账号。列表缺失或为空表示没有可复用的账号。提供方方法选择兼容的候选项,并提供复用或新账号;它们不得加载共享凭据来填充列表。
自定义交互式认证方法如果铸造静态令牌或 API 密钥,可以在其返回的配置档案上请求受保护的持久化:typescript
return {
profiles: [
{
profileId: "acme-ai:device",
credential: { type: "token", provider: "acme-ai", token },
secretStorage: {
kind: "store",
namePrefix: "ACME_AI_TOKEN",
},
},
],
};
```
OpenClaw 仅在暂存验证运行期间保留内联值。在最终持久化边界,它会将值写入受保护的本地存储,并在认证配置档案中保存 tokenRef 或 keyRef。namePrefix 必须是大写的环境变量风格名称。OpenClaw 会添加一个由提供方和最终配置档案 id 派生的稳定后缀,以便多个配置档案保持独立。请仅将此用于提供方铸造的静态凭据,不要用于轮换的 OAuth 凭据或已作为 SecretRefs 提供的值。
对于实时 /models 发现、目录辅助函数、价格归一化以及更窄的单一提供方入口,请参阅提供方模型目录。
3. 添加动态模型解析
如果你的提供方接受任意模型 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 返回所请求的模型。OpenClaw 会应用与同步动态解析相同的已配置覆盖和归一化。现有返回空值的钩子仍会在准备后重试 resolveDynamicModel。
4. 添加运行时钩子(按需)
大多数提供方只需要 catalog + resolveDynamicModel。请根据你的提供方需求逐步添加钩子。
从提供方钩子族中的共享族构建器开始,然后使用提供方钩子接线连接各个钩子。
5. 添加额外功能(可选)
步骤 5:添加额外功能¶
提供方插件可以注册嵌入、语音、实时转录、实时语音、媒体理解、图像生成、视频生成、Web 获取和 Web 搜索,与文本推理并列。OpenClaw 将其分类为 hybrid-capability 插件——这是公司插件的推荐模式(每个供应商一个插件)。请参阅内部机制:功能所有权。
从提供方语音功能注册音频功能。从提供方媒体与搜索注册嵌入、生成、获取和搜索。
6. 测试
步骤 6:测试¶
```typescript src/provider.test.ts import { describe, it, expect } from "vitest"; // Export your provider config object from index.ts or a dedicated file 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 {#publish-to-clawhub}
提供商插件以与任何其他外部代码插件相同的方式发布:
```bash
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
clawhub skill publish <path> 是用于发布技能文件夹的不同命令,而不是插件包 - 请勿在此处使用它。
文件结构¶
<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)
目录顺序参考¶
catalog.order 控制你的目录相对于内置提供商合并的时机:
| 顺序 | 时机 | 用例 |
|---|---|---|
simple |
第一轮 | 普通 API 密钥提供商 |
profile |
在 simple 之后 | 基于身份验证配置文件控制的提供商 |
paired |
在 profile 之后 | 综合多个相关条目 |
late |
最后一轮 | 覆盖现有提供商(冲突时胜出) |
后续步骤¶
各章节迁移位置¶
单页版本的每个章节现在都位于此页面或以下五个子页面之一。单页版本中的锚点仍会在此处解析。
提供商模型目录¶
提供商模型目录 — 实时模型发现、目录辅助函数、价格规范化以及单提供商入口辅助函数。
提供商钩子族¶
提供商钩子族 — 共享的 replay、stream 和 tool-compat 族构建器,以及其背后的 SDK 接缝。
提供商钩子接线¶
提供商钩子接线 — 针对身份验证交换、请求头、传输身份、用量以及钩子顺序表的逐钩子接线。
提供商语音能力¶
提供商语音能力 — 语音、实时转录、实时语音以及媒体理解能力。
提供商媒体与搜索¶
提供商媒体与搜索 — 嵌入、图像和视频生成、网页抓取以及网页搜索能力。
相关¶
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw