DeepSeek Harness(DSH)工具插件开发:用 inject tools 加 defineTool 注册模型可调用的工具,声明 parameters 与 output.schema,在 execute 里返回唯一 canonical 值并遵守 exec.signal,再用卡片呈现界面。
本文转自 DSH Plugin Hub 插件市场
DSH plugin 写工具插件的固定套路是三步:声明 inject: ['tools']、在 apply 里 ctx.tools.register(defineTool({ ... }))、在 defineTool 里分别声明 parameters(模型能传什么)、output.schema(execute 必须返回什么)与 execute(怎么执行)。 无论你叫它 DSH插件 还是 DeepSeek插件,这套契约完全一样。
DSH plugin 工具插件的最小形态
一个工具插件 = 声明依赖 + 注册工具定义,注册是 effect 式的,插件卸载即自动注销。 官方给出的最小写法如下(来源):
import {
readFile } from 'node:fs/promises'
import type {
Context } from '@deepseek-ai/cordis'
import {
defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'read_file',
description: 'Read a file from disk.',
parameters: {
path: {
type: 'string', required: true, description: 'Absolute path' },
limit: {
type: 'number' },
},
output: {
schema: {
type: 'string' },
render: (_args, value) => [{
type: 'text', text: value }],
},
async execute(args, exec) {
return readFile(args.path, {
encoding: 'utf8', signal: exec.signal })
},
}))
}
inject: ['tools'] 不能省——它让 Cordis 先备好工具注册表,apply 里才敢直接用 ctx.tools。依赖声明的完整机制见 怎么写插件。
DSH plugin 的 parameters 与 output:一个进、一个出
parameters 管入参、output.schema 管出参,两者职责不重叠。 官方对这条边界的表述是:defineTool 从 parameters 推导并校验 execute 的 args;execute 返回的是 output.schema 声明的那个 canonical 值,再由 output.render 转成模型可见内容(来源)。
- 参数自动校验:类型、必填键、字面量约束、精确其一联合、嵌套值都由框架在
execute之前验完——你不必再写类型判断,但 DSL 表达不了的约束(非空字符串、正数、跨字段规则)要自己查。 description是写给模型看的:它决定模型何时调用这个工具,不是注释。- 输出只声明一个根值:可以是对象、数组、标量或 null——按「这个值的诚实形态」来选,不要为了界面好看把文案塞进 schema。
DSH plugin 的 execute 契约:四条硬规则
execute 是一份契约,不是普通函数——四条规则越界就会出问题。
- 只返回一个 canonical 值。 不要返回内容块、不要让调用方从散文里解析 id 或字段;注册表会把它快照为无损 JSON、校验、冻结。
- 抛异常即
isError。 基础设施故障(文件不存在、网络断)直接 throw;业务上的非理想结果仍返回 canonical 值,例如「进程非零退出」应当是一个正常返回值,由渲染器解释,而不是异常。 - 遵守
exec.signal。 信号触发时取消在途工作——exec还携带不可变的执行身份与 token,args应视为只读输入。 - 注册后不要改自己的定义。 同进程的类型化贡献不是序列化边界,注册后不得改动 schema 或替换回调;要热换工具就处置其所属 effect 再注册新的。
想做权限或埋点,别写进工具本体。 官方建议用 tools/pre-execute(放行 / 拒绝 / 追问策略)、ctx.tools.guard()(不可撤销的最终拒绝)、tools/execute(环绕派发加超时、重试、指标)、tools/post-execute(替换展示内容或结果)与 tools/result(观察不可变结果)这些扩展点,让工具体保持纯粹。
DSH plugin 的长时任务与 UI 卡片
耗时操作走后台任务通道,界面呈现交给纯函数卡片——两者都不要污染 canonical 值。 需要用 run_in_background 时,经 ctx.jobs.start({ kind, label, owner: exec.agent, run }) 注册,成功分支返回类型化的句柄(如 { kind: 'background', jobId }),Code Mode 绝不能去解析那句人类可读的 started background job bash-1 来取 id(来源)。
界面卡片通过两个可选方法声明,返回带 card 标签的渲染意图:
| 方法 | 卡片 | 适用场景 |
|---|---|---|
presentCall(args) |
terminal |
你的调用本身就是一条 shell 命令 |
presentCall(args) |
diff |
你的调用会创建或修改文件 |
presentCall(args) |
generic |
默认卡片,可带 kind 图标与 locations 跳转 |
presentResult(args, result) |
同名卡片 | 完成态:终端输出、已应用的 diff、搜索结果等 |
两条会咬人的硬规则:① 这些函数在实时流与会话回放中都会执行,因此必须是 args(与结果)的纯函数——不许 I/O、不许读会话状态、不许看时钟或随机数;② 界面专用格式不要混进模型结果,output.render 管面向模型的文案,presentationMeta 加卡片呈现管界面状态。
工具没有任何界面呈现时,会回退到通用卡片(标题 = 工具名,输入 = 原始参数),不会崩。界面相关的更多呈现方式见 插件界面开发。
DSH plugin 工具插件的自检与下一步
注册前过一遍这五项:
inject里有没有tools;parameters与output.schema是否描述清楚,description是否面向模型;execute是否只返回 canonical 值、是否正确区分「抛异常」与「非理想结果」;- 是否兑现了
exec.signal; - 卡片呈现函数是否为纯函数。
需要可替换的实现时再拆包:把定义(Service Definition)、实现(Provider)、消费方(暴露成 tool)拆成三个包,详见 开发指南 的三角色设计;打包与发布见 打包成 bundle 与 发布到插件中心。插件装好后可在 DSH Plugin Hub 的已安装列表确认状态。
常见问题
DSH plugin 怎么写一个模型可以调用的工具?
DSH plugin 注册一个模型可调用的工具只需三步:① export const inject = ['tools'] 让框架先备好工具注册表;② 在 apply 里 ctx.tools.register(defineTool({ ... }));③ 在 defineTool 里声明 parameters、output.schema 与 execute。 注册是 effect 式的:插件 fiber 被处置时工具会自动注销(来源:官方「构建一个工具」)。
defineTool 的 parameters 和 output.schema 分别管什么?
在 DSH plugin 的 defineTool 里,parameters 管入参、output.schema 管出参,两者不要混用。 parameters 描述模型可以传什么参数,defineTool 会据此自动推导并校验 execute 的 args 类型;output.schema 描述 execute 必须返回的唯一 canonical 值,面向模型的文案交给 output.render 转换(来源:官方「工具编写参考」)。
DSH 工具插件的 execute 里能不能自己返回文本块?
不能——DSH plugin 的 execute 只返回 output.schema 声明的那个 canonical JSON 值,注册表会把它快照、校验、冻结后交给 output.render(args, value) 转成模型可见内容。官方明确要求不要在函数体里返回内容块、也不要让调用方去解析散文取 id 或字段(来源:官方「工具编写参考」)。
DSH 工具插件怎么处理错误和取消?
DSH plugin 工具处理错误有两条规则:基础设施故障直接 throw(注册表会捕获并标记 isError),业务上的非理想结果仍返回 canonical 值,交给渲染器解释(例如进程非零退出)。另外必须遵守 exec.signal,它触发时取消尚未完成的工作(来源:官方「工具编写参考」)。
DSH 工具插件在界面里的卡片怎么自定义?
DSH plugin 用 presentCall(args) 与 presentResult(args, result) 返回带 card 标签的渲染意图来定制卡片:终端命令用 terminal、文件改动用 diff、其余用 generic(可带 kind 图标与 locations 跳转)。这两个函数会在实时流与回放中执行,必须是纯函数——不读文件、不读会话状态、不看时钟,否则回放会崩(来源:官方「工具编写参考」)。
本文转自 DSH Plugin Hub 插件市场,版权归属 DSH Plugin Hub 插件市场。
分类:插件开发
原文地址:https://dsh-plugin.org/zh/tutorials/develop-tool-plugin