DeepSeek Harness 插件开发新手教程

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


1. 先搞懂三个词

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

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


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

位置 是什么
C:\Users\guanxi\.dsh\ dsh 的"家目录"(DSH_HOME),所有全局数据都在这
.dsh\settings.yaml 全局设置(界面语言、模型列表等)
.dsh\.credentials.yaml API 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. 我们插件长什么样(每个文件干什么)

vbnet 复制代码
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 都是同一个套路,导出三个东西:

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:身份证 + 两份声明

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:把自己挂到系统树上

yaml 复制代码
- insert:
    - id: command-balance
      name: dsh-command-balance

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


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

第 0 步:一次性准备

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

sh 复制代码
npm i -g pnpm

第 1 步:建插件目录

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

第 2 步:安装进 profile

sh 复制代码
dsh plugin --profile web add D:\deepseek\dsh-command-balance

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

第 3 步:重启验证

sh 复制代码
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. 卸载与回滚

sh 复制代码
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 节,九成是老坑。

相关推荐
念何架构之路32 分钟前
go-grpc设计思想
开发语言·后端·golang
system_error_x1 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn3 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈3 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780514 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123454 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒4 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记4 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk598784 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端