首页 › LLM大语言模型 › Deepseek › DeepSeek Harness 中文教程 › 第 13 章:如何扩展 dsh

第 13 章:如何扩展 dsh

这一章把前面各章的知识变成可执行步骤。完整的分步指南在 docs/cookbook/;这里给出「该选哪个扩展点、最小代码长什么样、下一步读哪个 cookbook」。​

13.1 先选对扩展点​

第 1 章的「新行为该放哪」表是起点。核心原则:新行为挂到文档化的扩展点上,改动 agent 循环本身才需要更新架构图。常用映射(详见扩展 cookbook 的特性→机制表):​

•

加模型提供方 → LlmAdapter 子类 + registerAdapter。​

•

加模型能力 → ctx.tools.register();schema 自动进入 prompt 组装。​

•

拦截工具/请求/turn → tools/pre-execute、agent/pre-step、agent/request、agent/turn-stopping。​

•

加人类命令 → ctx.commands。​

•

加后台工作 → ctx.jobs + job_* 工具。​

•

加模型可见上下文 → agent.inject()。​

•

加 UI/编辑器集成 → 驱动 ctx.agents、从 session/event 渲染。​

13.2 加一个工具​

工具注册在 ctx.tools 上。defineTool 是类型化 helper(raw JSON-Schema ToolDefinition 也能直接 register,MCP 工具就是这么进来的)。最小形态:​

​

代码块​

TypeScript

import { readFile } from 'node:fs/promises'​

import type { Context } from '@deepseek-ai/cordis'​

import { defineTool } from '@deepseek-ai/dsh-tools'​

​

export const name = 'my-tool'​

export const inject = ['tools']​

​

export function apply(ctx: Context) {​

ctx.tools.register(defineTool({​

name: 'read_file',​

description: 'Read a file from disk.', // 模型看到什么​

parameters: {​

path: { type: 'string', required: true, description: 'Absolute path' },​

limit: { type: 'number' }, // 缺省即 optional​

},​

output: {​

schema: { type: 'string' },​

render: (_args, value) => [{ type: 'text', text: value }],​

},​

async execute(args, exec) {​

// args 从 schema 类型化:{ path: string; limit?: number }​

// exec 携带不可变身份 + token;signal 是可操作字段​

return readFile(args.path, { encoding: 'utf8', signal: exec.signal })​

},​

}))​

}​

​

execute() 契约的关键规则(详见工具编写参考):​

•

args 已为你校验:defineTool 在 execute 前按 ParameterSchemaSpec 校验模型生成的 arguments。DSL 表达不了的约束(非空、正数、跨字段)仍需你手工检查。​

•

执行身份受保护:注册表把 arguments 物化为分离的 lossless JSON、在策略开始前冻结、分配不透明 exec.token;callId/name/arguments/agent/token/signal 在派发中不可变。​

•

声明并返回一个规范 JSON 值:output.schema 用 ValueSchemaSpec;execute 只返回推断值,注册表快照、校验、冻结后交给 output.render。​

•

抛错或返回无效值 = isError:基础设施故障抛错;非零退出码这类「正常领域结果」放进规范值。​

•

遵守 exec.signal:触发时取消在途工作。​

•

异步通知用 exec.agent:agent.inject({ content, source: { kind: 'plugin', plugin: '' } }) 追加持久上下文供下一个模型请求看到——它不是唤醒。​

长时工作:用 producer 配置门控 run_in_background,经 ctx.jobs.start({ kind, label, owner: exec.agent, run }) 注册;成功返回 { kind: 'background', jobId } 这类规范 handle。​

Code Mode 免费拿到你的工具:每个可见工具都变成 await tools.(args),无需额外集成。​

UI 卡片:output.render 是模型面内容;UI 卡片由 presentCall/presentResult 决定(generic/terminal/diff/search/web 意图)。它们是 args 的纯函数,必须能在回放时重跑。​

13.3 加一个 hook / 策略插件​

hook 插件是拦截点上的普通 Cordis 插件。权限门例子——从 tools/pre-execute 门返回类型化决定:​

​

代码块​

TypeScript

import type { Context } from '@deepseek-ai/cordis'​

import type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'​

​

declare function isAllowed(exec: ToolExecution): Promise​

​

export const name = 'permission-gate'​

​

export function apply(ctx: Context) {​

ctx.on('tools/pre-execute', async (exec, next): Promise => {​

if (!(await isAllowed(exec))) {​

return { kind: 'deny', reason: 'Denied by policy.' }​

}​

return next()​

})​

}​

​

扩展点选择:tools/pre-execute 做可扩展 allow/deny/ask;ctx.tools.guard() 做后面监听器无法撤销的单调终态 deny;tools/execute 包裹实际派发生命周期(超时/重试/指标);tools/post-execute 做显式结果改写;tools/result 观察不可变终态。​

13.4 加一个 UI 插件或 Chat 节点​

UI 插件从 session/event 流渲染(assistant token 流是 assistant/chunk,加上 turn/step 边界与工具活动),经 agent.followup() / agent.steer() 把输入送回。浏览器插件给内置 Web Client 贡献业务行时,注册一个 ConversationNodeDefinition + keyed Chat 渲染器(见 Conversation Node 指南)。​

13.5 加一个协议驱动​

协议驱动把 wire peer 适配到 ctx.agents。stdio 驱动拥有 stdout、经工厂创建/resume agent、把协议请求映射到 followup()/cancel()。拆 agent 用 AgentHandle.dispose()(停 + 等退出,到达 quiescence)。packages/acp/acp 是「仅自动化」的完整范例。​

13.6 加一个 LLM 适配器​

子类化 LlmAdapter、实现 stream()、ctx.llm.registerAdapter(providers, adapter) 注册 provider 路由。dsh-llm-deepseek、dsh-llm-pi-ai 是随附范例。完整步骤见 LLM 适配器指南。​

13.7 加一个 provider(能力接缝)​

给既有接缝加 provider,就是继承 Service Definition 的抽象服务、实现其方法、注册到对应的 ctx.。以 shell 为例:继承 ShellExecutor(run/start/sandboxMode)并注册到 ctx.shell——bash-local、bash-sandbox、pwsh-local 都是这么做。要新建一个完整接缝,则需同时设计三角色:Service Definition + 至少一个 Provider + 至少一个 Consumer。​

13.8 加一个新包​

新包遵循统一的命名与布局规则,详见加包 checklist。要点:包名 @deepseek-ai/dsh-;src/types.ts 只放类型;测试在包级 tests/;每包拥有 ./invariant(注册 manifest 名,检查事件/数据关系或给 No runtime invariant: 理由);README 记录模型/token/KV-cache 效果;注册进恰好一个 tsconfig 聚合。​

下一步​

写完代码只是开始——dsh 有严格的测试与文档纪律。最后一章第 14 章:开发、测试与文档讲清工作流、测试策略与文档规范,以及该跑哪些检查。​