编写最小可运行插件与四要素剖析

约 25 分钟

掌握插件的四大导出项与 Context 服务查找机制,编写并挂载第一个本地基础插件。

上一节结束时,你已经完成了 DeepSeek Harness (dsh) 本地开发环境的搭建与运行时验证:CLI 与 Web 控制台均确认可用,Cordis 内核驱动的“一切皆插件”运行机制也在这一过程中清晰呈现——模型适配器、工具注册表与智能体主循环,本质上都是挂载在上下文中的插件。环境就绪只是起点,接下来你要从“验证机制”转向“编写插件”,把这套机制真正握在自己手里。

要开发自己的智能体扩展,你首先需要掌握标准插件的代码结构。一个标准的 Harness 插件采用清晰的声明式规约,通过标准导出向运行时交付元数据、依赖项、配置模式与初始化逻辑。本节将深入拆解插件的四大核心要素,并阐明 Context 容器如何解耦插件与具体服务实现。

核心概念

插件标准四大导出 (name/inject/Config/apply)

【生活类比】想象你在组装一套智能家居扩展模块。模块外壳贴有设备名称标签(name),说明书上标明必须插入哪种供电底座与通信网关(inject),配电盒上附带电压调节旋钮与参数规范(Config),而模块通电插上底盘时触发的内部电路启动流程就是执行接入动作(apply)。

【技术定义】在 DeepSeek Harness 与 Cordis 体系中,一个标准的函数式插件模块遵循四要素导出契约:

  1. name(string,可选/推荐):插件的唯一标识符与显示名称,用于框架日志输出、依赖拓扑分析与调试诊断。
  2. inject(string[] 或声明对象,可选):插件所依赖的 Cordis 服务列表。Loader 会等待声明的服务全部就绪后再激活该插件。
  3. Config(Schema 对象,可选):插件的声明式配置模式(通常基于 Schemastery 定义),用于校验用户输入并生成默认配置。
  4. apply(ctx, config)(函数,必需):插件的生命周期入口函数。框架挂载插件时调用此函数,传入当前隔离上下文 ctx 和解析后的配置对象 config。

核心概念

Context 容器与内置服务注入 (ctx.tools / ctx.llm)

【生活类比】Context 就像工匠随身携带的标准化多功能工具箱。工匠不需要知道螺丝刀具体由哪家钢铁厂制造,只要工具箱里有标注为“螺丝刀”的卡槽,工匠伸手就能拿到符合规格的工具并使用。不同工匠还可以向工具箱注册新的专用夹具。

【技术定义】Context(上下文容器,通常简写为 ctx)是 Cordis 的核心对象,充当统一的服务注册表与生命周期管理者。在 Harness 运行时中:

  1. 服务以稳定的 key 挂载在 Context 实例上,例如 ctx.tools(工具注册服务)、ctx.llm(模型调用服务)、ctx.sessions(会话管理服务)与 ctx.logger(日志记录服务)。
  2. 插件之间不通过具体的 ES 模块路径直接 import 相互的具体实现类,而是直接在 apply(ctx) 中通过 ctx.<service_name> 访问依赖。
  3. 这种基于 key 的服务查找机制实现了服务提供方(Provider)与服务消费方(Consumer)的彻底解耦,使得模型适配器、工具分发管道等核心组件均可被配置化替换。

下面是一个最小可运行的 TypeScript 插件示例。它展示了四大导出的实际编写方式:

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)并挂载该文件时:

  1. Loader 首先解析导出的 name 与 Config,校验外部传入的配置参数并赋予默认值。
  2. Loader 检查 inject 列表,确保 ctx.logger 服务已经在当前上下文树中完成注册。
  3. 当依赖满足后,调用 apply(ctx, config) 执行逻辑,输出带有指定前缀的就绪日志。
自检:为什么在编写 DeepSeek Harness 插件时,调用其他服务(如 ctx.tools)推荐使用 inject 声明依赖,而不是直接 import 其他插件的实现代码?

使用 inject 声明依赖可以实现服务消费者与具体实现类的解耦。Context 作为服务的统一仓库,插件只需要面向 ctx 上的稳定接口编程,而无需关心提供该服务的是哪个具体的 npm 包或适配器。同时,inject 让 Loader 能够自动构建拓扑加载图,确保服务就绪后再执行 apply,避免加载时序错误。