DeepSeek Harness 插件开发新手教程

0 阅读8分钟

写给"特别新"的新手:不需要你会写多少代码,只要会用命令行、会复制粘贴。 这篇教程会带你从零理解并复刻一个真实可用的插件:dsh-command-balance(顶栏余额胶囊 + /balance 命令)。 所有代码都在本机 D:\deepseek\dsh-command-balance\,你可以边看教程边对照。


1. 先搞懂三个词

词是什么打个比方
DeepSeek Harness(dsh)DeepSeek 的编程智能体框架,你在用的这个界面就是它一台空舞台
插件(plugin)给 dsh 加功能的小程序,每个功能都是一个插件舞台上的演员
cordisdsh 用来管理插件的"插座系统",负责插上、断电、互相配合接线板

dsh 的设计哲学是 "Everything is a Plugin(一切皆插件)":你看到的会话、文件编辑、 斜杠命令、甚至顶栏的每个按钮,全都是插件。所以"给 dsh 加功能" = "写一个插件"。


2. 你的电脑上有什么(文件地图)

位置是什么
C:\Users\guanxi\.dsh\dsh 的"家目录"(DSH_HOME),所有全局数据都在这
.dsh\settings.yaml全局设置(界面语言、模型列表等)
.dsh\.credentials.yamlAPI key 保管库(DEEPSEEK_API_KEY 就存在这,注意别把这个文件发给别人)
.dsh\profiles\web\web 这个 profile(界面档案)的配置间
.dsh\profiles\web\package.json这个 profile 用了哪些插件(不要手改,用命令改)
.dsh\profiles\web\cordis.patch.yml你自己的"覆盖层",想改插件默认配置就写在这
.dsh\profiles\node_modules\所有已安装插件的本体
D:\deepseek\dsh-command-balance\我们写的插件,今天的主角

profile 是什么? 一套"启动套餐"。dsh web = 用 web 这套套餐启动(带完整界面)。 还有 headless(无界面)、sdk 等套餐。插件装进哪个套餐,哪个套餐就有这个功能。


3. 我们插件长什么样(每个文件干什么)

D:\deepseek\dsh-command-balance\
├── package.json        ← 插件的"身份证":叫什么、入口在哪、声明自己是 bundle + 客户端插件
├── cordis.patch.yml    ← "安装说明书":告诉 dsh 把我挂到系统树的哪个位置
├── lib\
│   ├── index.js        ← 服务端(跑在 Node 里):查余额、注册 /balance 命令、提供远程接口
│   └── client.js       ← 客户端(跑在浏览器里):顶栏那颗胶囊 + 悬停气泡
├── test\
│   ├── smoke.mjs       ← 离线测试(不花一分钱,不打真接口)
│   └── live.mjs        ← 在线测试(用真实 key 打一次余额接口)
└── README.md           ← 说明文档

一个插件可以只有服务端(比如只加个斜杠命令),也可以两头都有(要往界面上画东西就必须有客户端)。


4. 服务端插件的最小骨架(三件套)

任何 dsh 插件的 JS 都是同一个套路,导出三个东西:

const name = "command-balance";   // ① 名字:插件的编号,别和别人重名
const inject = ["commands"];      // ② 依赖:我需要哪些系统服务才能干活
function apply(ctx, config) {     // ③ 干活:ctx 是"插线板",把功能接上去
  ctx.commands.register({
    name: "balance",              // 用户输入 /balance 里的 balance
    description: "查询 DeepSeek 平台账户余额",
    handler: async () => ({ kind: "success", text: "余额是……" })
  });
}
export { apply, inject, name };
  • handler 返回 {kind: "success"|"error", text},界面会把 text 直接显示给用户,不经过模型,不花 token。
  • config 是安装时可以传的配置(比如 API 地址、超时时间),不传就用默认值。

API key 从哪来?

绝不把 key 写死在代码里。插件按这个顺序找:

  1. 问 dsh 的凭据库:ctx.get("credentials").resolve("DEEPSEEK_API_KEY") —— 就是你 C:\Users\guanxi\.dsh\.credentials.yaml 里那个,和模型页共用;
  2. 凭据库没有 → 回退到系统环境变量 DEEPSEEK_API_KEY。

5. 让 dsh 认识你的插件(两个声明)

① package.json:身份证 + 两份声明

{
  "name": "dsh-command-balance",
  "type": "module",
  "main": "lib/index.js",
  "exports": {
    ".": "./lib/index.js",
    "./client": "./lib/client.js"
  },
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" },
    "client": { "platform": "web" }
  }
}
  • "bundle": { "patch": ... } 声明"我是一个功能包,装上就自动生效";
  • "client": { "platform": "web" } 声明"我还有一段跑在浏览器里的代码"(只有要画界面才需要)。

② cordis.patch.yml:把自己挂到系统树上

- insert:
    - id: command-balance
      name: dsh-command-balance

意思:"往系统树里插入一行,编号 command-balance,代码就是 dsh-command-balance 这个包"。 每个插件一行,像乐高说明书一样逐层叠加。


6. 动手:从零装一个插件(完整流程)

第 0 步:一次性准备

dsh 的插件命令需要 pnpm(一个包管理器)在 PATH 里,没有就装一个(只需一次):

npm i -g pnpm

第 1 步:建插件目录

照着第 4、5 节把三个文件写好(或直接复制 D:\deepseek\dsh-command-balance\ 改名)。

第 2 步:安装进 profile

dsh plugin --profile web add D:\deepseek\dsh-command-balance

⚠️ 必须用绝对路径。相对路径会被 dsh 锚定到它的 profile 目录,装错地方。 这条命令做的事:把你的目录以链接方式装进 profile,并且因为声明了 dsh.bundle, 自动把它追加进 dsh.profile.bundles。

第 3 步:重启验证

dsh --profile web --dump-config | findstr balance    # 确认组合树里有这一层
dsh web                                              # 正常启动

重启后进任意会话:顶栏看到胶囊、输入 / 能看到 balance 命令,就成功了。

第 4 步(进阶):顶栏胶囊是怎么画的

浏览器里那段代码(lib/client.js)有三件套:

  1. 外壳格式:必须套一层 window.__ModuleLoader__.load({ id, factory }), 文件名必须是 lib/client.js(和 package.json 的 exports 对应);
  2. 画在哪:dsh 界面上有很多预定义的"插槽"(slot),比如 conversation.session.header.utilities 就是"会话顶栏工具区"。 用 ctx.slots.inject("插槽名", ...) 把一个 React 组件放进去;
  3. 数据怎么来:浏览器不能直接拿 API key(不安全),要反过来问服务端:
    • 服务端:ctx.provide("balanceController", 服务对象) 挂一个服务;
    • 客户端:ctx.remote.$mount(描述符) 声明"我知道这个服务", 然后就能调用 ctx.get("remote.balance").get();
    • 返回值是信封 {ok, value},真正的业务结果在 .value 里(新手最容易栽的坑)。

7. 测试与调试

想做什么怎么做
语法检查(不运行)node --check lib/index.js
不花钱的功能测试node test/smoke.mjs(伪造接口响应,测各种分支)
真接口连通测试node test/live.mjs(打真实 API,不回显 key)
确认插件被装进套餐dsh --profile web --dump-config,搜你的插件 id
页面行为不对先 Ctrl+F5 强制刷新(旧页面常缓存旧代码)

页面顶部出现"Failed to load plugins: 你的插件名"? 说明客户端代码在激活时抛错了。 给自己代码里的 apply 包一层 try/catch、把错误写到 window.__XXX__ 上,刷新后用 控制台读取,是最快的定位办法(我们就是这么找到问题的)。


8. 我们真实踩过的坑(提前帮你踩了)

  1. pnpm 不在 PATH → dsh plugin add 报错。npm i -g pnpm 解决。
  2. add 用了相对路径 → 被锚定到 profile 目录,装错位置。永远用绝对路径。
  3. 端口被占 → 起了两个 dsh web 会撞端口。netstat -ano | findstr :端口 找到 PID, taskkill /F /PID 数字 杀掉。
  4. 欢迎页看不到胶囊 → 顶栏工具区是"会话级"插槽,要先进入一个会话。
  5. 改了客户端代码页面没变化 → 客户端 bundle 在启动时编排,重启 dsh + 浏览器强刷。
  6. 客户端 codec 缺 create() 工厂 → 插件激活直接失败,页面顶部有横幅提示。 每个远程接口描述符的 result/参数 codec 都要 create: () => 校验器对象。
  7. 客户端拿不到自己的服务 → 服务是插件自己挂的,别把它写进模块级 inject(自己等自己,永远卡死), 用 ctx.get("remote.balance") 在调用时取。
  8. 以为拿到的是余额,其实是信封 → 远程调用返回 {ok, value} 两层,解包再判断。

9. 卸载与回滚

dsh plugin --profile web remove dsh-command-balance

删除依赖、自动从套餐里摘除。你的插件源码目录还在,想再装随时 add 回来。


10. 名词小抄

  • profile:启动套餐(web / headless / sdk……),插件装进套餐才生效
  • bundle:声明了 dsh.bundle.patch 的插件,装上即自动生效
  • patch / insert:往系统树里"插入一行"的说明书,后写的覆盖先写的
  • slot(插槽):界面上预留的扩展位,如"会话顶栏工具区";分全局级和会话级
  • 凭据 seam:dsh 的钥匙保管库,代码里只写钥匙名字,不写钥匙内容
  • Remote 服务 / RPC:浏览器里的插件代码想拿数据,就通过它问 Node 里的服务端
  • SRC 模式:Gateway 的一种"免注册表"发现方式,服务对象带上约定的标记字段即可被发现

11. 学完可以玩什么

  1. 让 AI 自己查余额:给插件再加一个"工具(tool)"声明,模型就能在对话中自己调用查余额;
  2. 任务列表面板:D:\deepseek\dsh-panel-tasks\ 是第二个实例——右侧边栏"任务"标签页, 实时镜像模型的任务清单(dsh 的 todos 会话投影),完成一项自动打勾。 它是纯客户端插件(服务端是空壳),读数据用的正是 dsh 自带的会话投影机制;
  3. 做别的胶囊:换成"今日用量""最近会话数",套路完全一样,换个 slot 和数据源而已;
  4. 多 profile:同一插件 dsh plugin --profile headless add ... 也装进无界面模式;
  5. 读真源码:所有官方插件都在 C:\Users\guanxi\.dsh\profiles\node_modules\@deepseek-ai\ 下, 每个都带中文 README,是最好的教科书;官方仓库文档:github.com/deepseek-ai/deepseek-harness(docs/ 目录)。

祝玩得开心!遇到报错先看第 8 节,九成是老坑。