DSH 协议参考
这一页列出 DSH 适配器读写的 DeepSeek Harness(DSH)Web 远程协议字段,以及它们怎么映射成 ChatKit 的消息、片段和轮次结论。怎么接入见 DSH。
传输
- 连接只拼相对路径,交给
resolveUrl(path, kind)变成完整地址:kind为http时是api/{method},为ws时是api/remote.mux。地址前缀、鉴权参数都在这里决定;需要请求头或走代理时替换fetch/WebSocket。 - 单次调用的信封:请求
{ type: 'client-request', rpcId, method, payload: { args } },rpcId每次随机;回复{ type: 'server-response', rpcId, result },result.ok为true时取result.value。 - mux 帧:客户端只发
{ type: 'open', streamId, endpoint, payload: { args } };服务端回item(带value)、end、error。逻辑流在连接断开前不会主动关闭。 - 发起一轮的顺序:先登记这一轮的监听 → 等这个会话的
session/follow快照和$events的ready→ 再 POSTsession/prompt。开头的事件不会丢。 - 快照之前到达的 follow 内容丢弃;
$events在ready之前到达的帧丢弃。
怎么认出属于这一轮的内容
同一会话的 follow 流里有所有 turn 的内容,连接按下面的规则只把这一轮的交给协议:
不按"最近开始的 turn"猜。同一会话里由其他客户端发起的轮次不会实时显示,刷新后从历史出现。
按 DSH Web 0.x 的 dsh-api-gateway / dsh-client-connection(remote.mux)协议核对。DSH 仍是 developer preview,字段可能变化;升级后以适配器源码里的 wire.types.ts 为准重新核对。
发出的请求
发送:session/prompt(mode: queue)
POST api/session/prompt,payload.args 为 { request }:
transformInput 收到的 defaultInput 是 { content, mode: 'queue' },要返回完整请求体;mode 会被丢掉,requestId、sessionId、mode 由连接补上。
content 的编码:
回复只看 result.ok。DSH 的 file 内容需要先上传拿 receiptId,适配器不发。
插话、停止、历史、回答
maxMessages 来自连接选项 historyMaxMessages,不给就不带。
读取的事件
会话事件(follow 的 { type: 'event', event })
事件外层读 type、seq(事件编号、session/page 的位置)、time(消息时间)、data。
流式帧(follow 的 { type: 'assistant-stream', frame })
流式帧的事件编号是 stream:{attemptId}:{revision},会话事件是 seq:{seq}。
需要客户端回答的请求($events)
agentId 就是会话 id。只有这个会话有轮次已认定、还在读时才认领上面两种请求;否则、或是别的 event,立刻回 next。
没有内置处理的事件
这些内容适配器不变成聊天内容,接入方可以自己拿:
- 属于某一轮的(带这一轮的
data.turn,或在读这一轮期间到达、不带turn),会进transformOutput,收到原始的DshFeedItem,可以返回OutputChange[]补 data 片段。 - 不属于任何一轮的、
emit通知、或用于插槽的,用connection.listen(sessionId, listener):它收到这个会话的全部原始内容(会话事件、流式帧、请求、emit、本地的回答状态)。
listen 驱动插槽:
transformOutput 把一步的结束变成 data 片段:
刷新加载历史时,适配器把历史回放成与实时同形的会话事件(kind: 'event')逐条交给 transformOutput(context.source 为 history,带这一轮的 runId 与 messageId),从这一轮出现回答之后开始。流式帧和请求只在实时里有。上面的写法实时与刷新后都会生成同样的 data 片段。
审批与追问
上游请求
挂在哪个工具片段上:
片段上的交互:metadata['chatkit.interaction']
用 getDshInteraction(part) 读。
connection.respond
发出的原生回答:
历史
不保留的内容:
- 只在流式帧里出现过、没有落盘的内容(被撤回的 attempt)。
- 审批的
requestId与displayReason;刷新后审批不能回答。 source.kind不是user的用户消息。- 发送时合并进文本的引用,还原后只是文本;图片的
size按 base64 长度估算。 - 同一轮的用户消息(含插话)在历史里都排在回答之前。
编号
结束与错误
只有读到这一轮的 turn/end 才结束。结束前先把仍在等待的交互标为 cancelled;没有正常完成时,还在执行的工具片段标为 cancelled。
失败的 message 取 reason.message,其次 reason.error.message;都没有时 max-tokens 为"回答超出长度上限",其它为"Agent 没能完成这一轮"。cause 是整个 reason。
工具失败的片段错误码是 DSH_TOOL_ERROR;不支持的附件见上文"发送"一节。错误消息不带地址。
断线:WebSocket 断开时全部逻辑流失效,正在读的轮次抛 DshConnectionClosedError,由 Core 标记连接中断;不认定执行结束,不自动重发,经历史恢复。下次使用时重新连接;有 listen 监听时自动重连,见下文。停止是会话级的 session/cancel,受理只代表请求送达,以 turn/end(aborted)为准。
listen 断线重连
连接断开(或这个会话的订阅单独结束)时,有 listen 监听的会话:
- 监听先收到
{ kind: 'connection', status: 'disconnected' }; - 连接按退避自动重连并重新订阅(重新打开
session/follow与$events):第一次等reconnect.minDelayMs(缺省 1000),之后翻倍,最多等reconnect.maxDelayMs(缺省 30000); - 订阅恢复后收到
{ kind: 'connection', status: 'reconnected' }。断线期间的事件不会补发,需要时自行补拉状态(如session/projections)。
每次重新订阅失败经 onError('listen', error) 报告,之后继续重试。取消全部监听或调用 close() 后停止;连接选项 reconnect: false 关闭自动重连(仍会收到 disconnected)。正在进行的一轮不续接,仍由会话层经历史确认终态。
示例
follow 流里的一条会话事件(mux item 帧):
审批请求,以及 respond({ sessionId, requestId: 'wf-1', response: 'allowed-once' }) 发出的回答:
其余字段见上游。