Skip to content

插件配置

本章目标

  • 用「Config 类型 + 同名 Schemastery schema」声明插件配置,理解默认值归属
  • 读懂 cordis.yml 行结构(id / name / config)与覆盖规则
  • 理解 Requiresinject)语义:为什么部署必须同时加载服务提供者
  • 了解 loader 机制(schema 校验、默认值填充、!!js 插值)
  • 区分 settings.yaml 与凭据:机密不进 cordis.ymlredactSecrets 保护密钥
  • 完成带必填/可选字段的配置示例

声明 Config:类型 + schema

Cordis 插件通过两个同名导出声明配置:Config 接口(类型)与同名 Schemastery schema(运行时校验器)。默认值直接写在 schema 中;插件加载时 Cordis 用该 schema 校验并填充默认值,apply 收到的 config 已校验且类型安全:

ts
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

/** 配置类型:apply 收到的 config 就是它 */
export interface Config {
  /** 问候语模板,{name} 会被替换为被问候者的名字 */
  greeting: string
  /** 是否把问候语转成大写 */
  uppercase: boolean
}

/** 与类型同名的 Schemastery schema:默认值写在这里 */
export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  uppercase: Schema.boolean().default(false),
})

export function apply(ctx: Context, config: Config) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      let message = `${config.greeting}, ${args.name}!`
      if (config.uppercase) message = message.toUpperCase()
      return message
    },
  }))
}

注意:不要导出普通对象作为 Config——它不满足 Cordis 要求的 Standard Schema 接口。

cordis.yml 的行结构

每个插件行由三部分组成:

yaml
- insert:
    - id: greet
      name: './src/my-plugin.ts'
      config:
        greeting: 'Hi there'
        uppercase: true
  • id:部署内唯一的插件实例 id,也是 patch 覆盖的定位键;
  • name:插件入口。本地开发用相对源码路径,发布后写包名(如 '@deepseek-ai/dsh-tool-fs');
  • config:传给 apply 的配置对象,逐字段经 schema 校验。

patch 覆盖规则:后应用的行按 id 覆盖先前的行,且是整行替换(非深度合并)——覆盖时必须重述该行需要的每个键。

配置如何被读取

Schema 在插件加载时执行校验:配置不合法则加载失败并给出明确错误;通过后未提供的字段取默认值。修改 cordis.yml 中某插件的 config 会触发热替换(HMR):卸载旧实例、加载新实例;注册都是 effect,卸载时自动清理,不留旧注册。

Harness 的硬约定——无硬编码可调参数:凡不同部署取值可能不同的参数,必须定义为配置字段。检验标准:能否在 cordis.yml 中改值而不改代码?

Requires 语义:inject 服务键

inject: ['tools'] 声明插件依赖 tools 服务键。它既是时序约束(注册表就绪后再 apply),也是部署约束:配置树必须同时加载这些服务的提供者(官方配置目录的 Requires: 行正是这些键)。缺少提供者,插件就无法激活。

loader 的 !!js 插值

除静态值外,loader 允许配置项写 !!js 表达式:它挂载一次组合,等待每行普通注入,再基于该行已注入的上下文求值配置。两种常见用途:

yaml
- id: my-app
  name: '@example/my-app'
  inject: [myAppStartup]
  config:
    port: !!js ctx.myAppStartup.port ?? 8080   # 引用已注入的服务
    cwd: !!js process.env.PROJECT_DIR ?? process.cwd()  # 引用环境变量

这让同一份 patch 在不同环境落地不同取值,而无需维护多份配置文件。

settings.yaml 与凭据:机密不进 cordis.yml

harness 还有两个与配置相关的文件:

settings.yaml(位于 $DSH_HOME):用户可编辑的设置文档,按 namespace 分节。插件经 ctx.settings 注册 namespace,解析顺序为 schema 默认值 → 组合 base → 用户分节。组合配置仍留在 cordis.yml——namespace 只承载用户可编辑的子集。

.credentials.yaml$DSH_HOME/.credentials.yaml):凭据的真正存放处。cordis.yml 与 settings 里只出现凭据引用——一个环境变量名(CredentialRef),值归凭据提供方所有,消费方每次操作解析一次。轮换密钥只需更新存储,无需重启即可作用于下一次请求;空存储值在任何地方都视为不存在。例如:

yaml
- id: llm
  name: '@deepseek-ai/dsh-llm-deepseek'
  config:
    apiKeyEnv: MY_GATEWAY_KEY   # 只写环境变量名(CredentialRef),不写密钥本身

配置界面的兜底是 redactSecretsdescribe({ redactSecrets: true }) 会把 role('secret') 字段从返回的 value / base / user 中剥离,只枚举 {path, set} 槽位——页面因此能渲染只写输入框而永远收不到明文密钥;任何对外传输接口都必须传入该选项。

完整示例:必填 + 可选字段

需要严格校验时,让无效配置在加载时失败:

ts
export interface Config {
  apiKey: string
  timeout: number
  mode: 'fast' | 'accurate'
}

export const Config = Schema.object({
  apiKey: Schema.string().required(),          // 必填:缺失直接加载失败
  timeout: Schema.number().default(30000),     // 可选:默认值写在 schema
  mode: Schema.union(['fast', 'accurate']).default('fast'),
})

export function apply(ctx: Context, config: Config) {
  // config 已校验且类型安全
}

对应的 cordis.yml 行:

yaml
- insert:
    - id: validated
      name: './src/validated-plugin.ts'
      config:
        apiKey: !!js process.env.MY_API_KEY
        mode: accurate   # timeout 省略,取默认 30000

小结

  • 配置 = 同名 Config 类型 + Schemastery schema,默认值写在 schema,非法配置加载即失败;
  • cordis.yml 行由 id / name / config 组成,覆盖是整行替换;
  • injectRequires:部署必须加载对应服务提供者;
  • !!js 让配置项引用环境变量与已注入服务;
  • 密钥只以 CredentialRef(环境变量名)出现,值在 .credentials.yamlredactSecrets 保证界面只见槽位、不见明文。

下一步

  • 打包与安装插件:把本地插件发布为可安装的组合包(bundle)与 profile;
  • 官方插件配置目录(config-catalog):查看每个包的完整配置字段与默认值;
  • 能力分层:把可替换能力拆分为 Service Definition / Service Provider / Consumer。