ByteNoteByteNote
从 hello 函数开始:写你的第一个 Cordis 插件
字

字节笔记本

2026年10月6日 · 约 6 分钟读完

从 hello 函数开始:写你的第一个 Cordis 插件

API中转
¥120

Cordis 是 DeepSeek Harness 的底层插件框架,口号是「一切皆插件」:工具、LLM 适配器、文件访问,甚至 agent loop 本身,都是挂载到共享上下文里的插件。官方仓库 deepseek-ai/deepseek-harness 采用 MIT 协议,附带的 Cordis 教程用七个可以运行的章节带你动手实践,本文整理其中第一章:不写一行框架启动代码,只用两个文件跑通第一个插件,全程不需要 API 密钥。

第一个 Cordis 插件的启动流程:从两个文件到一行输出

插件是一个带 apply 的模块

在教程的工作目录里新建 hello.ts,全部代码只有七行:

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

export const name = 'hello'

export function apply(ctx: Context) {
  console.log('hello from my first plugin')
}

约定在这里已经全部出现。Cordis 加载一个模块时,会用一个上下文调用它的 apply 函数,这个上下文就是参数里的 ctx,插件此后注册的所有内容都通过它完成。name 导出项是可选的显示元数据,作用是在诊断信息里标识这个插件。import type 只导入类型信息,运行时会消失,因此这个文件没有引入任何运行时依赖。

用 cordis.yml 组装应用

第二个文件更短,创建 cordis.yml,内容只有一行:

yaml
- name: './hello.ts'

这份文件是一组 Cordis 配置项的列表,name 是模块指定符,可以是相对路径,也可以是 NPM 包名,loader 会逐项挂载。要注意的是,各项会并发启动,它们在列表中的位置并不保证插件的加载先后,真正的顺序由服务依赖(inject)决定。这个设计把「用哪些」和「按什么顺序起」分开了:你在文件里声明要组合哪些插件,排序这件事交给依赖关系。官方仓库里 dsh base 的配置就是一份更长的插件组合清单,部署 overlay 在它之上继续打补丁。

运行:三步走到一行输出

在教程目录执行启动命令:

sh
node --import tsx ../../vendor/cordis/bin.js

预期输出只有一行 hello from my first plugin,随后进程自行退出。背后依次发生了三件事:启动器创建根 Context 并挂载 Loader 插件;Loader 读取 cordis.yml,解析 ./hello.ts 并把它作为子插件挂载;Cordis 调用你的 apply(ctx)。命令里的 --import tsx 标志让 Node 无需构建步骤就能直接运行 TypeScript。

你的文件里没有任何框架启动代码,这正是 Cordis 的核心分工:插件只描述自己贡献什么,cordis.yml 负责把应用组合起来。

三种插件形态

函数是最常见的写法,但 Cordis 一共接受三种形态:

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

// 1. 函数插件:就是你刚写的那种
export function apply(ctx: Context) {}

// 2. 对象插件:一个带 apply 方法的对象
export const objectPlugin = {
  name: 'object-plugin',
  apply(ctx: Context) {},
}

// 3. 类插件:Service 子类
export class MyService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'myTutorialService')
  }
}

Cordis 接受的三种插件形态与选型规则

选择规则很直白:在需要对外公开一项服务之前,一直用函数形态;当你想在 ctx 上挂一个可被其他插件依赖的服务时,再改用继承 Service 的类形态。

故意把它弄崩:两种失败方式

看框架怎么失败,最能看清它的边界。第一种,让 apply 直接抛异常:

ts
export function apply(ctx: Context) {
  throw new Error('apply exploded')
}

再次运行,进程会因这个错误终止。也就是说,插件加载失败是明确报错、终止进程,而不是悄悄跳过这个配置项继续跑。

第二种失败更隐蔽:如果某个配置项的模块无法被解析,比如路径或包名拼错了,Cordis 会通过 logger 服务报告错误,但不会让进程崩溃。还有一个例外要尽早知道:在启动阶段,这条报告可能在 console 导出器开始观察之前就丢失了。所以当你新增一行配置却看不到任何效果时,先检查拼写,再怀疑别的。

写在最后

七行代码加一行配置,「插件如何被加载」这条主干就走完了。官方教程的后续章节继续往下延伸:生命周期与 effect 讲插件卸载时注册如何撤销,服务一章讲依赖注入,再往后是类型化事件、配置校验,最后接入真实的 harness,注册一个模型可调用的工具。对想给 DeepSeek Harness 写扩展的开发者来说,这一章是从读懂架构到写下第一行插件代码之间最短的路。

相关文章

分享: