插件开发起步
本教程共 32 篇 · 第 20 篇 · 更新于 2026-08-15 · 约 6 分钟阅读
本节目标:理解插件的本质与最小结构,写出第一个 hello 插件,并搞清 dsh 按什么顺序加载插件。
一切皆插件
DeepSeek Harness 的标语是 Everything is a Plugin(一切皆插件)。这不是宣传口号。默认 web profile 会挂载 133 个插件(hello-dsh 教程实测,以你本机插件列表为准)。模型适配器(llm)、会话历史(session)、你正在看的网页(webserver)、左侧边栏(ui-sidebar),甚至 agent 的主循环(agent-loop)——全部是插件。
这意味着框架没有「特权核心」。你想让 Agent 多一个能力,就挂一个插件;想换掉某个实现,就换一个插件。卸载时,插件注册的一切自动回退。这套模型来自底层元框架 Cordis(4.0.0-rc.8)。从本章起,我们开始用代码接触它。
最小插件:一个文件
插件是一个导出 apply 函数的 TypeScript 模块。apply 是插件的入口——框架加载插件时调用它,并传入 ctx(上下文对象)。
// hello.ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello' // 插件名,用于日志与诊断
export function apply(ctx: Context) {
console.log('hello from my first plugin')
}
name 是可选的显示元数据,只用于标识插件。apply 是唯一必须的东西。这段代码没有注册任何能力,但已经是一个合格的插件。
apply 与 ctx
ctx 是框架传给每个插件的上下文对象。它有两个身份:
- 注册入口:
ctx.on()监听事件、ctx.tools.register()注册工具,都通过它完成。 - 注册记录本:通过
ctx注册的一切资源都记在插件的 Fiber 作用域里,插件卸载时自动清理。
新手容易把 ctx 当成全局对象。它其实绑定当前插件实例的生命周期——插件没了,注册的东西跟着没了。
三种插件形态
函数形态最常见,但 Cordis 还接受对象形态和类形态:
import { Service, type Context } from '@deepseek-ai/cordis'
// 1. 函数形态:直接导出 apply
export function apply(ctx: Context) {}
// 2. 对象形态:一个带 apply 方法的对象
export const objectPlugin = {
name: 'object-plugin',
apply(ctx: Context) {},
}
// 3. 类形态:Service 子类,用于对外提供服务
export class MyService extends Service {
constructor(ctx: Context) {
super(ctx, 'myService')
}
}
选择规则很简单:大多数场景用函数形态;需要向其他插件提供服务时,用类形态(第 22 章展开)。
加载插件:—patch 覆盖层
写好的插件怎么进应用?开发期最常用 --patch。建一个本地项目目录:
scratch-plugin/
├── src/
│ └── my-plugin.ts # 插件源码
└── cordis.yml # patch 覆盖层
cordis.yml 告诉 dsh 插入哪个插件:
- insert:
- id: hello
name: '/absolute/path/to/scratch-plugin/src/my-plugin.ts'
Note
name必须是绝对路径。patch 文件只贡献配置,不会改变 loader 解析模块路径时使用的目录。
用 --patch 启动 Web UI:
npx @deepseek-ai/dsh web --patch ./scratch-plugin/cordis.yml
终端打印 hello from my first plugin 即加载成功。打开 http://127.0.0.1:3080,在设置 → 插件列表里能搜到它。
插件加载顺序
生效配置在空根之上按固定顺序逐层组合,后应用的层按行胜出:
- profile 的
dsh.profile.bundles列表(先是@deepseek-ai/dsh-base,再是各已安装组合包,按加入顺序) - profile 自己的
cordis.patch.yml - home 级
$DSH_HOME/cordis.patch.yml(机器本地偏好,各 profile 共享) - 每个
--patch <path>overlay(按命令行顺序)
Tippatch 按
id整行替换目标行的整个 config,不做深度合并。覆盖别人的行时,必须重述该行需要的每一个键。
查看本机实际生效的插件树用 dsh --profile web --dump-config,输出里能看到每一层来自哪个组合包。
插件如何被发现
插件有三条被发现路径:
- 本地开发:
--patch插入本地文件,路径写在cordis.yml里。 - 打包分发:声明
dsh.bundle的 npm 包,用dsh plugin --profile <name> add <包名>安装进 profile(第 24 章)。 - 生态检索:GitHub 仓库打上
dsh-plugintopic,就会被社区插件列表收录。官方 awesome 列表与第三方插件市场都按这个 tag 检索。
hello-dsh:社区实例参考
社区仓库 pingfanfan/hello-dsh 是零基础插件开发教程,包含 22 个中文 skill 示例,面向 0.1.0-rc.6 实测过。它确认了几个容易踩的细节:web profile 默认禁用 skill 相关插件(需要 --patch 启用)、--patch 是插入而非覆盖(指 insert 条目;按 id 改既有行仍会替换整个 config)、端口被旧进程占用时新实例会报 EADDRINUSE。这些行为随版本变化,与你版本不一致时,以 dsh --version 实测为准。
Warning项目处于 Developer Preview 阶段(基线
@deepseek-ai/dsh0.1.0-rc.6),API 可能破坏性变更。写插件前先dsh --version确认版本。
加载失败会怎样
apply 抛异常,进程会因该错误终止——插件加载失败会明确报错,不会静默跳过。但有一条例外:如果 name 指向的模块无法解析(路径拼错、包名写错),具体行为以官方 CLI 参考和 dsh --version 实测为准。新加配置项没效果时,先查拼写。
小结
- 插件 = 导出
apply的模块,ctx是注册入口,也是注册记录本。 - 三种形态:函数、对象、Service 子类;大多数场景用函数。
- 开发期用
--patch加载本地插件,name必须是绝对路径。 - 四层加载顺序:Bundle 序 → Profile patch → home patch →
--patch。 apply抛错进程终止;模块无法解析时以官方 CLI 参考和实测为准。