
字节笔记本
2026年10月6日 · 约 7 分钟读完
Cordis 教程 5:插件配置与 Schema 校验
这是 Cordis 插件开发系列的第 5 篇,主题是配置:插件如何从 cordis.yml 拿到一份经过校验的配置,以及配置出错时系统如何反应。Cordis 是开源项目 deepseek-harness 底层的插件框架,工具、模型适配器、文件访问乃至 agent 主循环,全部以插件的形式挂载到同一个上下文上。插件一多,配置就成了绕不开的问题,这一篇给出的答案非常干脆:配置可以随便写,但必须过校验。
配置先过校验,插件不带病启动
cordis.yml 里的每个条目都可以携带一个 config 块,插件则在代码里声明一个 schema。加载器在调用 apply 之前,先用这个 schema 校验 config 块:校验通过,配置才会被交给插件;校验失败,加载直接终止,并给出精确到字段的报错。这套机制的核心承诺是一句话:插件绝不会在配置不完整的状态下启动。配置错误因此从「运行到一半才暴露的隐患」变成了「加载阶段就被拦下的显式错误」。

一个可配置的插件
在 tmp/cordis-tutorial 目录下创建 config-demo.ts:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
export const name = 'config-demo'
export interface Config {
greeting: string
targets: string[]
}
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
targets: Schema.array(String).default(['world']),
})
export function apply(ctx: Context, config: Config) {
for (const target of config.targets) {
console.log(`${config.greeting}, ${target}!`)
}
}关键在导出的 Config:它既是 TypeScript 接口,又是同名的运行时 schema。消费方拿到类型,编译期就能发现字段写错;Cordis 拿到校验器,运行期能拦下非法值。一份声明,两头受益。这个教程仓库用 Schemastery 来定义 schema,但 Cordis 本身接受任何符合 Standard Schema 规范的验证器;反过来说,只导出一个普通对象当作 Config 是行不通的,插件声明的必须是真正能执行校验的东西。
默认值补齐,apply 拿到的配置永远完整
在 cordis.yml 里挂载它:
- name: './config-demo.ts'
config:
targets: ['alpha', 'beta']运行后输出:
Hello, alpha!
Hello, beta!注意 greeting 并没有出现在配置里,是 schema 里的 default('Hello') 把它补齐了。也就是说,apply 拿到的 config 永远是完整的:每个字段要么来自 YAML,要么来自默认值。插件内部不需要再写「字段缺失怎么办」的兜底分支,配置处理被压缩成一件事:信任输入,直接用。
坏配置当场失败
如果把配置写错,比如把数组写成了字符串:
- name: './config-demo.ts'
config:
targets: 'not-an-array'加载阶段就会得到这样的报错:
ValidationError: invalid config:
- $.targets expected array but got not-an-array (at targets)报错信息直接给出字段路径:$.targets 期望数组,实际拿到的却是字符串。此时插件对应的 fiber 进入 FAILED 状态,教程的启动器打印错误后以状态码 1 退出。系统不会带着一份残缺配置继续跑,错误被按在了启动之前。

教程还强调了配套的一条纪律:schema 只能挡住结构错误的配置,如果配置本身合法,但其中引用的资源或提供方当前不可用,插件也应当在能够解析这个引用的时刻立刻拒绝启动。失败要趁早,能今天暴露的问题绝不留到运行时。
!!js:写在配置里的加载期计算值
有些配置值必须在加载时计算,比如从环境变量读取。教程所用的 loader 支持 !!js 标签,后面跟一段 JavaScript 表达式,求值结果作为配置值:
- name: './config-demo.ts'
config:
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'这是很典型的环境相关配置写法:环境变量里有值就用环境变量,没有就回落到默认值。
!!js 的适用范围有明确边界:只能出现在条目的 config 块和 disabled 字段里。disabled 字段的表达式会在每次挂载决策时基于 loader 上下文重新求值,所以一行插件可以按平台或环境决定自己是否启用;而 name、id、inject 等其余元数据保持静态,写在里面的表达式只会被当成普通的真值数据,不会被求值。动态的部分被刻意收窄到两个位置,其余一律静态,这份克制正是配置系统不失控的关键。
写在最后
这一篇的配置方案可以压缩成三条:配置进 YAML,校验进 schema,失败要趁早。schema 同时充当类型声明和运行时校验器,默认值保证 apply 的输入永远完整,校验失败则用精确到字段的报错和 FAILED 状态把问题拦在启动之前。对任何想给自己的框架加配置层的项目来说,这都是一套值得照抄的顺序。



