猫哥写了三个 dsh 插件(会话导出、通知、报表)之后,发现也挺简单哈,看来想做贡献也没那么难。有个问题一直绕不开:插件(plugin)和工具(tool)到底啥关系? 官方文档里一会儿说"一切皆插件",一会儿又冒出个"工具编写参考",初看挺懵的。
这篇文章猫哥用大白话 + 最小 demo 把这件事讲清楚。看完你会发现:它俩根本不是并列概念------工具只是插件能注册的"内容"之一。
------ 猫哥,一个热爱分享AI技术和爱折腾的人。更多折腾记录见我的博客:blog.csdn.net/qq8864
一句话结论
- 插件 = 一个
apply(ctx)模块,是框架的安装/生命周期单元。它决定"什么时候加载、依赖什么、卸载时清理什么"。 - 工具 = 插件通过
ctx.tools.register(...)注册的能力 ,是模型可见、可调用的函数。它决定"agent 能做什么"。
工具是插件注册的内容之一,不是和插件并列的东西。

打个比方:插件是安装的应用程序 ,工具是应用暴露的函数/API 端点。一个应用可以暴露 0 个、1 个或多个 API------对应一个插件可以注册 0 个、1 个或多个工具。
插件是什么?最小 demo 长这样
dsh 的"一切皆插件"指的是:模型适配器、工具、文件访问、agent 循环本身,全都是挂到共享上下文(ctx)上的插件。一个插件就是一个导出 apply 函数的模块:
ts
// hello-plugin.ts ------ 插件的最小形态
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin' // 显示名(可选)
export const inject = ['tools'] // 依赖就绪后才执行 apply(可选)
export function apply(ctx: Context) {
// 在这里注册这个插件贡献的一切:
// ctx.tools.register(...) 注册工具
// ctx.on('session/event', ...) 订阅事件
// ctx.effect(() => cleanup) 声明卸载清理
console.log('[hello-plugin] loaded!')
}
插件有完整的生命周期 (Fiber 状态机:PENDING → LOADING → ACTIVE/FAILED → UNLOADING → DISPOSED),声明了 inject 会等依赖服务就绪;通过 ctx 注册的任何东西在插件卸载时自动撤销(不用手动 removeListener)。
工具是什么?最小 demo 长这样
工具是"给模型用的函数"。核心约定:description 和 parameters 是给模型看的 (决定模型什么时候调、传什么参),execute 是真正干活的:
ts
// my-tool.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' }, // 规范返回值的 schema
render: (_args, value) => [{ type: 'text', text: value }], // ← 模型看到的结果
},
async execute(args, exec) {
// args 已被 schema 校验且只读:{ path: string; limit?: number }
return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
},
}))
}
注意几个点:
- schema 是你的桥梁 :
parameters校验模型生成的参数(类型、必填、联合分支),校验通过的args才进execute;schema 还会自动流入系统提示词的组装,模型"知道"有这个工具、怎么用。 - execute 的约定 :
args只读;exec.signal是取消信号(信号触发要取消进行中的工作);抛异常或返回无效值 =isError;成功要返回规范 JSON 值(别把 UI 格式塞进返回值)。 - 展示两分 :
output.render给模型生成自然语言结果;presentCall/presentResult给 UI 画卡片(terminal/diff/search/web/generic)。展示方法是纯函数------会话日志回放时也会运行,不能做 I/O、不能读状态。
一次工具调用,背后发生了什么

模型(根据 description/parameters 生成调用)
→ tools 注册表(schema 校验 → args 冻结为只读 + exec.token → 权限策略 pre-execute/guard)
→ execute(args, exec)(真实干活,遵守 signal 取消)
→ 规范 JSON 值(output.schema 校验、冻结、presentationMeta 投影)
├── output.render(args, value) → 模型可见的自然语言
└── presentResult(args, result) → UI 卡片(terminal/diff/search/web)
附带两个很爽的能力:
- Code Mode 自动触达 :每个已注册工具在 Code Mode 里都能直接
await tools.read_file({ path, limit })调用,参数/返回类型从同一套 schema 自动派生------写完工具,程序化 API 也自动有了。 - 长时间任务 :声明
run_in_background后用ctx.jobs.start(...)注册后台任务,返回类型化的{ kind: 'background', jobId }句柄。
那"观察型插件"呢?------我们做的三个就是
工具型插件给 agent 加能力 ;还有一类插件不给模型加任何能力,只在后台被动观察会话。我们之前写的三个插件全是这种:
ts
// dsh-session-export / dsh-notify / dsh-session-report 的骨架
export const inject = ['sessions']
export function apply(ctx: Context) {
ctx.on('session/event', (session, event) => {
if (event.type !== 'turn/end') return
// 防抖 → 导出 Markdown / 推送 webhook / 生成报表
})
}
它们不注册工具,但同样是"插件"------通过事件订阅在 turn 结束后自动干活。所以:
| 工具型插件 | 观察型插件 | |
|---|---|---|
| 给模型加能力? | ✅ 是 | ❌ 否 |
| 核心注册 | ctx.tools.register(defineTool(...)) |
ctx.on('session/event', ...) |
| 例子 | tool-bash、文件读写、web 搜索 | 我们的导出/通知/报表 |
两种形态也可以混在一个插件里:既注册工具,又订阅事件。
什么时候写哪种?
- 想让 agent 会做新的事 (查数据库、操作浏览器、调用内部 API)→ 写工具型 插件:
defineTool({ name, description, parameters, execute })四件套。 - 想让 dsh 在后台自动做事 (复盘、通知、记账、备份、监控)→ 写观察型 插件:订阅
session/event+ 防抖 + dispose flush 三件套(我们三篇文章就是这个骨架)。 - 不确定?先想清楚:这个能力是"模型主动调"还是"系统自动做"。模型调 → 工具;系统做 → 观察型插件。
关于作者
本文作者 猫哥,DeepSeek Harness 插件开发者,日常折腾 AI Agent、开源与效率工具。写过并开源了三个 dsh 插件:
- dsh-session-export------会话导出与复盘
- dsh-notify------任务完成通知(Server酱/钉钉/飞书/Webhook)
- dsh-session-report------成本与耗时报表
更多 AI/开发折腾记录,欢迎来我的博客坐坐:https://blog.csdn.net/qq8864。觉得有用的话点个关注、留个言,后续还会持续输出 dsh 插件生态的实战内容。