跳转至

工具插件

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。 验证成功时输出:

Plugin stock-quotes is valid.

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、激活方式或工具 名称后,重新运行生成器:

npm run build
openclaw plugins build --entry ./dist/index.js

单工具插件的生成 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 中,安装包路径:

openclaw plugins install ./stock-quotes
openclaw plugins inspect stock-quotes --runtime

对于打包冒烟测试,请先执行 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 package publish ./stock-quotes --dry-run
clawhub package publish ./stock-quotes

使用显式的 ClawHub 定位符安装:

openclaw plugins install clawhub:your-org/stock-quotes

裸 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 生成的元数据已过期)

清单不再与入口元数据匹配。请运行:

npm run build
openclaw plugins build --entry ./dist/index.js

请同时提交 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 中,重新安装、重新构建并重新运行验证。

安装后工具未出现

按以下顺序检查:

  1. openclaw plugins inspect <plugin-id> --runtime
  2. openclaw plugins validate --root <plugin-root> --entry ./dist/index.js
  3. openclaw.plugin.json 中包含带有预期工具名称的 contracts.tools。
  4. package.json 中存在 openclaw.extensions: ["./dist/index.js"]。
  5. 安装报告已成功应用运行时;在编辑源代码或修复激活失败后,运行 openclaw plugins reload <plugin-id>。

另请参阅

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw