dsh-plugin-guide.md 15 KB

DeepSeek Harness (dsh) 插件创作机制指南

本文档基于上游仓库 deepseek-ai/deepseek-harness(克隆于本目录 deepseek-harness/,已加入 .gitignore)的官方文档整理,作为本仓库(dsh-plugins)开发 dsh 插件的参考。

上游处于 developer preview 阶段,存在破坏性变更风险。本文所有路径引用均相对于 deepseek-harness/。

1. 项目概览

DeepSeek Harness(dsh)是 DeepSeek AI 开发的开源 Agent Harness(智能体运行框架),底层基于 Cordis 插件框架(以 vendor 方式引入为 @deepseek-ai/cordis)。

核心架构理念:一切皆插件。模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件,因此每一部分都可以从配置替换。不存在需要打补丁的特权内核——扩展 dsh 的方式是把插件挂载到其他插件旁边。

关键目录:

路径 内容
docs/user/develop/ 面向插件作者的教程(入门、工具、配置、发布、框架)
docs/cookbook/ 扩展实操手册(添加包/工具/LLM 适配器/Chat 节点/设置卡片)
docs/architecture.zh.md 整体架构与扩展点归属映射
docs/cordis-primer.zh.md Cordis 框架入门
packages/ 全部核心包(monorepo,pnpm workspace)
examples/ 可运行示例(mcp-memory、headless-agent、web-schedule 等)

2. Cordis 五个核心概念

理解插件机制的基础(详见 docs/cordis-primer.zh.md):

  1. 插件是实现 Service 的对象——可以是带可选 inject 和 apply(ctx) 字段的函数,也可以是 Service 子类,生命周期由 Cordis 管理。
  2. 上下文是服务的容器——一个服务占据一个稳定的 ctx.<key>(如 ctx.tools、ctx.llm、ctx.sessions);其他插件通过 key 查找服务,而非导入具体实现。
  3. 通过 inject 声明服务依赖——插件等待所需服务就绪后才启动;加载顺序由服务依赖表达,无需手动编排。
  4. 类型化事件用于通信——通过 TypeScript 声明合并注册事件名,以 emit、waterfall、parallel、serial(另有 bail)分发。
  5. 注册是可逆的副作用——工具、监听器等通过 ctx.effect() 或 ctx.on() 安装,reload/teardown 时自动撤销。

3. 插件的基本形态

插件是导出 apply 函数的 TypeScript/JavaScript 模块(详见 docs/user/develop/basic/index.zh.md):

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

export const name = 'hello-plugin'
export const inject = ['tools']  // 可选:声明依赖的服务,就绪后才执行 apply

export function apply(ctx: Context) {
  // ctx.tools 已就绪,在此注册能力
}

三种等价形态:

  • 函数形式(最常用):如上。
  • 对象形式:export default { name, inject, apply(ctx) {} }。
  • 类形式(需要向其他插件提供服务时):export default class MyService extends Service,构造函数中 super(ctx, 'myService') 占据 ctx.myService。

生命周期(Fiber 状态机)

PENDING → LOADING → ACTIVE
                 ↘ FAILED
ACTIVE → UNLOADING → DISPOSED
  • 声明 inject 的插件在依赖服务就绪后加载;依赖消失时自动卸载,恢复后重新加载。
  • 自动清理:ctx.on()、ctx.tools.register()、ctx.llm.registerAdapter() 等注册随插件卸载自动撤销;自定义资源用 ctx.effect(() => disposer) 声明清理逻辑。
  • 处置器按注册逆序调用,但异步处置器并发执行;有顺序依赖的清理须放进同一个 effect。
  • 支持 HMR:修改 cordis.yml 配置或(通过 cordis-plugin-hmr)源文件会卸载旧实例、加载新实例。

4. 插件配置

导出 Config(Schemastery schema)即可让用户在 cordis.yml 中传配置(详见 docs/user/develop/basic/config.zh.md):

import Schema from '@deepseek-ai/schemastery'

export interface Config {
  greeting: string
  maxRetries: number
}

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

export function apply(ctx: Context, config: Config) {
  console.log(config.greeting)
}

设计约定:

  • 无硬编码可调参数:凡不同部署可能需要不同值的参数,必须定义为配置字段。检验标准:能否不改代码、只改 cordis.yml 就改变该值?
  • 配置错误要响亮:在 schema 中表达完备约束,使无效配置在加载时即失败。
  • 不要导出普通对象作为 Config,必须满足 Standard Schema 接口。

5. 工具开发

面向模型的工具通过 defineTool + ctx.tools.register() 注册(详见 docs/user/develop/basic/tool.zh.md 与 docs/cookbook/adding-a-tool.zh.md):

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

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

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'read_file',
    description: 'Read a file from disk.',       // 模型可见描述
    parameters: {
      path: { type: 'string', required: true, description: 'Absolute path' },
      limit: { type: 'number' },                  // 默认可选
    },
    output: {
      schema: { type: 'string' },                 // 规范 JSON 值的 schema
      render: (_args, value) => [{ type: 'text', text: value }],  // 模型可见内容
    },
    async execute(args, exec) {
      // args 已按 schema 自动校验并推导类型
      // exec.signal 用于取消;exec.agent 可发送异步通知
      return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
    },
  }))
}

核心约定:

  • 参数自动校验:defineTool 在 execute 前按 parameters 校验模型生成的参数;schema DSL 表达不了的约束(非空、正数、跨字段)需手动检查。
  • 返回规范 JSON 值:execute 只返回 output.schema 声明的值;不要返回内容块或迫使调用方解析自然语言。抛异常或返回无效值即 isError。
  • 遵守 exec.signal:信号触发时取消进行中的工作。
  • schema 自动流入系统提示词:注册即生效,无需额外集成;Code Mode 中每个可见工具自动可经 await tools.<name>(args) 调用。
  • UI 卡片独立声明(可选):presentCall(args) / presentResult(args, result) 返回渲染意图(generic / terminal / diff / search / web 卡片)。必须是纯函数(实时流式与日志回放都会执行,禁止 I/O、时钟、随机数)。
  • 后台任务:producer 配置 run_in_background,用 ctx.jobs.start({ kind, label, owner: exec.agent, run }) 注册;返回 { kind: 'background', jobId } 类型的规范句柄。
  • 执行策略不内建到工具:用 tools/pre-execute(允许/拒绝/询问)、tools/execute(截止时间/重试/指标)、tools/post-execute(改写展示或结果)、tools/result(观测不可变结果)等事件实现。

生产级参考实现:packages/shell/tool-bash(三包结构)、packages/core/fs 中的工具。

6. 事件系统

事件是插件间松耦合通信的核心机制(详见 docs/user/develop/framework/events.zh.md):

模式 语义
emit 广播;监听器按注册顺序同步观察,无返回值
bail 短路;第一个非空返回值成为最终结果
serial 顺序执行,await 异步结果,非空返回值终止后续
waterfall 中间件链;监听器收到 (...args, next),必须调用 next() 才能委托下游,不调用即短路(用于拦截/网关)
parallel 所有监听器并行观察
  • 事件名遵循 namespace/action 约定(如 agent/pre-step、tools/result、session/event)。
  • 通过声明合并获得类型安全:

    declare module '@deepseek-ai/cordis' {
    interface Events {
    'my-plugin/ready': (payload: { id: string }) => void
    }
    }
    
  • 注意区分:turn/*、step/*、tool/call、tool/result 等是持久化会话事件类型(日志事实),观察它们要监听 session/event 并检查 event.type;agent/*、tools/* 才是实时 Cordis 事件。

  • 单决策事件(如 agent/pre-step、agent/request、llm/stream、tools/* 三事件)是 waterfall;agent/turn-stopping 是 serial。

7. 打包与分发

两个核心概念(详见 docs/user/develop/basic/publish.zh.md):

  • 组合包(bundle):附带一个配置层的 npm 包,是你编写并分发的单位。package.json 中 dsh.bundle 指向其 patch 文件。
  • profile:位于 $DSH_HOME/profiles/<name> 的可启动组装目录,dsh.profile.bundles 按序列出组合包。由 dsh plugin 命令维护,无需手写。

组合包最小结构

hello-plugin/
├── package.json       # 声明 dsh.bundle
├── cordis.patch.yml   # 被列入 profile 时应用的配置层
└── index.js           # 插件入口
{
  "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(插件行按包名引用,让 Node 模块解析找到已安装代码):

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

本地开发(--patch overlay)

无需打包,直接以绝对路径插入本地源码:

- insert:
    - id: hello
      name: '/absolute/path/to/my-plugin/src/my-plugin.ts'
dsh web --patch ./cordis.yml   # 仓库源码内用 pnpm dsh web

安装与加载顺序

dsh plugin --profile demo add ./hello-plugin      # 本地路径
dsh plugin --profile demo add github:you/hello-plugin   # git(需 prepare 脚本 + allowBuilds 授权)
dsh plugin --profile demo add your-package        # npm(预构建,无需授权)
dsh plugin --profile demo remove dsh-hello-plugin
dsh --profile demo --dump-config                  # 查看实际生效的配置树

生效配置按以下顺序逐层叠加(后应用的层按行胜出,patch 替换目标行整个 config,不做深度合并):

  1. profile 的 dsh.profile.bundles 所列各组合包 patch(按列表顺序,首个为 @deepseek-ai/dsh-base)
  2. profile 自己的 cordis.patch.yml
  3. home 级 $DSH_HOME/cordis.patch.yml
  4. 各 --patch <path> overlay(按 argv 顺序)

推论:你的 patch 可按 id 覆盖前面层的行(须重述该行全部键);用户可在自己 profile 的 patch 中覆盖你的行(因此优先给出合理默认值)。

git 安装的构建授权

git 安装拉取源码而非构建产物,作者需提供自包含的 prepare 脚本(参考 turtle-ui);用户需在 profile 的 pnpm-workspace.yaml 中授权(allowBuilds: { dsh-hello-plugin: true })——这等于允许该包代码在安装时于本机执行,只对可信源码授权并锁定 commit。免授权的替代:发布到 npm(预构建 lib/)或交付 tarball(pnpm pack)。

8. 扩展点速查表

新行为附加到已有扩展点(完整版见 docs/architecture.zh.md § 新行为的归属位置):

目标 机制
添加模型提供方 在 ctx.llm 上注册适配器
添加面向模型的能力(工具) ctx.tools.register();schema 自动加入提示词组装
添加用户命令(无需模型轮次) ctx.commands
添加后台工作 ctx.jobs;job_* 工具收集/停止
添加 shell / 终端执行 注册 ctx.shell 后端 / ctx.terminals 后端
添加文件系统访问或策略 注册 ctx.fs 提供方,或监听 fs/* 事件
限制所启动的进程 ctx.sandbox 后端
拦截请求、工具或轮次 agent/* 或 tools/* 事件;agent/turn-stopping 停止轮次
添加模型可见上下文 agent.inject()(落入下一次获准的请求)
添加 UI / Web Chat 节点 驱动 ctx.agents 并从 session/event 渲染;注册 ConversationNodeDefinition
添加持久会话状态 扩展 SessionEventMap,从日志渲染和回放
生成会话标题 注册唯一的 ctx.sessionTitle 提供方
fork 活跃会话 ctx.sessions.fork(source, boundary?)
将注册项限定到单个 agent 使用该 agent 的 agent.ctx

能力 seam(可替换能力)

一个 seam 包含三种角色,可按需拆分为不同包:

  • Service Definition:声明接口
  • Service Provider:实现接口(替换提供方即改变整个产品,如把 fs/进程指向远程沙箱)
  • Consumer:使用能力(通常是面向模型的工具)

命名与包拓扑

  • 单一 engine/runtime/policy/controller/resolver/store 用单数 ctx key;registry 用复数。
  • 接口包用能力名称;实现包加机制/协议/环境/厂商限定词。
  • 向上游贡献 workspace 包的完整清单见 docs/cookbook/adding-a-package.zh.md(本仓库开发树外插件无需遵循其 workspace 不变式,但角色命名规范值得借鉴)。

9. 关键工作流速记

# 克隆并从源码运行(探索/调试上游)
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness && pnpm install && pnpm run build
pnpm dsh web --patch ./my-overlay/cordis.yml

# 快速验证插件层
dsh --profile <name> --dump-config

# 发布插件
pnpm build && pnpm publish     # 预构建 lib/ 后发布 npm

10. 上游文档索引

主题 路径(相对 deepseek-harness/)
第一个插件 docs/user/develop/basic/index.zh.md
开发一个工具(教程) docs/user/develop/basic/tool.zh.md
插件配置 docs/user/develop/basic/config.zh.md
打包与安装插件 docs/user/develop/basic/publish.zh.md
插件与生命周期 docs/user/develop/framework/index.zh.md
事件系统 docs/user/develop/framework/events.zh.md
服务与依赖 docs/user/develop/framework/service.zh.md
工具编写参考(权威) docs/cookbook/adding-a-tool.zh.md
LLM 适配器 docs/user/develop/practice/llm-adapter.zh.md、docs/cookbook/adding-an-llm-adapter.zh.md
架构总览 docs/architecture.zh.md
Cordis 入门 / 教程 docs/cordis-primer.zh.md、docs/cordis-tutorial/index.zh.md
扩展实操手册 docs/cookbook/extension-cookbook.zh.md
工具目录 / 配置目录(生成) docs/tool-catalog.zh.md、docs/config-catalog.zh.md
示例集 examples/README.zh.md

社区:GitHub Discussions;插件仓库可打 dsh-plugin topic 提升可发现性;Discord。