跳到主要内容
路径文档

从程序驱动 DSH:SDK

一句话版:SDK 将 dsh profile 作为子进程启动,经 stdio newline JSON-RPC 驱动并回收它。源码基线:0.1.5-alpha.1 / 5dda764ed3;npm latest / next 为 0.1.2-rc.1,alpha 通道为 0.1.5-alpha.1(状态页)。

TypeScript client 默认解析匹配版本的 @deepseek-ai/dsh 依赖,也可指定 dshBin。组合通过 profile(默认 sdk)与有序 patches 选择,不再使用旧 launch: { command, args } 或整份外部 cordis.yml 启动契约。被开发的工程仍是独立工作区。

组成与分工​

包职责
@deepseek-ai/dsh-sdk-protocol定义 SDK 运行时通信协议:一个 newline-delimited JSON-RPC 传输类 + 双方都说的命名请求/结果/通知类型
@deepseek-ai/dsh-sdk-clientTypeScript 客户端,含高层 DeepSeekHarness 与低层 HarnessClient
@deepseek-ai/dsh-sdk-jsonrpc-server服务端插件:在 stdio 上以 newline JSON-RPC 服务进程外 SDK 客户端
deepseek-harness-sdkPython 版(deepseek_harness),镜像同一协议与分层,但不 import TS 类型

服务端:sdk-jsonrpc-server 插件与应用边界​

运行时侧由一个名叫 sdk-jsonrpc-server 的插件承担。它声明 inject: ['agents'](packages/sdk/server/src/index.ts):

export const name = 'sdk-jsonrpc-server'
// 只需 agent 工厂;initialize 用 ctx.get() 读可选的 LLM 能力缝。
export const inject = ['agents']

核心是 HarnessSdkJsonRpcServer:构造时订阅会话、agent、subagent 生命周期事件并发通知;initialize 配置路由、必要时挂载 DeepSeek 适配器回落;session/prompt 按 sessionId 取/建一次会话并排队一条用户消息,立即返回 { messageId } 入队回执;shutdown 处置所有 SDK 自有 agent、适配器与订阅到静默,然后由插件 flush 响应、dispose 根上下文并 exit(0)。handleRequest 是方法分发(packages/sdk/server/src/server.ts):

async handleRequest(method: string, params: Record<string, unknown> | undefined): Promise<unknown> {
switch (method) {
case 'initialize': return this.initialize(params as unknown as InitializeParams)
case 'session/prompt': return this.prompt(params as unknown as SessionPromptParams)
case 'shutdown': return this.shutdown()
default: throw new Error(`unknown DeepSeek Harness SDK runtime method: ${method}`)
}
}

stdout 就是协议:SDK profile 将 stdout 留给 JSON-RPC 帧,诊断走 stderr;自定义插件应保持这一边界。运行时组合由 profile 与 patches 决定。

传输层:JsonRpcLineTransport​

协议用一个传输类 JsonRpcLineTransport 把 JSON-RPC 2.0 帧在调用方持有的字节流上按每行一个紧凑 JSON 帧传输(packages/sdk/protocol/src/transport.ts):

  • 有 id + method 的是请求;只有 id 是响应;只有 method 是通知;格式错误的 JSON 行被忽略。
  • start() 挂监听、close() 摘监听并在不销毁流的情况下拒绝 pending 请求。
  • 缺失请求处理器回 -32601;处理器抛错回 -32603 带错误消息。错误响应以保留 wire code 与可选 data 的 JsonRpcResponseError 形式拒绝 pending 的 request()。
private write(message: Record<string, unknown>): void {
this.output.write(`${JSON.stringify(message)}\n`)
}

wire 方法表​

协议命名所有载荷(packages/sdk/protocol/src/types.ts 的 HarnessSdkRequestMap / HarnessSdkNotificationMap):

方向方法载荷说明
client→serverinitializeInitializeParams → InitializeResult进程级握手:工作目录 + provider/model 路由 + 可选正 maxTokens
client→serversession/promptSessionPromptParams → SessionPromptResult排队一条用户消息,立即返回持久的 { messageId } 入队回执
client→servershutdown无参 → {}处置 SDK 自有 agent/适配器/订阅到静默,再退出
server→clientsession.eventSessionEventNotification运行时内每个会话的完整会话日志事件(不筛选),流式记录即发送
server→clientsession.statusSessionStatusNotification整台 agent 的 running / idle 跃迁
server→clientsubagent.startedSubagentStartedNotification运行时内创建了一个子会话
server→clientsubagent.finishedSubagentFinishedNotification一次进程内子 agent 运行结束(仅本地运行;远端运行不上报)

几个关键语义,都来自协议 README / 类型 JSDoc:

  • SessionPromptResult.messageId 只标识被排队的 UserMessage,不代表后续某条 assistant 消息、回合结束或 prompt 结果。
  • 客户端自行结合开放式的 session.event 流与整台 agent 的 session.status 来划分"活动区间"。
  • SubagentFinishedNotification.lastAssistantMessage 含子的最后非空 assistant 消息;都没有则该字段缺席。
  • InitializeParams.maxTokens 是可选正整数,封顶 SDK 创建 agent(及进程内后代)的每次对话模型输出。
  • serverInfo.name 保持线上稳定值 deepseek-harness-sdk-runtime(当前版本 0.0.1,未校验)。

协议 README 也坦承当前边界:无协议版本协商(serverInfo.version 未校验)、无 cancel / session 关闭方法(放弃某回合需关闭运行时进程)、服务器→客户端请求是"死能力"(传输支持但服务器从不发送,为未来审批流预留)。

客户端:两层 API​

高层 DeepSeekHarness(自带运行生命周期)​

import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client'

await using harness = new DeepSeekHarness({
profile: 'sdk',
dshHome: '/absolute/path/to/isolated-dsh-home',
cwd: '/absolute/path/to/workspace',
provider: 'deepseek-official',
model: 'deepseek-v4-flash',
maxTokens: 49_152,
})
const result = await harness.run('say hi')
console.log(result.finalResponse)

(packages/sdk/client/README.md 与 src/api.ts)要点:

  • 子进程在首次使用时惰性启动,并在多次 run() 间由本实例持有;close()(或 await using)是必须的,否则子进程不会被回收。
  • start() 记忆化 initialize 握手;握手失败会回收运行时并换成新 client,以便下次调用用新子进程重试(直到 close() 终结)。
  • run(input, { sessionId?, onNotification? }) 拥有一次活动区间:排队 prompt → 等其 MessageId 出现在持久的 agent/inbox/spliced 回执里 → 收满到下一次整台 agent idle。返回 RunResult { sessionId, finalResponse, events, notifications }。
  • finalResponse 是区间内最后一条已提交的根会话 assistant 文本,不是因果上分配给这条 prompt 的输出——转向、注入上下文、其他排队工作都可能在 idle 前贡献内容。events 只含根会话事件,notifications 另含从 subagent.started 发现的全部后代,均按 wire 顺序。
  • session(id?) 打开一个命名或全新会话句柄。

src/api.ts 的 HarnessSession.run 是这条"收到回执 → idle"收集循环:

const messageId = await client.prompt(this.id, contentBlocks)
let received = false
while (true) {
const notification = await subscription.next()
if (!received) {
if (notification.method !== 'session.event'
|| notification.params.sessionId !== this.id
|| !isInboxReceipt(notification.params.event, messageId)) continue
received = true
}
collect(notification)
if (notification.method === 'session.status'
&& notification.params.sessionId === this.id
&& notification.params.status === 'idle') break
}

低层 HarnessClient(精细控制)​

协议客户端(src/client.ts):显式 start() / initialize() / prompt() / request() / close(),加通知订阅。

  • prompt(sessionId, contentBlocks) 一旦运行时接受就返回排队消息 id,从不等待 agent 活动。
  • subscribe(filter?) 返回 NotificationSubscription(可 await 的 next()、非阻塞 tryNext()、async 迭代);subscribeSessionTree(id) 把作用域收窄到一个会话及 subagent.started 血统边推出来后发现的后代——运行时对整个 context 里的每个会话都发通知,收窄是客户端侧做的,与 Python SDK 相同。
  • 类型化错误(src/client.ts):JsonRpcResponseError(wire 错误,保留 code/data)、RequestTimeoutError(超时)、SdkProtocolError(协议外响应)、TransportClosedError(运行时没了——消息带退出码与有界的 stderr 尾)。
  • close() 先发协议 shutdown(shutdownTimeoutMs,默认 1000ms),再走 stdin-EOF → SIGTERM → SIGKILL 阶梯(disposeEofGraceMs 默认 6000、disposeGraceMs 默认 3000)直到进程真正退出。阶梯在 harness context 之外运行,因此不走 dsh-subprocess 服务——这是该缝文档化的异常。它幂等,且已经关闭的 client 拒绝复用。
  • HarnessClientOptions.env 给定时整体替换子进程环境(undefined 则继承);凭据策略由调用方负责(隔离型启动可用 dsh-subprocess 的 scrubbedParentEnv 作为共享 scrubbed 基础)。

两种 client 都是纯净库:在 Cordis context 上什么都不注册。它们拉起的运行时进程是完整 harness,其组合由选定 profile 与 patches 决定。

Python SDK​

安装时应让 SDK 与运行时版本匹配;下列源码契约不代表 alpha wheel 已发布。

from deepseek_harness import DeepSeekHarness

with DeepSeekHarness(
dsh_home="/absolute/path/to/isolated-dsh-home",
cwd="/absolute/path/to/workspace",
profile="sdk",
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
) as harness:
result = harness.run("概述这个项目。")
print(result.final_response)
  • 包依赖精确同版本 deepseek-harness-runtime-bin wheel,启动绑定的 dsh --profile sdk,旧 dsh-jsonrpc-agent 入口已退出。
  • 显式提供 dsh_home 或子环境里的非空 DSH_HOME;Python SDK 不自动发现 ~/.dsh。
  • cwd 是 Agent 工作区,runtime_cwd 是子进程目录;patches 是有序 overlay,dsh_bin 替换 CLI 可执行文件但保留 profile 语法。
  • Python 初始化超时默认 30 秒(initialize_timeout_seconds);普通 turn 默认无超时,除非设置 request_timeout_seconds。TypeScript 初始化默认 10 秒。
  • 源码中的 runtime wheel 目标包括 Windows x64、Linux x64/arm64、macOS 14+ arm64 与 x64(0.1.3-alpha.1 新增 macOS x64);具体发行版是否已发布应另查包仓库。
  • run() 拥有持久 inbox 回执到整台 Agent idle 的活动区间。final_response / finish_reason 是该区间最后的根回复 / turn 结束原因,不等于专属于这一条 prompt 的因果响应。
  • 使用 context manager 或 close() 回收子进程。

完整 profile 与最小 profile​

sdk 继承 base 组合。sdk-minimal 是独立完整树,提供平台对应的持久 shell、str_replace_editor、一个 DeepSeek adapter 与未压缩 JSONL 会话;省去 settings、受管凭据、telemetry、Web 工具、compaction、工作区 instructions、skills、jobs 和 subagents。

最小不等于受限:sdk-minimal 固定 danger-full-access,shell / editor 可访问进程有权限的任意路径。应使用一次性隔离环境,单独换一个工作目录并不构成隔离。

为什么这满足了"把 DSH 当嵌入运行时驱动"​

  1. 边是纯进程边——运行时是子进程,stdio 是工厂协议行;不依赖任何 UI、网络端口或全局安装,可嵌进 CI、批处理、编排器。
  2. stdout 纯净——协议帧独占 stdout,诊断走 stderr,任何程序化解析都不会被日志污染。
  3. 明确的活动区间——run()(或自己组合 session.event + session.status)把"发一条提示 → 等到整台 agent 空闲"抽象成一次可 await 的调用,并归还 finalResponse 与完整事件/通知流。
  4. 生命周期完整——await using / context manager 保证子进程被回收;close() 走协议 shutdown + EOF/SIGTERM/SIGKILL 阶梯到幂等终态。
  5. 双语言对称——TS 与 Python 走同一条 wire,选任意一门。跨进程子 agent 后端(dsh-sdk subagent provider)正是用这套 TS client 把每个子 agent 跑成完整 peer harness。

验证 / 试一试​

构建匹配的官方源码后:

pnpm dsh --profile sdk --help
pnpm dsh --profile sdk-minimal --help
pnpm dsh --profile sdk --dump-config

真实会话还需要配置模型路由与凭据。使用上方 TypeScript 示例,或源码中的 Python 示例:

python python/sdk/examples/minimal.py "Say hi" \
--workspace /absolute/path/to/disposable-workspace \
--dsh-home /absolute/path/to/isolated-dsh-home

Python 示例显式选择 sdk-minimal,访问策略见上节。可用订阅或 run(input, { onNotification }) 观察 session.event / session.status。旧 demo:jsonrpc 与整份外部配置启动示例现已由 profile 替代。

下一步​

  • 想看这层 API 在 harness 内部怎么当子 agent 后端用,见 子 Agent 与并行。
  • 属性与限制完整引用见源码 packages/sdk/{protocol,client,server}/README{.zh}.md 与 python/sdk/README{.zh}.md。