Cordis 插件开发教程

开发你的第一个 Cordis 插件

通过一个可运行示例,创建最小 TypeScript 插件,将它加载到 DeepSeek Harness Web UI,并理解自动清理与服务依赖声明。

难度入门预计时间15–20 分钟最终成果一个本地 Hello 插件

来源说明

本独立教程依据当前官方开发者预览文档整理。用于生产前,请再次核对上游命令和 API。

官方教程源码仓库

00 · 开始之前

开始之前

官方教程假设你已经可以从源码运行 DeepSeek Harness。请在仓库根目录操作,确保命令和 Patch 路径与示例一致。

  • 已检出 deepseek-ai/deepseek-harness 源码
  • 已使用仓库指定的包管理器安装依赖
  • 终端当前位于仓库根目录
  • Node.js 与 pnpm 版本符合当前检出版本要求

DeepSeek Harness 及插件协议仍处于开发者预览阶段,请固定开发使用的 revision。

步骤 1

创建本地插件项目

在 Harness 仓库内创建临时项目。第一个实验保持本地化,加载路径更明确,也更容易完整移除。

shell
mkdir -p scratch-plugin/src

步骤 2

编写插件模块

Harness 插件是导出 apply 函数的 TypeScript 模块。框架使用 Cordis Context 调用 apply,你可以通过这个上下文注册能力。

创建 scratch-plugin/src/my-plugin.ts。示例中的日志将作为插件成功加载的可观察信号。

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

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  // Required dependencies are ready before apply runs.
  console.log('[hello-plugin] plugin loaded!')
}

步骤 3

注册到 cordis.yml

先在仓库根目录执行 pwd,再创建 scratch-plugin/cordis.yml 作为 Web 配置覆盖层。请把示例路径替换为 pwd 输出的绝对路径。

yaml
- insert:
    - id: hello
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
插件路径必须是绝对路径。Patch 只贡献配置,不会改变模块加载器解析路径时使用的 Profile 目录。

步骤 4

使用覆盖层启动 Web UI

在仓库根目录使用 Patch 文件启动 Web UI,然后访问 http://127.0.0.1:3080。

shell
pnpm dsh web --patch ./scratch-plugin/cordis.yml
加载成功后,启动期间终端会输出 [hello-plugin] plugin loaded!。

05 · Cordis

生命周期与服务依赖

Cordis 会把注册内容限定在插件上下文内。在插件持有定时器、连接、工具或共享能力前,应先理解下面两个机制。

使用 ctx.effect() 清理副作用

通过 Context 注册的内容会随插件卸载而移除。对于定时器、网络连接等需要显式释放的资源,应从 ctx.effect() 返回清理函数。

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

export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => {
      console.log('heartbeat')
    }, 5000)

    // Runs automatically when the plugin unloads.
    return () => clearInterval(timer)
  })
}

使用 inject 声明服务依赖

如果插件依赖 tools、llm 或其他服务,请在 inject 中声明。框架会等待这些服务就绪后再调用 apply。

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

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

export function apply(ctx: Context) {
  // ctx.tools is ready here.
  ctx.tools.register(/* ... */)
}

06 · API

选择最简单的插件形态

Cordis 支持函数、对象和类三种形态。优先使用函数;只有插件需要向其他插件提供服务时,再考虑 Service 类。

函数形式

适合能力单一的插件,易于阅读、测试和卸载。

对象形式

适合把 name、inject、apply 和相关元数据组织在一起。

Service 类

适合对外提供具名 Cordis 服务并管理服务生命周期。

对象形式

typescript
export default {
  name: 'my-plugin',
  inject: ['tools'],
  apply(ctx: Context) {
    // Register capabilities here.
  },
}

Service 类

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

export default class MyService extends Service {
  static inject = ['tools']

  constructor(ctx: Context) {
    super(ctx, 'myService')
    // Perform synchronous initialization here.
  }
}

07 · Verify

验证清单

进程启动并不等于开发完成,还要检查加载路径、生命周期和移除行为。

  • Web UI 可以在 127.0.0.1:3080 打开。
  • 终端只输出一次 hello-plugin 加载日志。
  • 将绝对路径改为无效文件时,会得到可理解的加载错误。
  • 停止或卸载插件后,定时器等副作用被正确清理。
  • 移除 Patch 后,Web Profile 恢复原始行为。

常见问题

无法解析模块+

确认 cordis.yml 中是绝对路径、指向 .ts 文件,并且属于当前源码检出。

没有加载日志+

确认命令在仓库根目录运行,且 --patch 指向 ./scratch-plugin/cordis.yml。

服务为 undefined+

将服务名加入 inject,并只在 apply 被调用后访问。

3080 端口不可用+

停止占用该端口的进程,或按照当前上游 Web UI 参数调整。

继续开发

继续开发

最小插件验证了加载与生命周期。下一步可以开发真实 Tool、增加校验配置、完成打包,并针对固定的 Harness revision 进行测试。