Skip to content

在插件中添加工具

Tool 会加入 Agent 可见工具集。每个插件拥有一个独立 Toolset,模型侧最终名称由插件 id 和 Tool name 共同限定。参数必须使用 Zod 4 对象 Schema;普通对象、JSON Schema、字符串或只模拟 toJSONSchema 的对象都会在插件激活时被拒绝。

传给 ctx.tools.register()name 是插件内的本地名。OpenDesk 会为每个插件创建名为 plugin-{pluginId} 的独立 Toolset,Toolset 内部和插件中心都保留本地名;Host 注册表与模型请求使用限定名以隔离不同插件。

以插件 greeting-tools 注册本地 Tool greet 为例:

位置名称
插件中心greet
Toolsetplugin-greeting-tools
Toolset 内部greet
Host 注册表、tool.definitiongreeting-tools-greet
模型请求、ToolCall、执行阶段 Hookplugin-greeting-tools-greet

不同插件可以注册相同本地名,因为它们位于不同 Toolset。插件 team-ateam-b 都注册 inspect 时,模型侧名称分别是 plugin-team-a-inspectplugin-team-b-inspect。同一插件中,两个 Tool 名经过规范化后相同会导致第二次注册失败,插件激活过程中已经创建的注册项会一起回滚。

进入模型名称的插件 id 和 Tool 名只保留字母、数字、_-;其他连续字符替换为 -,首尾分隔符会移除。因此不要依赖空格、点或斜杠区分两个 Tool 名。

字段类型必填说明
namestringTool 名称,不能为空
descriptionstring给模型读取的功能说明,不能为空
parametersZod object schema直接注册时使用;必须同时支持 safeParse 和 toJSONSchema,且生成 object JSON Schema
agentIdsreadonly string[]仅向指定 Agent 暴露
serialExecutionboolean为 true 时要求串行执行,默认为 false
getDeclaredPermissionsfunction根据参数声明本次调用需要的权限
executeasync function执行 Tool
stopfunction宿主要求停止 Tool 时调用
renderobjectCLI 和 GUI 自定义渲染器

使用 SDK 的 tool 辅助函数时传入 args,而不是 parameterstool 会执行 parameters: z.object(args) 的转换。直接调用 ctx.tools.register 时始终传 parameters,不能传 args

execute(args, context, result) 接收:

参数字段说明
argsSchema 解析结果已通过 parameters 校验的工具参数
contexttaskId当前任务 id,某些无任务场景可能为空
contextworkspace当前工作空间,可能为空
contextabortAbortSignal,用于响应任务停止
resultattachResult(chunk)追加过程输出
resultupdateResult(text)替换文本结果
resultupdateResultObject(object)更新结构化结果

execute 可以返回 undefinedstring,或形如 output + resultObject 的对象。返回 string 会成为最终文本;对象中的 output 是最终文本,resultObject 是可选结构化结果。

getDeclaredPermissions 返回 PermissionRequest 数组。每一项包含:

字段可选值说明
resourceTypefile、network、skill、plugin、*受保护资源类型
actionread、write、execute、access、*操作
resourcePathstring实际路径、URL、Skill 名或插件 id,不要用笼统占位值

render.gui 接收 ToolRenderContext,返回 textcodekeyValue 内容;render.cli 额外接收 width,返回 string[]。渲染失败只会回退到默认显示,不会改变 Tool 执行结果。

ToolRenderContext 字段:

字段类型说明
toolstring模型侧最终 Tool 名;插件 Tool 包含 plugin-{pluginId}- 前缀
callIdstring本次调用 id
statuspending、running、success、error调用状态
argsunknown调用参数
resultstring可选文本结果
resultObjectobject可选结构化结果
widthnumber仅 CLI 渲染器可用的终端宽度

GUI 返回值:

{ type: 'text', text: 'plain text' }
{ type: 'code', code: '{"ok":true}', language: 'json' }
{ type: 'keyValue', items: [{ label: 'Status', value: 'Ready' }] }
import { definePlugin, tool } from '@bitclub.ai/opendesk-plugin-sdk';
export default definePlugin({
id: 'greeting-tools',
capabilities: ['tool'],
setup(ctx) {
return ctx.tools.register(tool({
name: 'greet',
description: 'Generate a greeting for one person',
args: {
name: tool.schema.string().min(1).describe('Person to greet')
},
getDeclaredPermissions(args, context) {
return [];
},
async execute(args, context, result) {
result.attachResult('Preparing greeting');
if (context.abort.aborted) {
return { output: 'Cancelled' };
}
return {
output: 'Hello, ' + args.name + '!',
resultObject: { greeted: args.name }
};
},
render: {
gui({ status, resultObject }) {
return {
type: 'keyValue',
items: [
{ label: 'Status', value: status },
{ label: 'Name', value: String(resultObject?.greeted || '') }
]
};
},
cli({ status, result, width }) {
return ['[' + status + '] ' + (result || '') + ' (width=' + width + ')'];
}
}
}));
}
});

无 SDK Tool 仍需 Zod 4。zod 是宿主映射依赖:插件自带版本时优先使用自己的版本,否则回退到 OpenDesk 内置版本;通过 ZIP/TGZ 安装时不会为它单独创建插件本地副本。

{
"name": "greeting-tools",
"version": "0.1.0",
"type": "module",
"dependencies": {
"zod": "^4.2.1"
}
}
import { z } from 'zod';
export default {
id: 'greeting-tools',
capabilities: ['tool'],
setup(ctx) {
return ctx.tools.register({
name: 'greet',
description: 'Generate a greeting for one person',
parameters: z.object({
name: z.string().min(1).describe('Person to greet')
}),
getDeclaredPermissions(args, context) {
return [];
},
async execute(args, context, result) {
result.attachResult('Preparing greeting');
if (context.abort.aborted) {
return { output: 'Cancelled' };
}
return {
output: 'Hello, ' + args.name + '!',
resultObject: { greeted: args.name }
};
},
render: {
gui({ status, resultObject }) {
return {
type: 'keyValue',
items: [
{ label: 'Status', value: status },
{ label: 'Name', value: String(resultObject?.greeted || '') }
]
};
},
cli({ status, result, width }) {
return ['[' + status + '] ' + (result || '') + ' (width=' + width + ')'];
}
}
});
}
};