DSH plugin 怎么写工具插件:defineTool 参数与输出声明、execute 契约与 UI 卡片渲染

插件开发发布于 2026-09-12作者: DeepSeek Plugin 插件市场
DSH pluginDeepSeek Harness工具插件defineTooltool
DeepSeek Harness(DSH)工具插件开发:用 inject tools 加 defineTool 注册模型可调用的工具,声明 parameters 与 output.schema,在 execute 里返回唯一 canonical 值并遵守 exec.signal,再用卡片呈现界面。

DSH plugin 写工具插件的固定套路是三步:声明 inject: ['tools']、在 applyctx.tools.register(defineTool({ ... }))、在 defineTool 里分别声明 parameters(模型能传什么)、output.schema(execute 必须返回什么)与 execute(怎么执行)。 无论你叫它 DSH插件 还是 DeepSeek插件,这套契约完全一样。

DSH plugin 工具插件的最小形态

一个工具插件 = 声明依赖 + 注册工具定义,注册是 effect 式的,插件卸载即自动注销。 官方给出的最小写法如下(来源):

ts
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 管出参,两者职责不重叠。 官方对这条边界的表述是:defineToolparameters 推导并校验 executeargsexecute 返回的是 output.schema 声明的那个 canonical 值,再由 output.render 转成模型可见内容(来源)。

  • 参数自动校验:类型、必填键、字面量约束、精确其一联合、嵌套值都由框架在 execute 之前验完——你不必再写类型判断,但 DSL 表达不了的约束(非空字符串、正数、跨字段规则)要自己查。
  • description 是写给模型看的:它决定模型何时调用这个工具,不是注释。
  • 输出只声明一个根值:可以是对象、数组、标量或 null——按「这个值的诚实形态」来选,不要为了界面好看把文案塞进 schema。

DSH plugin 的 execute 契约:四条硬规则

execute 是一份契约,不是普通函数——四条规则越界就会出问题。

  1. 只返回一个 canonical 值。 不要返回内容块、不要让调用方从散文里解析 id 或字段;注册表会把它快照为无损 JSON、校验、冻结。
  2. 抛异常即 isError 基础设施故障(文件不存在、网络断)直接 throw;业务上的非理想结果仍返回 canonical 值,例如「进程非零退出」应当是一个正常返回值,由渲染器解释,而不是异常。
  3. 遵守 exec.signal 信号触发时取消在途工作——exec 还携带不可变的执行身份与 token,args 应视为只读输入。
  4. 注册后不要改自己的定义。 同进程的类型化贡献不是序列化边界,注册后不得改动 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 工具插件的自检与下一步

注册前过一遍这五项

  1. inject 里有没有 tools
  2. parametersoutput.schema 是否描述清楚,description 是否面向模型;
  3. execute 是否只返回 canonical 值、是否正确区分「抛异常」与「非理想结果」;
  4. 是否兑现了 exec.signal
  5. 卡片呈现函数是否为纯函数。

需要可替换的实现时再拆包:把定义(Service Definition)、实现(Provider)、消费方(暴露成 tool)拆成三个包,详见 开发指南 的三角色设计;打包与发布见 打包成 bundle发布到插件中心。插件装好后可在 DSH Plugin Hub 的已安装列表确认状态。

常见问题

DSH plugin 怎么写一个模型可以调用的工具?

**DSH plugin 注册一个模型可调用的工具只需三步:① export const inject = ['tools'] 让框架先备好工具注册表;② 在 applyctx.tools.register(defineTool({ ... }));③ 在 defineTool 里声明 parametersoutput.schemaexecute。** 注册是 effect 式的:插件 fiber 被处置时工具会自动注销(来源:官方「构建一个工具」)。

defineTool 的 parameters 和 output.schema 分别管什么?

**在 DSH plugin 的 defineTool 里,parameters 管入参、output.schema 管出参,两者不要混用。** parameters 描述模型可以传什么参数,defineTool 会据此自动推导并校验 executeargs 类型;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 跳转)。这两个函数会在实时流与回放中执行,必须是纯函数——不读文件、不读会话状态、不看时钟,否则回放会崩(来源:官方「工具编写参考」)。

相关术语

defineTool
defineTool 是 DSH plugin 声明模型可调用工具的辅助函数,接收 name、description、parameters、output、execute 等字段,返回可直接交给 ctx.tools.register 的工具定义,并会从 parameters 推导 execute 的入参类型。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-tool.md
canonical value(规范值)
canonical value 是 DSH plugin 工具 execute 按 output.schema 返回的唯一结构化结果,注册表会把它快照为无损 JSON、校验并冻结,再交给 output.render 生成面向模型的内容。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-tool.md
exec.signal
exec.signal 是 DSH plugin 工具执行上下文里的取消信号,必须在触发时终止进行中的工作;它是被保护的执行身份之一,调用方与包装器都不得移除。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-tool.md
presentCall / presentResult
presentCall 与 presentResult 是 DSH plugin 工具的可选界面投影方法,分别返回调用中与完成后的卡片渲染意图(generic / terminal / diff / search / web),供宿主渲染;它们必须是对 args 与结果的纯函数。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-tool.md

来源