写一个自己的插件

从使用者变成贡献者:一个插件就是一个导出 apply(ctx) 的模块。写最小插件、注册模型可调用的工具、打包成 bundle 发布到 npm 或 GitHub。

约 10 分钟读完

读完这篇你会

  • 理解 DSH 插件的真实形态(一个导出 apply(ctx) 的模块)
  • 写出一个最小插件,并加载进 Web UI 验证
  • 给它加上一个模型可以调用的工具
  • 把它打包成可安装的插件包,发布给别人用

这一篇面向想给生态贡献插件的开发者,需要你能看懂基础的 TypeScript。普通用户可以跳过,不影响其他教程。

插件其实长这样

先破除神秘感:DSH 的"一切皆插件"不是修辞——它的每个能力(工具注册表、模型适配、界面、甚至主循环)都是一个插件,而你写的插件和官方插件地位完全相同

一个插件就是一个模块,导出三样东西:

import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-plugin'     // 插件名
export const inject = ['tools']     // 声明依赖的服务
export function apply(ctx: Context) {
  // 在这里注册能力
}
  • name:插件身份,诊断信息里显示的名字;
  • inject:依赖声明——框架会等这些服务就绪后才执行 apply,加载顺序由依赖决定而不是文件顺序;
  • apply(ctx):唯一入口。ctx 上挂着所有服务(ctx.toolsctx.llm……),你在里面注册的一切都会在插件卸载时自动清理,不需要手动 removeListener。

官方的开发者教程在仓库 docs/user/develop/basic/ 目录,这一篇就是它的浓缩版加中文注释。

第一步:跑起一个最小插件

最省事的开发方式是从官方仓库源码跑(能用内置的 pnpm dsh 命令):

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install && pnpm run build

在仓库根目录建一个实验目录和一个插件文件:

scratch-plugin/
└── src/
    └── my-plugin.ts

my-plugin.ts 写:

import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  console.log('[hello-plugin] plugin loaded!')
}

再建 scratch-plugin/cordis.yml(注意插件路径必须是绝对路径,把 /absolute/path/to/ 换成你机器上仓库的真实路径):

- insert:
    - id: hello
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'

带覆盖层启动:

pnpm dsh web --patch ./scratch-plugin/cordis.yml

打开 http://127.0.0.1:3080,启动日志里出现 [hello-plugin] plugin loaded!,你的第一个插件就跑起来了。

第二步:注册一个模型能调用的工具

my-plugin.ts 换成带工具注册的版本:

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

重启(还是同一条 --patch 命令),然后在对话里说:"用 greet 工具向 Ada 问好"。模型会调用 greet,拿到 Hello, Ada! 作为工具结果。

三个要点:

  1. name 是模型点名的依据,起名要表意;
  2. description 决定模型会不会想到用这个工具,写清楚触发场景;
  3. parameters 声明输入 schema,框架据此校验参数。

第三步(可选):接受用户配置

插件可以导出一个 Config schema,让用户在 cordis.yml 里改行为:

import Schema from '@deepseek-ai/schemastery'

export interface Config {
  greeting: string
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
})

export function apply(ctx: Context, config: Config) {
  console.log(config.greeting) // 用户值或 schema 默认值
}

设计原则:凡是不同部署可能想改的值,都应该是配置字段,不要硬编码。

第四步:打包发布

本地实验用 --patch 就够了;要分发给别人,需要打成一个 bundle(可安装的 npm 包)。目录结构:

hello-plugin/
├── package.json       # 声明 dsh.bundle
├── cordis.patch.yml   # 这个包贡献的配置层
└── index.js           # 插件入口

package.json 的关键是 dsh 字段:

{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}

cordis.patch.yml 里插件行引用包名而不是路径:

- insert:
    - id: hello
      name: dsh-hello-plugin

本地装进 profile 验证:

dsh plugin --profile web add ./hello-plugin
dsh --profile web --dump-config   # 能看到 "# == dsh-hello-plugin" 层说明挂上了

对外发布的三种方式:

方式命令特点
npmdsh plugin --profile web add your-package发布时预构建 lib/,用户安装无构建、无授权提示,体验最好
GitHubdsh plugin --profile web add github:you/hello-plugin拉源码不拉产物,用户要过 allowBuilds 授权;作者必须提供自洽的 prepare 脚本
tarballdsh plugin --profile web add ./hello-plugin-0.1.0.tgzpnpm pack 产物,同样免构建授权,适合内网分发

推荐优先发 npm。走 GitHub 分发时,务必在 README 里写清楚你针对的 dsh 版本——DSH 还在 developer preview 阶段,API 会有破坏性变更。

开发纪律四条

来自官方文档与事故复盘,违反了不报错但行为诡异:

  1. 只用具名导出:不要 export default。Loader 会把默认导出折叠成插件本体,inject 等元数据被静默丢弃,依赖声明失效;
  2. 禁止模块级副作用:模块只会被 import 一次,但插件可能挂载/卸载多次。setInterval 这类东西必须写在 apply 里、用 ctx.effect() 包裹,否则卸载后永远清不掉;
  3. patch 是浅覆盖不是深合并:覆盖某行 config 时要写全所有要保留的字段,只写一个等于把其他字段全删了;
  4. 加新行必须用 insert:想加行却用覆盖语法会报 entry not found

排错第一反应

任何"改了配置没生效",先 dump 最终配置树,不要猜:

dsh --profile web --dump-config

它会打印所有层叠加后的结果(bundle 层 → profile 层 → home 层 → --patch 层),你的改动在哪一层被吞了一眼可见。

下一步

写好的插件想让更多人发现?发到 GitHub 时加上 dsh-plugin 话题标签,方便社区检索——本站的插件目录正是从这里收录社区插件的。

常见问题 FAQ →