夜雨聆风学习资料网

ARTICLE · 1041035

从零写一个 DSH 插件

从零写一个 DSH 插件

一、DSH 是什么

DSH(DeepSeek Harness)是 DeepSeek 的 agent harness——它不是模型,而是驱动模型的那层运行时:agent loop 的编排、会话的持久化与回放、LLM 调用的抽象、工具的调度,都发生在这一层。

它有两个设计值得先记住。

服务化。运行时能力被拆成一组服务挂在容器上,插件按需声明后使用。比如 llm(模型调用与提供方注册)、sessions(会话存储)、sessionProjections(会话的只读派生视图)。@deepseek-ai/dsh-llm 这个包做的正是"提供方无关的 LLM 词汇与抽象服务"——上层是同一套调用接口,底下接哪个模型由适配器决定。

分 profile。DSH 按 profile 组织运行环境,例如 web UI 跑在 web profile 上。每个 profile 有自己的配置树和自己的 node_modules,互不干扰。下文所有 --profile web 就是这个意思。

二、DSH 插件是什么

一个 dsh 插件 =一个普通的 npm 包,包里装的是一段Cordis插件。Cordis 负责依赖注入和生命周期:ctx 随 fiber 销毁自动回收,插件不必自己写清理逻辑。

插件能做的事大致三类:

类型
做什么
入口
行为扩展
订阅运行时事件,改变默认行为
ctx.on(...)
能力注册
注册工具、中间件,供模型调用
对应服务的 register 方法
提供方适配
接入新的模型提供方
ctx.llm.registerAdapter(...)

安装用 CLI(它是 pnpm 的薄转发器):

dsh plugin --profile web add <包名>

三、DIY插件:dsh-welcome

例子选dsh-welcome:102 行、单文件,作用只有一个——每个新建的空会话,自动发一条欢迎消息「Hello,欢迎来到DSH」。选它是因为它小,却把上面说的机制全走了一遍。

未全局安装 dsh 时,用 npx 形式,将dsh 替换成npx @deepseek-ai/dsh

先跑通,后面的机制才有落点:

dsh plugin --profile web add dsh-welcomedsh web# web profile 禁用了 HMR,必须重启

新建一个会话,顶部出现一条正常的助手气泡就算通了。它不是带 plugin 标签的上下文注入,而是由助手"直接说出"的——做法是插件往会话日志里写一个完整、已闭合的助手轮次。

四、动手:从零搭出这个插件

4.1 先写骨架

一个 dsh 插件就是一个 ESM 模块,导出四样东西:

export const name   = ”dsh-welcome”;                        // 插件名,也是配置行的默认 idexport const inject = [”sessions”, ”sessionProjections”];   // 声明要用到的服务export const Config = z.object({                            // 可配置项(schemastery 声明)  greeting: z.string().default(”Hello,欢迎来到DSH”)});export function apply(ctx, config) {                        // 插件主体  ctx.on(”session/created”, handler, { globaltrue });}

四个导出各有分工:inject 声明服务依赖,没声明的服务在 ctx 上拿不到;Config 用 schemastery 描述,默认值即文档,宿主校验通过后才传进 apply;apply 里订阅事件、注册能力。

4.2 让它被加载

把 index.js 装进 profile、重启——大概率什么都不会发生。因为 dsh不扫目录,插件是否被启动完全由配置树决定:树里有一行 Loader,插件才会被加载。

插件通过 bundle patch 把自己这一行插进去。先在 package.json 声明:

{ ”dsh”: { ”bundle”: { ”patch”: ”./cordis.patch.yml” } } }

再在 cordis.patch.yml 里插入 Loader 行:

- insert:    - id: welcome      name: 'dsh-welcome'

配置树是分层的,patch 应用顺序决定了谁覆盖谁:

dsh plugin add 之所以能"装完即用",是因为它除了跑 pnpm,还会自动把插件追加进 dsh.profile.bundles 列表——省了自己改配置。

4.3 往会话日志里写事件

插件不直接操作 UI,而是往会话日志里 append 事件。日志用 turn / step 两级坐标定位,一个最简的完整轮次长这样:

UI 和模型历史都只读这份日志,所以只要事件序列合法,界面就会照常渲染,插件无需碰任何 UI 代码——这也是"欢迎语能变成一条正常助手气泡"的原因。

不过"序列合法"这四个字是有硬性门槛的:turn 的取值、stream 字段、以及 session/created 的触发时机都有约束。

五、本地开发与调试

从本地源码安装:

dsh plugin --profile web add ”file:D:\path\to\my-plugin”

安装后必需重启 dsh web

改了源码后必须remove再add,update 是空操作。原因是 profile 用 nodeLinker: hoisted,node_modules/<包> 是安装时复制出来的真实目录(不是软链);而 pnpm 对 file: 目录依赖在 lockfile 里只记 version: file:<路径>,不含版本号,所以重跑 add/install(甚至 --force)都是 Already up to date。

dsh plugin --profile web remove my-plugindsh plugin --profile web add ”file:D:\path\to\my-plugin”

最后记得重启 dsh web——web profile 的 HMR 被禁用,不重启不生效。

六、测试

新建会话

示例插件的完整源码在github.com/axingde/dsh-welcome

相关学习资料