DeepSeek App

August 13, 2026 · View on GitHub

契约来源:本机安装树 ~/.bun/install/global/node_modules/@deepseek-ai/(只读参考)· 锁定版本 @deepseek-ai/dsh@0.1.0-rc.6 用途:v3 S2 直连 carrier 的信封校验镜像、fixtures 契约测试、renderer client.ts 方法表对齐、升级漂移回归基线。 权威实现:dsh-host-apiproxy/lib/types/api/{rpc,rpc-map,events}.d.ts · dsh-client-connection/lib/index.js(trust fence + PRIVILEGED_METHODS) 维护规则:dsh 升级后必须重跑 fixtures 契约测试;任何字段名变更在此更新后再动 UI。


1. 传输与端点

unary   POST {origin}/api/<method>             body: client-request 信封
respond POST {origin}/api/respond              body: client-response 信封
remote  POST {origin}/api/<ns>/<method>        Typert 风格(commands/list 等),payload={args}
export  GET  {origin}/api/session.export?sessionId=<id>&includeDescendants=true   → zip
mux     WS   {origin}/api/events.mux           MuxFrame 流
host    WS   {origin}/api/events.host          HostFrame 流

路径常量(dsh-client-connection):API_PATH="/api" · MUX_EVENTS_PATH="/api/events.mux" · HOST_EVENTS_PATH="/api/events.host"

对真实 HTTP 服务,GET /api/events.mux|host 返回 426(无 SSE 回落);事件必须走 WebSocket。

2. 信封(rpc.ts 四象限模型)

// client-request(unary)
{ "type": "client-request", "rpcId": "<uuid>", "method": "session.prompt", "payload": { ... } }
// server-response(成功)
{ "type": "server-response", "rpcId": "<同一 uuid>", "result": { "ok": true, "value": ... } }
// server-response(业务失败:HTTP 仍 200,必须看 result.ok)
{ "type": "server-response", "rpcId": "<同一 uuid>", "result": { "ok": false, "error": { "code": "...", "message": "..." } } }
// client-response(应答审批/提问)
{ "type": "client-response", "rpcId": "<来自 server-request 的稳定 id>", "result": { "ok": true, "value": { ... } } }
  • rpcId:由发起方铸造;响应回声同一 id,从不新铸。server-request 的可应答帧(approval/question requested)带稳定逻辑 id,应答必须回声它。
  • method 必须等于路径最后一段。
  • Content-Type: application/json(其它 media type → 415)。
  • 远端(Typert)信封里 method 为 <ns>/<method>,payload 必须 { "args": { ... } }

3. 错误码(RpcErrorDetailsMap,0.1.0-rc.6 快照)

bad-request(issues) · cancelled · session-not-found(sessionId) · model-unavailable(provider,model) · session-conflict(sessionId,requestedCwd,existingCwd?) · invalid-time-zone(value) · workspace-attach-failed · workspace-not-found · workspace-invalid-path · workspace-name-conflict · workspace-move-invalid · directory-unreadable · directory-exists · directory-create-failed · directory-picker-unavailable(capability) · agent-preset-read-only · agent-preset-locked · agent-preset-conflict · agent-preset-not-found · 其它(见上游 rpc.d.ts 全表)。

4. RPC 方法全集(RpcMethodMap,0.1.0-rc.6)

session.list / search / create / history / models / selectModel / rename / fork / prompt / attachment / updateQueue / cancel
subagent.list / history / prompt / interrupt
host.describe / pickDirectory / listDirectory / createDirectory / openPath
workspace.list / create / rename / delete / insertBefore / insertSessionBefore / archiveSession
skill.list
agentPreset.list / select / read / copy / openDocument / remove
goal.create / edit / pause / resume / complete / clear
settings.describe / openDocument / update / replace / mutate
credentials.describe / set / unset
llm.providers / models / discoverModels

响应方法 = client-response 而非 client-request,故不在 RpcMethodMap(respond 走 /api/respond)。

5. 特权方法(PRIVILEGED_METHODS —— v3 S2 必须镜像)

来源:dsh-client-connection/lib/index.js:504。这些方法在浏览器传输上额外要求 loopback 信任(isTrustedApiRequest(request, [])),LAN 匿名调用者 403:

agentPreset.read / agentPreset.copy / agentPreset.openDocument / agentPreset.remove
host.pickDirectory / host.openPath
settings.describe / settings.openDocument / settings.update / settings.replace / settings.mutate
credentials.describe / credentials.set / credentials.unset
llm.discoverModels

明确不在特权表:llm.providers / llm.models(仅目录/模型名,LAN 模型选择器需要)。

6. 信任围栏(loopback trust fence)

  • 所有 /api 请求先过浏览器信任围栏(DNS rebinding / cross-site 防御,api-request-trust)。
  • 特权方法在浏览器传输上以空信任列表再验一次 → 钉死 loopback。
  • Electron 形态下信任语义 =「IPC 发送方 = 本 app renderer(webContents.id 校验)+ PRIVILEGED_METHODS 白名单」(v3 S2 替代)。
  • --host 0.0.0.0dsh web 显式拒绝(防远程代码执行)。

7. Mux 帧(api/events.d.ts MuxFrame)

{ "type": "session/event", "sessionId": "<id>", "event": { ...SessionEvent }, "view"?: { "for": "call"|"result", "view": ToolCallView|ToolResultView } }
{ "type": "session/subscribed", "sessionId": "<id>", "lastSeq": 0 }
{ "type": "session/queue", "sessionId": "<id>", "items": [ { "id": MessageId, "placement": "queued"|"steering"|"context", "message": Message } ] }
{ "type": "session/jobs", "sessionId": "<id>", "jobs": JobView[] }
{ "type": "session/projection", "sessionId": "<id>", "key": string, "value": unknown, "seq": number }
{ "type": "approval/requested", "sessionId": "<id>", "approvalId": ApprovalRequestId, "toolName": string, "callId"?: CallId, "reason"?: string }
{ "type": "approval/resolved", "sessionId": "<id>", "approvalId": ApprovalRequestId, "outcome": ApprovalOutcome }
{ "type": "question/requested", "sessionId": "<id>", "questions": AskUserQuestionItem[] }
{ "type": "question/resolved", "sessionId": "<id>", "questionRpcId": RpcId, "outcome": "answered"|"cancelled" }
{ "type": "stream/error", "error": RpcError }
  • mux 打开时先为每个已挂会话发 session/subscribed,再重放其仍待决的 approval/question requested 帧(rpcId 原样复用 —— 刷新恢复基线)。
  • session/projection活推(工具 view 姿态),不落日志;replay 在 host 端重算。客户端按 seq 高者胜,由 history 尾页的 projections 块播种。
  • session/queue / session/jobs全量快照(队列/任务注册表无持久事件,快照是收敛的唯一权威信号)。

8. Host 帧(HostFrame)

{ "type": "host/session-added", ... }         // 含 lineage anchor / product origin / project cwd / blank 位
{ "type": "host/session-removed", ... }
{ "type": "host/session-status", ... }        // running 翻转
{ "type": "host/agent-error", ... }           // 无 turn 位置的实时失败唯一出口
{ "type": "host/workspace-changed", ... }     // 每次持久变更后推全量新快照(客户端 upsert;重连基线 = workspace.list)
{ "type": "host/workspace-removed", ... }     // 注册删除增量,绝不暗示目录/日志删除
{ "type": "host/workspace-order-changed", ... }
{ "type": "host/archived-sessions-changed", ... } // 全量归档集
{ "type": "host/remote-event", ... }          // commands/change、settings/document-updated、credentials/updated、llm/adapters-updated、agent-preset/selected …

9. SessionEvent(fold.ts 折叠面)

user/message(含 context 来源)|assistant/chunk(text/reasoning/thought)|assistant/messagetool/calltool/result(status: done/error)|todo/writecompaction/endcompaction/summarycommand/runcommand/doneturn/end(reason.kind=error → error 节点)。

原版 Web 有、桌面 fold.ts 尚未折叠:workflow-run 节点、deliverables(轮尾产物)、message-feedback、子代理创建/结束、goal 状态流转、tool/result 结构化富内容(图片/文件输出)。

10. 会话导出

GET /api/session.export?sessionId=<id>&includeDescendants=true → zip(ArrayBuffer)。主进程收流后弹保存对话框。

11. 升级漂移守门

  1. 每次 @deepseek-ai/dsh 升级:先对 docs/fixtures/ 跑契约测试(信封校验 + fold 折叠),全绿才允许合并。
  2. 顺序铁律:协议变了 → 先改 dsh-runtime.ts/dsh-host.ts + client.ts + fixtures → 再改 UI。
  3. SESSION_FORMAT_VERSION = 0:预览期升级可能拒读旧日志 → 升级前提示导出 zip。