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 节,九成是老坑。

相关推荐
特立独行的猫A1 小时前
C++ 异步编程:std::future 与 std::promise 详解(含 RPC 客户端实现实践)
c++·后端
Anymous1 小时前
支付为什么要验两次签?——从渠道验签到服务间信任边界与 RSA2
后端
南归北隐2 小时前
Spring AI Alibaba Graph框架实现Tools工具调用
java·后端·spring·spring ai·spring ai tools
茉莉玫瑰花茶2 小时前
GO [ 并发 · 调度器 ]
开发语言·后端·golang
zhangzeyuaaa2 小时前
深入理解 Ruby 可变对象与不可变对象的原理、坑点与最佳实践
开发语言·后端·ruby
wdfk_prog2 小时前
Wi-Fi Direct 教程 05:control socket 与 eloop——P2P_FIND 怎样进入 wpa_supplicant 命令解析器
运维·服务器·后端·网络协议·ubuntu·p2p·wifi-direct
IT_陈寒2 小时前
Python的GIL锁让我把多线程代码全重写了!
前端·人工智能·后端
马剑威(威哥爱编程)3 小时前
【AI全栈后端12-04】Spring Boot 用结构化输出自动解析简历:让模型按你的 POJO 输出
java·人工智能·spring boot·后端
我的xiaodoujiao4 小时前
Django 基础知识详细图文教程 12-Django 模型定义与使用 2
后端·python·django