DeepSeek Harness插件开发入门指南:从零到配置化工具的重点在于把前置条件、操作顺序和容易误判的地方分清楚。
DeepSeek Harness 是一个基于 Cordis 插件框架的 Agent 运行时环境。在 Harness 中,一切皆插件——从工具注册到 UI 渲染,从 LLM 调用到会话持久化,所有能力都以插件形式存在。下文会带你从零开始,逐步掌握插件开发的核心技能:

在仓库根目录下创建 scratch-plugin 目录:
scratch-plugin/├── cordis.yml└── src/ └── my-plugin.ts
// src/my-plugin.tsimport type { Context } from '@deepseek-ai/cordis'export const name = 'hello-plugin'export function apply(ctx: Context) { console.log('[hello-plugin] plugin loaded!') ctx.effect(() => { const timer = setInterval(() => { console.log('[hello-plugin] heartbeat') }, 5000) return () => clearInterval(timer) })}关键点:
name 和 apply 函数ctx.effect() 用于注册副作用,插件卸载时会自动调用返回的清理函数# scratch-plugin/cordis.yml- insert: - id: hello name: 'file:///d:/codes/DeepSeek-Harness/scratch-plugin/src/my-plugin.ts'
Windows 注意:ESM 模块加载要求路径使用 file:// 协议格式,不能使用 d: 这样的裸路径。
pnpm dsh web --patch ./scratch-plugin/cordis.yml
打开 http://127.0.0.1:3080,终端中会打印 [hello-plugin] plugin loaded! 和每隔 5 秒的心跳日志。
仅有心跳日志的插件并不实用。要让插件在对话框中"可用",需要注册一个工具(Tool)——即 Agent 在对话过程中可以调用的能力。
// src/my-plugin.tsimport type { Context } from '@deepseek-ai/cordis'import { defineTool } from '@deepseek-ai/dsh-tools'export const name = 'greet-tool'export const inject = ['tools']export function apply(ctx: Context) { 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) { return `Hello, ${args.name}!` }, }))}关键点:
inject = ['tools'] 声明依赖,Cordis 会确保工具注册表就绪后再加载插件defineTool 根据 parameters 自动推导并校验参数类型execute 是实际执行逻辑,output.render 负责将结果转换为模型可读的内容启动后在对话框输入:
Use the greet tool to greet Ada.
模型会自动调用 greet 工具,传入 name: "Ada",工具返回 Hello, Ada!。
硬编码的 Hello 不够灵活。Harness 的约定是:凡是不同部署可能需要采用不同值的参数,都必须定义为配置字段。
// src/my-plugin.tsimport 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']export interface Config { greeting: string}export const Config: Schema = Schema.object({ greeting: Schema.string().default('Hello'),})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) { return `${config.greeting}, ${args.name}!` }, }))}# scratch-plugin/cordis.yml- insert: - id: hello name: 'file:///d:/codes/DeepSeek-Harness/scratch-plugin/src/my-plugin.ts' config: greeting: 'Hi there'
现在工具会返回 Hi there, Ada!。如果把 greeting 改成 What's up,则返回 What's up, Ada!。
你可能好奇:如果我在 cordis.yml 里把 greeting 写成数字,会怎样?
答案是:插件加载会直接失败。
Cordis 在加载插件时,会读取插件导出的 Config schema,调用其 ~standard.validate() 方法进行校验(位于 vendor/cordis/src/fiber.ts):
export function resolveConfig(runtime: Plugin.Runtime, config: any) { if (!runtime.Config) return config const result = runtime.Config['~standard'].validate(config) if (result.issues) { throw new ValidationError(result.issues) // 校验失败 → 插件加载失败 } else { return result.value // 校验通过 → 返回处理后的值 }}| 输入值 | 校验结果 | config.greeting |
|---|---|---|
'Hi there' | ✅ 通过 | 'Hi there' |
| 不填 | ✅ 使用默认值 | 'Hello' |
123 | ❌ 类型不匹配 | 插件加载失败 |
不填且没有 .default() | ❌ 缺少必填字段 | 插件加载失败 |
这种配置错误要响亮的设计,确保问题在启动阶段就被暴露,而不是在运行时才发现。
由于 scratch-plugin 不在任何 TypeScript 项目中,IDE 可能报错:
找不到模块 "@deepseek-ai/cordis" 或其相应的类型声明。
在 scratch-plugin 目录下创建 tsconfig.json:
{ "extends": "../tsconfig.base.json", "compilerOptions": { "composite": false, "incremental": false, "paths": { "@deepseek-ai/cordis": ["../vendor/cordis/src"], "@deepseek-ai/schemastery": ["../vendor/schemastery/src"], "@deepseek-ai/dsh-tools": ["../packages/core/tools/src"] } }, "include": ["src/**/*"]}继承项目的 tsconfig.base.json 并补充路径映射,IDE 就能正确解析类型了。
scratch-plugin/├── cordis.yml # 插件注册 + 配置├── tsconfig.json # TypeScript 配置└── src/ └── my-plugin.ts # 插件代码
| 步骤 | 内容 | 关键 API |
|---|---|---|
| 1 | 创建插件骨架 | export name, export apply(ctx) |
| 2 | 注册工具 | ctx.tools.register(defineTool(...)) |
| 3 | 添加配置 | export interface Config, export const Config = Schema.object(...) |
| 4 | 解决类型检查 | tsconfig.json 继承根配置并添加 paths |