工具插件
defineToolPlugin 构建一个仅添加可由 agent 调用的工具的插件:没有通道、模型提供商、钩子、服务或设置后端。它生成 OpenClaw 发现工具所需的清单元数据,而无需加载插件运行时代码。
对于提供商、通道、钩子、服务或混合能力插件,请改用 构建插件、通道插件 或 提供商插件 开始。
需求¶
- Node 24.16+ 或 Node 26.1+。
- TypeScript ESM 包输出。
dependencies中的typebox(不仅仅是devDependencies—— 生成的插件会在运行时导入它)。openclaw >=2026.5.17,首个导出openclaw/plugin-sdk/tool-plugin的版本。- 一个包含
dist/、openclaw.plugin.json和package.json的包根目录。
快速开始¶
openclaw plugins init stock-quotes --name "Stock Quotes"
cd stock-quotes
npm install
npm run plugin:build
npm run plugin:validate
npm test
plugins init 生成脚手架:
| 文件 | 用途 |
|---|---|
src/index.ts |
包含一个 echo 工具的 defineToolPlugin 入口 |
src/index.test.ts |
断言工具列表的元数据测试 |
tsconfig.json |
输出到 dist/ 的 NodeNext TypeScript 配置 |
vitest.config.ts |
用于 src/**/*.test.ts 的 Vitest 配置 |
package.json |
脚本、运行时依赖、openclaw.extensions: ["./dist/index.js"] |
openclaw.plugin.json |
初始工具的生成清单元数据 |
npm run plugin:build 会运行 npm run build(tsc),然后运行
openclaw plugins build --entry ./dist/index.js。npm run plugin:validate
会重新构建并运行 openclaw plugins validate --entry ./dist/index.js。
验证成功时输出:
openclaw plugins init <id> 选项:
| 标志 | 默认值 | 效果 |
|---|---|---|
--directory <path> |
<id> |
输出目录 |
--name <name> |
标题大小写的 <id> |
显示名称 |
--type <type> |
tool |
脚手架类型:tool 或 provider |
--force |
off | 覆盖已存在的输出目录 |
编写工具¶
defineToolPlugin 接受插件标识、可选的配置模式以及一个静态工具列表。参数和配置类型会从 TypeBox 模式中推断。
import { Type } from "typebox";
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
export default defineToolPlugin({
id: "stock-quotes",
name: "Stock Quotes",
description: "Fetch stock quote snapshots.",
configSchema: Type.Object({
apiKey: Type.Optional(Type.String({ description: "Quote API key." })),
baseUrl: Type.Optional(Type.String({ description: "Quote API base URL." })),
}),
tools: (tool) => [
tool({
name: "stock_quote",
label: "Stock Quote",
description: "Fetch a stock quote snapshot.",
parameters: Type.Object({
symbol: Type.String({ description: "Ticker symbol, for example OPEN." }),
}),
outputSchema: Type.Object(
{
symbol: Type.String(),
configured: Type.Boolean(),
baseUrl: Type.String(),
},
{ additionalProperties: false },
),
async execute({ symbol }, config, context) {
context.signal?.throwIfAborted();
return {
symbol: symbol.toUpperCase(),
configured: Boolean(config.apiKey),
baseUrl: config.baseUrl ?? "https://api.example.com",
};
},
}),
],
});
工具名称是稳定的 API。请选择唯一、小写且足够具体的名称,以避免与核心工具或其他插件发生冲突。
可选工具和工厂工具¶
当用户应在工具发送给模型之前显式将其加入允许列表时,设置 optional: true。openclaw plugins build 会写入对应的 toolMetadata.<tool>.optional 清单条目,使 OpenClaw 无需加载插件运行时代码即可看到该工具是可选的。
tool({
name: "workflow_run",
description: "Run an external workflow.",
parameters: Type.Object({ goal: Type.String() }),
optional: true,
execute: ({ goal }) => ({ queued: true, goal }),
});
当工具在创建前需要运行时工具上下文时,使用 factory —— 例如针对特定运行选择退出、检查沙箱状态或绑定运行时辅助函数。即使具体工具在运行时构建,元数据仍保持静态。
tool({
name: "local_workflow",
description: "Run a local workflow outside sandboxed sessions.",
parameters: Type.Object({ goal: Type.String() }),
optional: true,
factory({ api, toolContext }) {
if (toolContext.sandboxed) {
return null;
}
return createLocalWorkflowTool(api);
},
});
工厂可以使用 toolContext.delivery?.send({ text, mediaUrl }) 在活跃会话中发送出站消息。宿主选择目标、账户、线程和本地媒体策略;插件无法重新指定此辅助函数的目标,且保留的副本在回合关闭后停止工作。对于由 Gateway 传输拥有投递的通道,此辅助函数不可用。
工厂可以返回一个核心 AgentTool、它们的数组,或返回 null 或 undefined 以选择退出,如上例所示。当它返回一个具体工具时,该工具使用核心运行时签名 execute(toolCallId, params, signal?, onUpdate?),工具调用 ID 位于第一位。这与上面所示的声明式 execute(params, config, context) 的参数顺序相反,并且与 构建插件 中 api.registerTool 的示例一致。从工厂工具的第一个参数读取 params 会返回工具调用 ID 字符串。
具体工具可以提供 prepareArguments(args),用于在架构验证之前规范化输入。当工具调用必须逐个执行时,原生代理循环也会遵循 executionMode: "sequential"。每当组装工具时,这些运行时属性、架构和显示元数据都来自当前工厂上下文。参数准备和执行使用同一实例。保留的工具在其所属插件注册表退役后停止工作。
所有者授权的延续¶
要在显式 sessions_yield 之后精确父级恢复时参与,请通过 api.registerTool 注册一个 OpenClawPluginToolFactory<2> 描述符:
api.registerTool(
{
contextVersion: 2,
create(context) {
if (context.senderIsOwner !== true) return null;
return createPrivilegedTool({ assertCurrent: context.assertInvocationCurrent });
},
},
{ name: "my_privileged_tool" },
);
OpenClawPluginToolContext<2> 类型要求 assertInvocationCurrent。请将其贯穿异步等待的工作,并在最终同步写入或请求守卫中调用它,位于副作用之前——而不仅仅是在开始工作之前或返回之后。它会检查捕获的插件生命周期以及已准入的运行/工作线程权限;延续还会检查原始所有者的实时精确父级绑定。独立的 HTTP/RPC 调用使用其经过身份验证的请求生命周期,而 MCP 工具保留现有的经过身份验证的授权或回环运行时生命周期。仅元数据的目录构建不会授予调用权限。一个没有已准入调用的保留版本化工具,在其守卫被调用时会失败。
客户端输入工具也可能接收 assertInputCommitAllowed。在持久化客户端提供的字节时,请将这个主机绑定的同步策略回调带到存储所有者的最终准入守卫。调用它会检查当前上传策略,即使对于 Gateway 无法分类的自定义工具名称也是如此。不要对不上传字节的纯文本操作调用它。它不执行数据库读取,因此可以在由工作线程支持的写入准入中运行。它不会替代调用权限或变更权限。请在异步准备过程中保留它,但不要将其应用于已接受的结果或补偿性清理。代理生成的输入不需要此客户端上传策略回调。
旧版函数和静态工具注册仍受支持,并保留其现有的直接轮次上下文;此更改不引入移除日期或缩短的兼容性窗口。它们不会获得持续的所有者身份。仅选择加入不会授予任何权限:仅管理调用者、无关会话和分离的 cron 运行仍然无法获取所有者的身份。senderIsOwner 是一个可用性检查,绝不能替代所需的最终效果守卫。
在具体工厂工具上设置 hideFromChannelProgress: true,以将其瞬时活动排除在频道进度草稿之外。生命周期事件和最终工具结果仍会正常流转。OpenClaw 在规范化其架构时会保留当前工厂的标志;省略或 false 会保持正常的进度行为。参见 进度草稿。
工厂仍需在开头声明固定的工具名称。当插件动态计算工具名称,或将工具与钩子、服务、提供者或命令组合时,请直接使用 definePluginEntry。
返回值¶
defineToolPlugin 会将普通返回值包装为 OpenClaw 工具结果格式:
- 当模型应看到该确切文本时,返回字符串。
- 当你希望模型看到格式化 JSON,并希望 OpenClaw 在
details中保留原始值时,返回 JSON 兼容值。
tool({
name: "echo_text",
description: "Echo input text.",
parameters: Type.Object({
input: Type.String(),
}),
execute: ({ input }) => input,
});
tool({
name: "echo_json",
description: "Echo input as structured JSON.",
parameters: Type.Object({
input: Type.String(),
}),
execute: ({ input }) => ({ input, length: input.length }),
});
当你需要自定义 AgentToolResult 或希望复用现有 api.registerTool 实现时,请使用工厂工具。
输出契约¶
当工具返回稳定的 JSON 兼容数据时,添加 outputSchema。它描述存储在 AgentToolResult.details 中的原始值,而不是 content 中的格式化文本:
tool({
name: "shipment_list",
description: "List shipments.",
parameters: Type.Object({
buyer: Type.Optional(Type.String()),
}),
outputSchema: Type.Array(
Type.Object(
{
id: Type.String(),
buyer: Type.String(),
paid: Type.Boolean(),
tons: Type.Number(),
},
{ additionalProperties: false },
),
),
execute: ({ buyer }) => listShipments(buyer),
});
Code Mode 和 Tool Search 会将此架构转换为有界的 TypeScript 风格输出提示。这样可以让模型在一个程序中调用并转换已知结果,而不是花费另一个模型轮次来观察其形状。
OpenClaw 会在执行目录调用之前编译架构,然后在工具钩子之后验证最终的 details 值,再通过桥接返回它。无效架构无法运行工具;结果不匹配会使已完成的调用失败。请包含所有非抛出结果变体,包括结构化错误变体;如果结果不稳定,则省略架构。不要在架构描述中放入密钥或敏感值,因为受信任的输出元数据可能变为模型可见。
当你希望获得完整紧凑的输出提示时,请在对象层使用 { additionalProperties: false };开放式或截断的架构仍可通过可调用目录句柄的 describe() 获取,但不会被声明为完整的快速索引契约。
工厂工具在其返回的具体 AnyAgentTool 上声明 outputSchema。静态 tool({ factory }) 声明不接受单独的输出架构,因为它可能与运行时工具产生偏差。
OpenClaw 还会根据 details 评估调用结果,因此 status、ok、
success、error、timedOut 和 exitCode 是保留名称。如果 status
为 blocked、denied、invalid、cancelled 或其他任何失败值,
除非 ok 或 success 显式为 true,否则该调用会被标记为失败,即使
execute 正常返回也是如此。使用这些名称之一的领域数据应放在包装键下,例如 { card },
而不是放在 details 的顶层。
对于工具自有的超时,请在 details 中返回 timedOut: true 和一个正整数
timeoutMs。如果代理未提供最终回复,OpenClaw 会将该时长包含在回退警告中,
而不暴露原始错误文本。当有可用的部分结果时,返回 partial: true 和一个非空
results 数组;警告会包含其数量。这些诊断信息不会将不完整的操作变成成功的调用。
配置¶
configSchema 是可选的。省略它时,OpenClaw 会应用严格的空对象
schema;生成的 manifest 仍会包含 configSchema。
export default defineToolPlugin({
id: "no-config-tools",
name: "No Config Tools",
description: "Adds tools that do not need configuration.",
tools: () => [],
});
如果提供了 configSchema,第二个 execute 参数会基于它进行类型标注:
const configSchema = Type.Object({
apiKey: Type.String(),
});
export default defineToolPlugin({
id: "configured-tools",
name: "Configured Tools",
description: "Adds configured tools.",
configSchema,
tools: (tool) => [
tool({
name: "configured_ping",
description: "Check whether configuration is available.",
parameters: Type.Object({}),
execute: (_params, config) => ({ hasKey: config.apiKey.length > 0 }),
}),
],
});
OpenClaw 从 Gateway 配置中的插件条目读取插件配置。 不要在源代码或文档示例中硬编码密钥;请根据插件的安全模型使用配置、环境变量或 SecretRefs。
生成的元数据¶
OpenClaw 必须在导入插件运行时代码之前读取插件 manifest。
defineToolPlugin 会为此暴露静态元数据,并且
openclaw plugins build 会将其写入包中。在更改插件 id、名称、描述、配置 schema、激活方式或工具
名称后,重新运行生成器:
单工具插件的生成 manifest:
{
"id": "stock-quotes",
"name": "Stock Quotes",
"description": "Fetch stock quote snapshots.",
"version": "0.1.0",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
},
"activation": {
"onStartup": true
},
"contracts": {
"tools": ["stock_quote"]
}
}
contracts.tools 是重要的发现契约:它告诉 OpenClaw 哪个
插件拥有每个工具,而无需加载每个已安装插件的运行时。过时的
manifest 会导致工具从发现中缺失,或者注册
错误被归咎于错误的插件。
包元数据¶
openclaw plugins build 还会将 package.json 与所选运行时
条目对齐:
{
"type": "module",
"files": ["dist", "openclaw.plugin.json", "README.md"],
"dependencies": {
"typebox": "^1.1.38"
},
"peerDependencies": {
"openclaw": ">=2026.5.17"
},
"openclaw": {
"extensions": ["./dist/index.js"]
}
}
发布构建后的 JavaScript(./dist/index.js),而不是 TypeScript 源条目。
源条目仅适用于工作区本地开发。
在 CI 中验证¶
当生成的元数据过期时,plugins build --check 会失败且不会重写文件:
npm run build
openclaw plugins build --entry ./dist/index.js --check
openclaw plugins validate --entry ./dist/index.js
npm test
OpenClaw SDK 兼容性字段带有 TypeScript @deprecated 注解,
编辑器会将其显示为迁移警告。要在 CI 中强制执行这些注解,请启用
类型感知规则,例如
@typescript-eslint/no-deprecated。
Oxlint 不是类型感知的,因此无法强制执行这些注解。因此,生成的
plugins init 脚手架不会添加弃用 lint 配置。
plugins validate 会检查以下内容:
openclaw.plugin.json存在并通过常规 manifest 加载器。- 当前条目导出了
defineToolPlugin元数据。 - 生成的 manifest 字段与条目元数据匹配。
contracts.tools与声明的工具名称匹配。package.json将openclaw.extensions指向所选运行时条目。
在本地安装和检查¶
从单独的 OpenClaw checkout 或已安装的 CLI 中,安装包路径:
对于打包冒烟测试,请先执行 pack 并安装 tarball:
npm pack
openclaw plugins install npm-pack:./openclaw-plugin-stock-quotes-0.1.0.tgz
openclaw plugins inspect stock-quotes --runtime --json
安装会自动应用于正在运行的本地 Gateway;如果 Gateway 已停止,请启动它。让代理使用该工具。如果工具不可见,请在更改代码之前检查插件运行时和生效的 工具目录(参见 故障排查)。 在后续源代码或 manifest 编辑后,使用 插件重新加载。
发布¶
包就绪后,通过 ClawHub 发布。clawhub package publish
接受一个源:本地文件夹、GitHub 仓库(owner/repo[@ref])或
tarball URL。
使用显式的 ClawHub 定位符安装:
裸 npm 包规范会从 npm 安装,但 ClawHub 是 OpenClaw 插件首选的 发现和分发界面。有关所有者范围和 发布审核,请参阅 ClawHub 发布。
故障排查¶
plugin entry not found: ./dist/index.js¶
所选入口文件不存在。请运行 npm run build,然后重新运行
openclaw plugins build --entry ./dist/index.js 或
openclaw plugins validate --entry ./dist/index.js。
plugin entry does not expose defineToolPlugin metadata(插件入口未暴露 defineToolPlugin 元数据)¶
该入口未导出由 defineToolPlugin 创建的值。请确认模块的默认导出是 defineToolPlugin(...) 的结果,或使用 --entry 传入正确的入口。
openclaw.plugin.json generated metadata is stale(openclaw.plugin.json 生成的元数据已过期)¶
清单不再与入口元数据匹配。请运行:
请同时提交 openclaw.plugin.json 和 package.json 的更改。
package.json openclaw.extensions must include ./dist/index.js(package.json 的 openclaw.extensions 必须包含 ./dist/index.js)¶
包元数据指向了另一个运行时入口。请运行
openclaw plugins build --entry ./dist/index.js,以便生成器将包元数据与你打算发布的入口保持一致。
Cannot find package 'typebox'(找不到包 'typebox')¶
构建后的插件在运行时导入了 typebox。请将其保留在 dependencies 中,重新安装、重新构建并重新运行验证。
安装后工具未出现¶
按以下顺序检查:
openclaw plugins inspect <plugin-id> --runtimeopenclaw plugins validate --root <plugin-root> --entry ./dist/index.jsopenclaw.plugin.json中包含带有预期工具名称的contracts.tools。package.json中存在openclaw.extensions: ["./dist/index.js"]。- 安装报告已成功应用运行时;在编辑源代码或修复激活失败后,运行
openclaw plugins reload <plugin-id>。
另请参阅¶
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw