12 KiB
description, kind
| description | kind |
|---|---|
| 面向客户端与服务端实现者的 SDK 协议格式(wire format)说明:Harness 运行时与其 SDK 客户端之间使用的按换行分帧 JSON-RPC 传输,以及具名的请求、结果与通知类型。 | package-library |
@deepseek-ai/dsh-sdk-protocol
English | 中文
概述
dsh-sdk-protocol 让 DeepSeek Harness 运行时与其 SDK 客户端通过按换行分帧的字节流交换 JSON-RPC 2.0 消息:一个传输类,加上协议两端共同使用的具名请求、结果与通知类型。服务端是 dsh-sdk-jsonrpc-server 插件;客户端是 TypeScript 的 dsh-sdk-client 与 Python SDK(后者复现这些结构但不导入它们)。当你实现或调试协议某一端时使用本包:分帧规则、方法名、载荷类型与错误语义都在这里。它是纯库——无插件、无配置、无注册。
目录
使用本包
当你构建或调试 SDK 协议端——服务插件、客户端库或使用该协议的自定义工具——时使用本包。它为你提供一个在调用方持有的字节流上承载 JSON-RPC 2.0 的传输,以及每个 SDK 方法与通知的类型化结构。
InitializeParams.approvalRequests: true 启用由 HarnessSdkClientRequestMap 索引的服务端请求 approval/request。ApprovalRequestParams 包含 sessionId、toolName,以及可选的 callId 和 reason;ApprovalRequestResult 为 { outcome: "allowed-once" | "rejected" | "cancelled" }。省略或设为 false 时保留现有应答链。只有 allowed-once 批准单次操作。取消会丢弃待处理请求及迟到响应;shutdown 和传输断开使待处理决定拒绝放行。客户端等待 Agent 完成期间,必须继续读取并回答请求。
分帧与传输
在你拥有的字节流上,每个 \n 结尾的行承载一条 JSON-RPC 2.0 消息。同时带 id 与 method 的帧是请求,仅 id 是响应,仅 method 是通知;格式错误的行会被忽略。没有注册处理器的请求应答 -32601,处理器失败应答 -32603,错误响应会以 JsonRpcResponseError 拒绝挂起的请求,并保留协议中的 code 与可选 data。start() 挂接流监听器,close() 移除监听器并拒绝挂起请求,但不销毁流。
SDK 方法
两个协议端共享同一套方法:客户端到服务端请求、显式启用的服务端到客户端请求,以及服务端通知。
| 方向 | 方法 | 载荷类型 |
|---|---|---|
| client→server | initialize |
InitializeParams → InitializeResult |
| client→server | session/prompt |
SessionPromptParams → SessionPromptResult(持久入队回执) |
| client→server | session/query |
SessionQueryParams → SessionQueryResult |
| client→server | session/resume |
SessionResumeParams → {} |
| client→server | session/wait |
SessionWaitParams → {} |
| 客户端→服务端 | session/working-directory/get |
SessionWorkingDirectoryParams → SessionWorkingDirectoryResult |
| 客户端→服务端 | session/working-directory/set |
SessionWorkingDirectorySetParams → SessionWorkingDirectoryResult |
| client→server | shutdown |
无参数 → {} |
| server→client | approval/request |
ApprovalRequestParams → ApprovalRequestResult |
| server→client | user-question/ask |
SdkUserQuestionRequest → SdkUserQuestionResult |
| server→client | user-question.cancelled |
SdkUserQuestionCancelled |
| server→client | session.event |
SessionEventNotification(运行时内每个会话,不过滤) |
| server→client | session.status |
SessionStatusNotification(整个 agent(智能体)的 running/idle 转换) |
| server→client | session.query.completed |
{ sessionId, commandId, finalResponse, finishReason } |
| server→client | subagent.started |
SubagentStartedNotification |
| server→client | subagent.finished |
SubagentFinishedNotification(仅进程内运行) |
HarnessSdkRequestMap、HarnessSdkClientRequestMap、HarnessSdkHostRequestMap 与 HarnessSdkNotificationMap 按方法名索引这些结构;包根与传输一起导出它们。
session/wait 接受已有 SDK 会话的 { sessionId },在根 Agent 持续空闲且活跃的受管理后代工作结束后返回 {}。带有停放输入的空闲后代保持驻留,不阻塞这次等待。未知 id,以及没有后续终止事件记录的 Agent 运行时错误,都会使请求失败。等待期间提交的通知在同一传输上先于响应到达;原始 session.status 转换仍表示整个 Agent 的状态。
session/query 接受 { sessionId, contentBlocks },其中 contentBlocks 使用 SdkPromptContentBlock[]。两个 SDK 都将字符串规范化为文本块,并通过同一入口发送内容块数组。普通输入保留内容块顺序排队,返回 { kind: "prompt", messageId };受支持命令结束后返回 { kind: "command", execution }。已识别命令要求一个完整文本块,其他块只能是编码图片;其他组合会在执行或入队前被拒绝。图片经过原生附件接纳与处理器校验。空数组和只包含空白文本的数组会被拒绝;允许仅图片输入。Prompt 回执不是模型结果,命令完成也不要求 inbox 回执或 idle 通知。识别规则及受支持命令由 SDK 服务端定义。
session/prompt 将内容块排入队列,不经过原生 slash command 路由;运行时 skill(技能)识别仍然适用。
命令生命周期通知先于 session/query 的 handler 响应到达。命令查询以同一 commandId 的 session.query.completed 通知结束高层观察,等待命令启动的模型活动;目标创建和恢复还等待目标终止及收尾活动。状态命令无需模型 turn,其完成通知可以先于响应到达。SDK 内部消费该回执并返回 RunResult,转发器必须保持顺序。关闭会取消尚未结束的观察。
session/resume 接受非空的持久化 { sessionId },恢复该会话后返回 {}。它要求先完成初始化并使用原持久化存储,不发送新输入;会话缺失、不可读、不兼容或被占用时,请求失败且不创建替代会话。历史事件保留在存储日志中,通知只报告新提交的事件。
原生用户问答
提供回答能力的宿主设置 initialize.capabilities.userQuestions: true。运行时发送带有 { requestId, sessionId, questions } 的 user-question/ask;问题保留原始 id、选项、detail 与展示意图。宿主返回 { answers: [{ id, selected, custom? }] },每个问题 id 必须恰好出现一次;用户关闭问题时可返回只含 { cancelled: true } 的对象。关闭映射为原生 ASK_CANCELLED,不表示运行时取消、回答方失败或批准。无效答案批次返回 BAD_ANSWER,宿主或传输失败返回 PROVIDER_ERROR。SDK 不生成替代答案。
user-question.cancelled 标识已停止等待的请求。宿主取消自己的工作并丢弃问题;运行时放弃对应的待决 RPC,忽略晚到响应。未声明该能力时,仍由现有进程内回答方处理;缺少回答方则报告 NO_PROVIDER。此流程不要求 GUI。
载荷语义
SessionPromptResult.messageId 标识已排队的用户消息;它不标识后续的助手消息、轮次结束或提示词结果。SdkPromptContentBlock 接受普通持久内容以及 SdkEncodedImageBlock { type: "image", data, mimeType };服务器在入队前把编码图像转换为持久引用。InitializeParams.reasoningEffort 是所选提供方/模型路由可选的非空适配器自有标识符;省略时保留该模型的默认值。InitializeParams.maxTokens 是可选的正安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时应用所选适配器的确切模型默认值。服务器会在初始化期间解析确切路由,并在握手成功前拒绝 session/prompt,因此缺少适配器、模型不可用或推理强度不受支持时,不会回退到构造期默认值。SubagentFinishedNotification.lastAssistantMessage 携带子 agent 最后一条非空 assistant 消息;若不存在这类消息,则携带其累积的 assistant 文本;子 agent 两种输出均未产生时,该字段缺省。serverInfo.name 的协议值固定为 deepseek-harness-sdk-runtime。通知载荷依赖 SessionEvent(dsh-session)、ContentBlock(dsh-llm)与 SubagentStopReason(dsh-subagent),因此会话词汇是协议格式约定的一部分。
理解实现
实现细节——点击展开
本节解释协议库背后的设计;可观察行为已在使用本包中完整说明。
设计理念
本包采用一种职责分离设计:两个协议端共用一个按换行分帧的传输类,并以具名类型索引协议方法。包根是唯一的导入面——源模块不支持深层导入。它是没有插件、配置或注册的纯库;服务插件与客户端负责其周围的一切行为。
源码地图
| 文件 | 职责 |
|---|---|
src/transport.ts |
JsonRpcLineTransport:行分帧、请求/响应/通知分发、错误映射、挂起请求记账 |
src/types.ts |
具名请求/结果与通知载荷类型,按方法索引 |
src/index.ts |
消费方接口:传输与具名协议类型 |
帧分发
入站行逐条解析:带 id 与 method 的帧通过请求处理器应答(或应答 -32601),仅 id 的帧结算匹配的挂起请求(错误帧以 JsonRpcResponseError 拒绝它),仅 method 的帧交给通知处理器。start() 挂接输入监听器;close() 移除它们并在不销毁流的情况下失败所有挂起请求。
进一步探索
当协议约定不够用时阅读以下页面。它们从服务插件进入客户端与可运行应用。
- JSON-RPC 服务插件 — 通过 stdio 服务该协议的运行时插件。
- TypeScript SDK 客户端 — 驱动该协议的客户端。
- Python SDK — 复现这些结构的 Python 对应实现。
- SDK 应用组合包 — 启动服务器的
dsh --profile sdk应用。
模型体验
无,因为这是面向客户端的协议库;模型可见行为归对外服务入口后方的运行时插件所有。
KV Cache 影响
无;此包既不组装也不发送提供方请求。
已知限制与延期工作
这些限制说明协议未覆盖或未承诺的内容。它们是当前包约束,不是与其他协议格式的对比或任务积压。
- 无协议版本协商——握手只携带
serverInfo.version(0.0.1,客户端不校验);处于预发布阶段,无兼容承诺。 - 无取消与会话关闭方法——客户端放弃轮次的方式是关闭运行时进程;见 JSON-RPC 服务插件。
- 问题等待仅属于当前进程——问题请求 id 不能在运行时重启后继续用于回答。已完成的工具结果通过普通持久化路径保留在 Session 历史中。
开发备注
维护者的工作上下文——点击展开
本开发备注是维护者的工作上下文,明确不具权威性——已交付的行为与限制见上文各节与代码。本协议的各个结构由 Python SDK 复现(而非导入),因此在这里更改方法、载荷或协议稳定值 serverInfo.name 时,必须在同一次变更中更新 Python 对侧与 TypeScript 客户端。没有记录其他未解决的开放设计问题。