
改行为只能改代码?
代码不变,行为可变
配置机制实战
Config · Schemastery · !!js
DSH · Cordis 学习系列
📦 5 Parts + Conclusion
👉 滑动
PART 01
会问好的插件
config-demo.ts
PART 02
在 YAML 中喂配置
默认值补齐
PART 03
配置错误不启动
FAILED 状态
PART 04
动态配置 !!js
环境变量
PART 05
配置 vs 服务
边界在哪
PART ///
写在最后
五课知识树
没有配置的插件,就像一个“被焊死的工具箱”
有了配置,它就变成了“可调扳手”——干同样的活,却不做同样的事。
在第四课中,我们学会了用“事件”让插件之间松耦合地通信——你发一条消息,谁关心谁就来听,不需要互相盯着。这让我们插件的协作方式有了质的飞跃。
但还有一个问题困扰着我们的插件:太死板了。
比如上一课的stats服务,它统计了tool_call和prompt的次数,并发出stats/report事件。但如果我们想让它在不同环境下统计不同的事件名称呢?或者我们希望它有时打印到控制台,有时写入文件?
难道要为了这点差异,再复制一份代码改改?
没必要。Cordis提供了一套配置机制,让插件可以通过cordis.yml文件接收外部参数,从而在不同场景下展现出不同的行为。代码不变,行为可变。
今天这一课,我们就来学习如何给插件加上“可调旋钮”。
01
PART
从一个“会问好”的插件开始
FIRST CONFIG PLUGIN
我们先写一个简单但可配置的插件,它的任务是:对着一群人打招呼。
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']),
})
// apply函数多了一个参数:config
export function apply(ctx: Context, config: Config) {
for (const target of config.targets) {
console.log(`${config.greeting}, ${target}!`)
}
}
这里有两个关键点:
export interface Config:这只是给TypeScript看的,让编辑器有类型提示。
export const Config: Schema<...>:这是运行时校验规则。它告诉Cordis:“我期望的配置长这样,如果不符合,就别加载我。”
Schemastery是Cordis使用的配置校验库,它支持:
基本类型:string()、number()、boolean()
复合类型:object({ ... })、array(...)
默认值:.default('Hello')
验证条件:.required()、.min(0)
02
PART
在 cordis.yml 中提供配置
FEED CONFIG VIA YAML
上面写了插件,现在我们需要在cordis.yml中给它“投喂”配置。
创建一个新的cordis.yml(或者替换之前的内容):
- name: './config-demo.ts'
config:
targets: ['alpha', 'beta']
运行命令:
node --import tsx ../../vendor/cordis/bin.js
输出:
Hello, alpha!
Hello, beta!
发生了什么?
Cordis加载插件时,会先读取YAML中的config块。
将config内容传给Schemastery验证器,与插件声明的Config进行比对。
缺少的字段(greeting)被默认值补齐。
完整的、经过验证的配置对象被传给apply(ctx, config)。
插件正常运行。
你可以把它想象成餐厅点餐:
插件是“菜单”(定义了能点什么菜,比如targets是“要跟谁打招呼”)
cordis.yml是“点菜单”(选好要哪些菜,比如['alpha', 'beta'])
如果某道菜没选(比如greeting没写),餐厅会给一个“默认做法”(default('Hello'))
03
PART
配置错误时,插件不会“硬着头皮上”
VALIDATION FAILS LOUD
这才是配置机制最有价值的地方:如果配置不合法,插件根本不会启动。
比如我们把cordis.yml改成这样(把targets从数组改成了字符串):
- name: './config-demo.ts'
config:
targets: 'not-an-array'
运行后,你会得到一个清晰的错误信息:
ValidationError: invalid config:
- $.targets expected array but got not-an-array (at targets)
最关键的是:插件不会进入apply函数,甚至不会加载,它的fiber会直接进入FAILED状态。这保证了你的插件逻辑永远在配置正确的前提下运行,你不需要在apply函数里再做一堆if检查来防御错误配置。
这种“错误及早暴露”的设计,能让你在开发阶段就发现配置问题,而不是在运行时莫名其妙地出现空指针。
04
PART
进阶用法:使用 !!js 动态计算配置值
DYNAMIC CONFIG · !!JS
有时候,配置值需要从环境变量读取,或者根据条件动态计算。Cordis的loader支持在YAML中使用!!js标签来执行JavaScript表达式。
示例:从环境变量读取打招呼用语
- name: './config-demo.ts'
config:
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
targets: ['alpha', 'beta']
如果运行前设置了环境变量DEMO_GREETING=Hi,输出就会变成:
Hi, alpha!
Hi, beta!
如果没设置,默认值 ‘Hello’ 仍然会生效。
!注意 🕳
!!js 表达式只在 config 块和条目的 disabled 字段中有效。你不能在 name、id 等元数据字段中使用它。
这种能力对于“同一个插件包,部署在不同环境”(开发/测试/生产)时非常有用——你不需要修改cordis.yml文件本身,只需通过环境变量注入不同的值。
05
PART
深入思考:配置与“服务”的区别
CONFIG VS SERVICE
你可能会有个疑问:config和服务(Service)都能向插件传递外部信息,它们的边界在哪里?
我的理解是:
| 启动时确定 | 运行时随时可调用 | |
| cordis.yml | ||
config 是“给插件的初始参数”,服务是“插件可调用的动态能力”。
///
LAST
写在最后
CONCLUSION
这一课我们学习了Cordis的配置机制。通过config块和Schemastery校验,我们让插件从“焊死的黑箱”变成了“可调参数的工具箱”。
回顾前五课的知识树:
第一课:插件是什么——导出一个apply函数。
第二课:如何善后——用effect管理资源清理。
第三课:如何协作——用Service提供能力,用inject消费能力。
第四课:如何通知——用Event广播消息。
第五课:如何定制——用Config接收外部参数。
下一课,我们将进入一个更宏大但也更实用的主题——组合与热重载(HMR),把分散的插件通过cordis.yml组织成一个完整的应用,并在修改配置时无需重启就能生效。
最后问你一个问题:假设你正在写一个DSH插件,它需要连接数据库。你会把数据库连接字符串放在config里,还是通过Service提供?为什么?
欢迎在评论区分享你的想法。点赞最高的3个答案,我会在下期进行剖析。
我是violetdream,热衷于分享 AI 观察与干货。
关注本公众号,下周更新【Cordis学习第六课】——组合与热重载:让插件像乐高一样拼装,像手机App一样热更新。
既然看到这里了,如果觉得有用,随手点个赞、在看、转发三连吧。
THANKS FOR READING
夜雨聆风