插件配置走"双导出":类型(TypeScript 用)+ 同名的 Schema(运行时校验)。改写 L13 的插件:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
export const name = 'greet-tool'
// ① 类型:给 TypeScript 看的合同
export interface Config {
greeting: string
maxRetries: number
verbose?: boolean
}
// ② Schema:给运行时校验用,默认值直接写在字段上
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
maxRetries: Schema.number().default(3),
verbose: Schema.boolean().default(false),
})
export function apply(ctx: Context, config: Config) { // ← 第二个参数
console.log(config.greeting) // 用户值或 schema 默认值
}
然后在 cordis.yml 里传配置:
- insert:
- id: hello
name: '/绝对路径/scratch-plugin/src/my-plugin.ts'
config:
greeting: '你好'
maxRetries: 5
export const Config = Schema.object({
apiKey: Schema.string().required(), // 必填:缺了加载就失败
timeout: Schema.number().default(30000),
mode: Schema.union(['fast', 'accurate']).default('fast'), // 只准二选一
})
校验发生在加载时:配置非法,插件当场加载失败并给出可操作的报错——而不是带着错误配置跑起来,半夜再炸。
const TIMEOUT = 30000 // 换个环境就得改代码
export interface Config {
timeoutMs: number // 默认 30000,yaml 可改
}
注意边界:协议常数、外部规范、安全不变量不是"可调参数",就该写死(比如 HTTP 状态码的含义不会因部署而异)。仓库对"一个 DEFAULT_* 常量就算可配置"的说法明确说不——配置必须在 Config 字段上。
自包含的错误(缺字段、类型不对、枚举外取值)→ 写进 schema,加载时就失败;引用性的错误(配置指向不存在的服务/资源)→ 在最早能解析的时点失败;绝不静默跳过缺失的引用——"忍一忍继续跑"把错误推迟到最难排查的地方。像登机安检:能当场查的(证件类型)当场查;要联网核实的(签证状态)在登机口前必须出结果;绝不能"看着像没事就放行"。
仓库推崇的进阶写法(dsh-shell 是模板):默认值不藏在执行逻辑的 ?? 里,而是集中在一个显式的 resolve(request): Spec 步骤:
// 请求(可能不完整)→ resolve 补全 → Spec(完整明确)→ run 只管执行
function resolve(request: Request): Spec {
return { timeoutMs: request.timeoutMs ?? 30000, cwd: request.cwd ?? homedir() }
}
async function run(spec: Spec) { /* 只见完整 Spec,无默认值逻辑 */ }
好处:默认值的决策集中可见——要找"这个参数默认是多少",看 resolve 一处即可。这是"显式 > 隐式"约定在配置上的落点。
改配置 = 热替换插件:旧实例卸载(注册全部回收)→ 新实例带新配置加载。因为"注册即效果",替换不会残留旧实例的任何注册。调参不用重启——改完 cordis.yml 保存即可。
cordis.yml 的 config 和 disabled 两个字段允许 !!js 表达式(两个感叹号),在插件上下文里求值:
- id: my-app
name: '@example/my-app'
config:
port: !!js ctx.myAppStartup.port ?? 8080 # 运行时取启动服务给的端口
disabled: !!js ctx.env.name === 'ci' # CI 环境不挂我
其余元数据(id、name、inject 等)必须字面量;环境相关的组合优先用这种覆盖层表达。
greeting 配置(默认 'Hello'),在 cordis.yml 里改成 '你好',验证输出变化。required() 的字段,故意不在 yaml 里提供,观察加载失败时的报错(体会 fail loud)。style: Schema.union(['formal','casual']),传非法值 style: 'yolo',确认被拒。