跳转至

06 · 附录:资源、HOWTO 与结语

Python SDK 驱动 harness、添加插件与贡献、常见 HOWTO 与参考表。

9. 用 Python SDK 驱动 harness

9.1 SDK 与 dsh 的关系和区别

关系 Python SDK 是 dsh 的进程级客户端:启动内置运行时(单文件 dsh-jsonrpc-agent)子进程,经 stdio 换行分隔 JSON-RPC 驱动;能力边界由组合里挂了哪些插件决定,不重新实现 harness 内核。
方面 dsh(产品本体)
--- ---
形态 TypeScript monorepo:CLI + Web + 插件生态 + 组合系统,从源码构建运行
扮演角色 运行时产品本身(消费者直接面向用户)
与运行时的连接 进程内直接调用插件
配置方式 profile / bundle / patch 组合树(cordis.yml)
环境变量 继承 DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL / DSH_HOME 等
平台 macOS / Linux 为主(Windows 部分支持)

9.2 上手步骤

  1. 安装(Python 3.10+):
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk

会自动装上同版本的 deepseek-harness-runtime-bin 平台 wheel,无需手动指定可执行文件。

  1. 设置凭据:DEEPSEEK_API_KEY 必须;若走 OpenAI 兼容代理再设 DEEPSEEK_BASE_URL。
export DEEPSEEK_API_KEY=sk-your-key-here
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
  1. 最小调用:
from deepseek_harness import DeepSeekHarness

with DeepSeekHarness as harness:
result = harness.run("Say hi.")
print(result.final_response)

上下文管理器会延迟启动运行时子进程并在多次调用间复用。

  1. 带 workspace / 会话持久化 / 自定义组合:
from pathlib import Path
from deepseek_harness import DeepSeekHarness

with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-flash",
max_tokens=49_152,
cwd=str(Path("/absolute/path/to/workspace").resolve),
session_root=str(Path("/absolute/path/to/sessions").resolve),
cordis=str(Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve),
) as harness:
result = harness.run("Inspect the repo and fix failing tests.", session_id="example-001")
print(result.final_response)
  1. 理解返回值:RunResult 含 session_id、final_response(该区间根会话最后提交的助手文本)、finish_reason(最后一个 turn/end 的 kind,如 completed/max-tokens/error)、events(根会话事件)、notifications(根会话 + 全部已知后代)、session_root。

  2. session id 复用语义:复用同一 harness + 同一 session_id 会保留该会话的 Bash 进程(工作目录、已导出的变量、shell 函数)。独立任务用新 id;只有需要延续同一段持久化会话才复用。

安全边界

内置组合(sdk-runtime/.../cordis.yml)不含 permission 层——bash 与编辑器可改任何路径,且上下文压缩关闭。行为等价于 danger-full-access(学习归纳:该名仅见于上游 app-boot.spec.ts 的 .env 拒绝名单 DSH_PERMISSION_MODE=danger-full-access,非正式组合名)。只应在可丢弃的 checkout 或容器内运行。仓库内置示例:python examples/jsonrpc-agent/minimal.py --workspace <dir> --session-root <dir> --session-id example-001 "task"。

10. 添加插件与贡献

所有扩展都走"在旁边挂插件",不改核心。两条路径:入门路径是往 preset 里挂一个最小插件(5 分钟验证);正式贡献是按仓库清单新增一个 workspace 包(含门禁、测试、文档约定)。

10.1 最小工具插件(入门路径)

最小工具就是一个有 name/inject/Config/apply 的 Cordis 插件 + ctx.tools.register(defineTool(...))。schema 自动进入 system-prompt 组装,卸载插件即注销工具:

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' }, // 默认可选
 },
 output: {
 schema: { type: 'string' },
 render: (_args, value) => [{ type: 'text', text: value }],
 },
 async execute(args, exec) {
 // args 由 schema 推导出类型 { path: string; limit?: number }
 return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
 },
 }))
}

execute 约定要点:参数在进入 execute 前已按 ParameterSchemaSpec 校验;exec 携带不可变身份与 token,exec.signal 是唯一可替换的运行期字段(用于超时);返回单一 canonical JSON 值;抛错或返回非法值即视为 isError。完整约定见 docs/cookbook/adding-a-tool.md。

UI 渲染是设计的一部分:用 output.presentCall/presentResult 返回 card-tagged render intent(generic / terminal / diff / search / web),且必须是 args(+ result)的纯函数——回放也要跑。见 11.3。

10.2 挂载到 preset 验证

在 $DSH_HOME/profiles/<name>/cordis.patch.yml(或 --patch <path> overlay)里写一个 patch 列表。文件是顶层 YAML 数组:按 id 定位的 patch 会整段替换目标行的 config(未改字段也要重述),insert 则插入新条目;!!js 表达式在挂载时插值。裸插件 name 从 profile 目录向上按 Node 模块规则解析,回退到 $DSH_HOME/profiles/node_modules。

# cordis.patch.yml —— 顶层数组,insert 一个插件行
- insert:
 - id: my-tool
 name: my-tool # 你的插件(安装进 profile 的 node_modules)

把插件安装进 profile 用 dsh plugin --profile <name> add <spec>(转发给 pnpm,profile 缺失时先初始化;相对路径 spec 锚定到调用目录,因此从插件 checkout 里执行 add . 装的就是该 checkout)。若是带 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } 声明的组合包,安装后会自动加入 dsh.profile.bundles 层栈。

验收

pnpm dsh --profile <name> --dump-config 能看到该行(注释标注来源文件与 overlay);跑一次任务观察工具被调用;对行为变化按 docs/testing.md 补测试。

10.3 正式贡献:新增 workspace 包

完整 checklist 见 docs/cookbook/adding-a-package.md(以 bash 与 adapter 包为模板校验过)。流程:

  1. 建包:packages/<group>/<pkg>/ 下四个文件——package.json(拷贝 packages/core/tools,改 name/description/deps)、tsconfig.json(extends ../../../tsconfig.base.json,rootDir src,references 含 vendor/cosmokit + vendor/cordis(用 Config 时加 schemastery)+ 每个 dsh 依赖)、src/index.ts(service 默认导出或 name/inject/apply/Config 插件)、README.md(含 gated Model Experience 段 + Known Limitations 段)。
  2. 注册进根配置:新包组加 tsconfig.base.json 的 @deepseek-ai/dsh-* wildcard;Host/Client 包分别加 tsconfig.host.json / tsconfig.client.json 的 references(一个包只属于一个 aggregate);有额外 entrypoint 才动 knip.json。workspaces、publint、tsdown、oxlint、constraints 全部由 glob 自动覆盖,不用改。
  3. 决定拓扑:可替换能力按 Service Definition / Provider / Consumer 拆包(shell 三人组是模板);单一用途插件一个包。命名用稳定职责,role 后缀有词典(Controller/Store/Registry/Policy/Provider/Backend/…),local 仅在"约定包含同宿主执行"时用。
  4. 写 README:包自身 API/事件/扩展点在前,最后以固定四段收尾:## Model Experience → ### Request context and condition(What the model sees / Token effect / KV Cache effect)→ ## Known Limitations and Deferred Work。
  5. 验证:
pnpm install
pnpm run doc-sync
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene

再按 docs/testing.md 补行为测试与快照;非平凡变更必须同 PR 附 Agent Note。

其它插件形态参考

docs/cookbook/extension-cookbook.md 有完整 feature → mechanism 映射:hook 插件(ctx.on('tools/pre-execute', ...) 返回 typed decision 的瀑布)、UI 插件(监听 session/event + agent.followup)、外部协议驱动(把 wire peer 映射到 ctx.agents)、LLM 适配器(LlmAdapter 子类 + ctx.llm.registerAdapter(['my-provider'], ...))。

11. 其他常用 HOWTO

11.1 读懂组合树与排查启动问题

  • dsh --profile <name> --dump-default-config:只打印 bundle 层;--dump-config 额外含 profile cordis.patch.yml、home 级 cordis.patch.yml、--patch overlays。两者都注释每行来源文件与修改过它的 overlay,!!js 表达式保持未求值。
  • 裸插件名解析失败、patch 目标找不到、schema 校验失败、启动失败都会报错并以非零退出——不会静默跳过。解析顺序:dsh 安装目录 → profile 目录 → $DSH_HOME/profiles/node_modules 维护的后备符号链接。
  • 环境变量:DSH_HOME(默认 ~/.dsh)、DEEPSEEK_API_KEY、可选 DEEPSEEK_BASE_URL。

11.2 无 key 联调与本地回放

  • pnpm mock:llm:本地 mock LLM 服务器,无需 API key 跑通工具调用回合。
  • pnpm run test:snapshot:keyless ACP/headless 回放,比对期望输出;-t <name> 过滤;test:snapshot:record 重录(需 key)。
  • 会话即 JSONL 日志:模型可见历史由 deriveMessages 投影,回放 = sessions.create(id, { seed })。任何模型可见输入都能从日志重建。

11.3 给工具配 UI 卡片(render intent)

presentCall(args) 返回待处理卡片,presentResult(args, { content, isError, meta }) 返回完成卡片;二者必须是纯函数(直播与回放都跑,禁止 I/O/读会话状态/时钟)。卡片类型:

card 用途 参考工具
generic 默认;可设 locations: [{path, line?}] 让编辑器跟随 大多数工具
terminal 调用本身就是一条 shell 命令 tool-bash
diff 创建/修改文件,内联 diff tool-fs write/edit
search 按文件分组的发现结果(仅 result 视图) tool-fs-search grep/glob
web 完成的网页检索,按 kind: search\|fetch 判别 tool-web

UI 专用格式(fenced console、diff、相对路径)留在卡片投影里,绝不进 canonical 值或 Native 内容;持久化的 replayable 卡片数据走 output.presentationMeta(args, value),经 tool/result 的 meta 回放复原。

11.4 常用参考

  • 事件:docs/event-producer-consumer.md + 每包组 subsystems 页;事件 JSDoc 需 @mode 与 payload @param。
  • 测试:docs/testing.md(门禁 test:coverage 每文件 100%;模型/用户可见行为要 keyless 快照)。
  • Cordis 入门:docs/cordis-primer.md + docs/cordis-tutorial/ 动手教程。
  • 文档规范:docs/AGENTS.md;贡献任何非平凡变更需 Agent Note(.agents/notes/),已归档的冻结。
  • 协议族:SDK wire 协议见 packages/sdk/protocol;ACP 自动化协议见 packages/acp/acp/README.md;Claude Code/Codex hooks 桥接见 packages/hooks。

12. 结语

上游 dsh 把模型、工具、沙箱与子 agent 都做成可替换插件,会话事实只追加在事件日志里;外层用 profile 与 bundle 组合出 Web、headless、SDK 等形态。用 Python 复现时,优先亲手实现三件事:日志作唯一数据源、可逆副作用与 waterfall 短路、扩展口三角色(Definition / Provider / Consumer)。做完 手册 01–05 与 05 路线图 §7.3,再读上游源码会快很多。