具名能力与依赖驱动组合
依赖能力,不要绑定提供方。
提供方公开具名能力,消费方声明需求,Cordis 负责启动、替换与清理。
greeter.ts
import { Service, type Context } from '@deepseek-ai/cordis'
export class GreeterService extends Service {
constructor(ctx: Context) {
super(ctx, 'greeter')
}
greet(who: string) {
return `Hello, ${who}!`
}
}
服务是挂载在 ctx 上的具名能力
Harness 自身的工具运行时、模型运行时和 Agent 管理器都遵循同一模型。
ctx.toolsctx.llmctx.agentsHarness 内置能力组合边界
消费方只依赖 'tools' 或 'shell' 这样的能力名称,不导入具体实现。配置可以更换提供方,而消费代码保持不变。
运行时注册与编译时类型是两件事
Service 基类负责注册实例;TypeScript 声明合并让 ctx.greeter 在消费端获得类型。后者不会生成运行时代码。
运行时
super(ctx, 'greeter')以名称注册实例,卸载时自动移除编译时
interface Context声明合并提供类型安全,不改变运行时greeter.ts
declare module '@deepseek-ai/cordis' {
interface Context {
greeter: GreeterService
}
}
export function apply(ctx: Context) {
ctx.plugin(GreeterService)
}inject 是持续生效的硬性依赖
Cordis 等待所有依赖出现后才运行 apply。决定启动时机的是依赖关系,不是 cordis.yml 中的文件顺序。
consumer.ts
export const name = 'consumer'
export const inject = ['greeter']
export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}greeter 不存在PENDINGapply 不运行,不会部分启动
→greeter 就绪ACTIVEctx.greeter 保证可用
静默退出不一定是成功加载
PENDING Fiber 不会维持 Node 事件循环。如果组合里没有其他活动项,进程可能以状态码 0 静默退出。
替换提供方会干净地重启消费者
inject 不是启动时的一次检查。依赖消失时,消费插件及其 effect 会卸载;新提供方出现后再重新加载。
旧 shellconsumer ACTIVE
→提供方卸载consumer DISPOSED
→新 shellconsumer ACTIVE
例如卸载 dsh-bash-local,再挂载另一个 shell 实现。所有 inject: ['shell'] 的插件都会针对新实现完整重启。
能降级运行的能力不要放进 inject
可选依赖缺失时插件仍需运行,因此在真正使用的位置调用 ctx.get() 并处理 undefined。
必需
export const inject = ['tools']缺失时保持 PENDING可选
const metrics = ctx.get('metrics')缺失时得到 undefinedoptional.ts
export function apply(ctx: Context) {
const greeter = ctx.get('greeter')
const output = greeter?.greet('maybe')
?? 'no greeter available'
console.log(output)
}隔离组可以拥有同名服务的不同实例
在 cordis.yml 的插件组上隔离 shell 后,每组内的插件只看到自己的 Bash 实例与配置。
group-a
shell / timeoutMs: 5000plugin-a互不可见
group-b
shell / timeoutMs: 60000plugin-b命名约束
每个应用的服务名称共用一个扁平命名空间。Harness 已使用 tools、llm 等普通名称,自定义服务应采用有辨识度的名称或前缀。