掌握插件的四大导出项与 Context 服务查找机制,编写并挂载第一个本地基础插件。
上一节结束时,你已经完成了 DeepSeek Harness (dsh) 本地开发环境的搭建与运行时验证:CLI 与 Web 控制台均确认可用,Cordis 内核驱动的“一切皆插件”运行机制也在这一过程中清晰呈现——模型适配器、工具注册表与智能体主循环,本质上都是挂载在上下文中的插件。环境就绪只是起点,接下来你要从“验证机制”转向“编写插件”,把这套机制真正握在自己手里。
要开发自己的智能体扩展,你首先需要掌握标准插件的代码结构。一个标准的 Harness 插件采用清晰的声明式规约,通过标准导出向运行时交付元数据、依赖项、配置模式与初始化逻辑。本节将深入拆解插件的四大核心要素,并阐明 Context 容器如何解耦插件与具体服务实现。
核心概念
插件标准四大导出 (name/inject/Config/apply)
【生活类比】想象你在组装一套智能家居扩展模块。模块外壳贴有设备名称标签(name),说明书上标明必须插入哪种供电底座与通信网关(inject),配电盒上附带电压调节旋钮与参数规范(Config),而模块通电插上底盘时触发的内部电路启动流程就是执行接入动作(apply)。
【技术定义】在 DeepSeek Harness 与 Cordis 体系中,一个标准的函数式插件模块遵循四要素导出契约:
name(string,可选/推荐):插件的唯一标识符与显示名称,用于框架日志输出、依赖拓扑分析与调试诊断。inject(string[] 或声明对象,可选):插件所依赖的 Cordis 服务列表。Loader 会等待声明的服务全部就绪后再激活该插件。Config(Schema 对象,可选):插件的声明式配置模式(通常基于 Schemastery 定义),用于校验用户输入并生成默认配置。apply(ctx, config)(函数,必需):插件的生命周期入口函数。框架挂载插件时调用此函数,传入当前隔离上下文ctx和解析后的配置对象config。
核心概念
Context 容器与内置服务注入 (ctx.tools / ctx.llm)
【生活类比】Context 就像工匠随身携带的标准化多功能工具箱。工匠不需要知道螺丝刀具体由哪家钢铁厂制造,只要工具箱里有标注为“螺丝刀”的卡槽,工匠伸手就能拿到符合规格的工具并使用。不同工匠还可以向工具箱注册新的专用夹具。
【技术定义】Context(上下文容器,通常简写为 ctx)是 Cordis 的核心对象,充当统一的服务注册表与生命周期管理者。在 Harness 运行时中:
- 服务以稳定的 key 挂载在 Context 实例上,例如
ctx.tools(工具注册服务)、ctx.llm(模型调用服务)、ctx.sessions(会话管理服务)与ctx.logger(日志记录服务)。 - 插件之间不通过具体的 ES 模块路径直接
import相互的具体实现类,而是直接在apply(ctx)中通过ctx.<service_name>访问依赖。 - 这种基于 key 的服务查找机制实现了服务提供方(Provider)与服务消费方(Consumer)的彻底解耦,使得模型适配器、工具分发管道等核心组件均可被配置化替换。
下面是一个最小可运行的 TypeScript 插件示例。它展示了四大导出的实际编写方式:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
// 1. 插件标识名,用于诊断与日志
export const name = 'custom-greeter'
// 2. 声明服务依赖:只有当 logger 服务存在时才执行 apply
export const inject = ['logger']
// 3. 配置定义与类型声明
export interface Config {
greetingPrefix?: string
}
export const Config: Schema<Config> = Schema.object({
greetingPrefix: Schema.string().default('Hello, Harness!'),
})
// 4. 插件入口函数
export function apply(ctx: Context, config: Config) {
ctx.logger.info(`${config.greetingPrefix} Plugin successfully mounted.`)
}当 Cordis Loader 读取配置文件(如 cordis.yml)并挂载该文件时:
- Loader 首先解析导出的
name与Config,校验外部传入的配置参数并赋予默认值。 - Loader 检查
inject列表,确保ctx.logger服务已经在当前上下文树中完成注册。 - 当依赖满足后,调用
apply(ctx, config)执行逻辑,输出带有指定前缀的就绪日志。
在传统脚本式框架中,插件的启动顺序由配置文件中书写的顺序强行决定,容易导致由于调整顺序引发的“未定义引用”崩溃。在 DeepSeek Harness 中,inject 改变了这一模式:插件加载顺序完全由声明的服务需求推导(Spatial Composability)。如果插件 A 声明了 inject: ['tools'],无论插件 A 在配置文件中排在第几行,框架都会等待提供 tools 服务的插件就绪后,再触发 A 的 apply 函数。
自检:为什么在编写 DeepSeek Harness 插件时,调用其他服务(如 ctx.tools)推荐使用 inject 声明依赖,而不是直接 import 其他插件的实现代码?
使用 inject 声明依赖可以实现服务消费者与具体实现类的解耦。Context 作为服务的统一仓库,插件只需要面向 ctx 上的稳定接口编程,而无需关心提供该服务的是哪个具体的 npm 包或适配器。同时,inject 让 Loader 能够自动构建拓扑加载图,确保服务就绪后再执行 apply,避免加载时序错误。