前言
DeepSeek Harness 是基于 Cordis 插件框架构建的 Agent 运行时环境。在 Harness 中,“一切皆为插件”——从工具注册到 UI 渲染,从 LLM 调用到会话持久化,几乎所有能力都以插件形式提供。本文将带你从零开始,循序渐进掌握 DeepSeek Harness 插件开发的核心方法:

- 创建第一个插件
- 将插件升级为可调用工具
- 添加插件配置(Config)并实现 Schemastery 校验
- 解决 Windows 开发环境下的常见问题
一、创建第一个插件
目录结构
先在仓库根目录下新建 scratch-plugin 目录:
scratch-plugin/
├── cordis.yml
└── src/
└── my-plugin.ts
插件入口文件
// src/my-plugin.ts
import 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函数,这是 DeepSeek Harness 插件的基本入口约定 ctx.effect()用于注册副作用,插件卸载时会自动执行返回的清理函数- 所有已注册的资源(例如事件监听、定时器等)都会在插件卸载时自动清理,便于维护运行时稳定性
注册到 cordis.yml
# 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
打开 https://127.0.0.1:3080 后,终端会输出 [hello-plugin] plugin loaded!,并且每隔 5 秒打印一次心跳日志,说明插件已经成功运行。
二、将插件升级为工具(Tool)
只有心跳日志的插件实际价值有限。要让插件真正能在对话界面中被 Agent 调用,就需要把它注册成一个工具(Tool),也就是对话过程中可执行的能力。
工具插件代码
// src/my-plugin.ts
import 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自动推导参数结构并执行类型校验,适合快速开发 DeepSeek Harness 工具插件execute是工具的核心执行逻辑,而output.render负责把返回结果转换为模型可读内容
在对话框中使用
启动后,在对话框中输入:
Use the greet tool to greet Ada.
此时模型会自动调用 greet 工具,并传入 name: "Ada",最终工具返回 Hello, Ada!。
三、添加插件配置:让问候语可配置
将 Hello 直接写死在代码里不够灵活。DeepSeek Harness 的推荐做法是:凡是不同部署环境中可能变化的参数,都应该定义为配置字段,这样更便于维护和复用。
定义 Config
// src/my-plugin.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']
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}!`
},
}))
}
在 cordis.yml 中传入配置
# 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!,从而实现配置化问候语。
四、Schemastery 的严格校验机制
你可能会问:如果在 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() | ❌ 缺少必填字段 | 插件加载失败 |
这种配置错误尽早暴露的设计思路,可以确保问题在启动阶段就被发现,而不是等到运行时才定位异常,十分适合生产环境中的插件开发与配置管理。
五、解决 IDE 类型检查问题
由于 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 |
