Skip to content

API 代理packages/host/apiproxy)是每一个 client 都要与之对话的共享 /api surface 的 host 侧实现。尽管名字如此,它并不只是模型 API 转发器:它是应用的 API 载体——即契约层,web UI 以及任何未来的 client 都靠它到达会话、host 能力与事件。Typert gateway(见 API Gateway)在同一个路由上认领类型化的端点;一切未被认领的请求都会落到代理。

角色
@deepseek-ai/dsh-host-apiproxyApiProxyServicectx.apiProxy)、createApiProxy、契约层、fetch 载体、会话导出

包布局

来自 packages/host/apiproxy/README.md,这个包分三层:

  • 契约层src/api/)——零 Node 依赖的 TypeScript API 契约,浏览器可直接导入。正是它让线路保持诚实:同一套类型被编译进两个面。
  • Fetch 载体src/fetch/)—— host 侧的 toFetchHandler(把代理包装成 Connection HTTP 桥的 fetch handler),以及 client 侧的 AbstractApiClient 加平台子类。
  • Host 实现src/api-proxy.ts)—— createApiProxy 加默认导出的 ApiProxyService gateway 插件,配置 { nativeOpen?, sessionExportCompressionLevel?, coldBlankProbeMaxBytes? },提供 ctx.apiProxy

这个包本身不注册任何路由;诸如 HTTP 之类的载体会包装 ctx.apiProxy。随附的 Web 装配在 packages/bundle/web-app/cordis.patch.yml 中接线它。

四象限线路

线路消息在 谁发起 × 请求/响应 上构成一个可判别联合,与物理通道解耦:

消息方向承载方式
ClientRequestClient → HostPOST /api/<method> 的 body
ServerResponseHost → Client该 POST 的响应 body
ServerRequestHost → ClientSSE 帧
ClientResponseClient → HostPOST /api/respond 的 body

响应始终回显匹配请求的 rpcId,绝不新增一个。方法的参数/返回结构只存在于领域接口签名中(SessionsApiHostApiEventsApi);RpcMethodMap 注册这些方法,其余每个位置都通过 RequestPayload<K> / ResponseValue<K> 推导。

校验收两层:Zod schema 锚定 satisfies z.ZodType<Wire<T>>,先解析信封,再解析业务 payload,按方法派发。业务错误走 RpcResult 的错误分支(RpcErrorDetailsMap 封闭错误码集合);HTTP 状态只表达载体。

README 中有一个与安全相关的细节:每个 /api POST 必须声明 application/json 媒体类型——否则在派发之前就以 415 拒绝,因此跨站的"简单"请求(浏览器不会为它发 CORS 预检)永远无法盲目执行一个有副作用的盲方法。

服务端请求与 respond 通道

代理不只是请求/响应。Host 发起的事件——会话更新、agent 状态、问题提示——以 ServerRequest SSE 帧流式推送。client 通过向 /api/respondClientResponse POST 回答问题。问题响应在第一个答案认领它之前会对照其挂起请求进行校验;一个多选项条目可以在 selected 中携带请求的选项标签,同时在 custom 中携带非空文本,而单选项条目则必须二选一。重复标签、未知标签、不匹配的 id、不完整的批次以及空的 custom 文本都会被判定为 bad-response 拒绝。

分层与协议决策记录在 RFC 笔记 docs/../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md2026-07-19-gui-web-client-architecture.md 中(.agents/notes/ 下的仓库相对路径)。

模型请求中继与 agent-default-model

代理也是 client 模型流量落脚的地方。ApiProxyService 消费 ctx.agentDefaultModel(见 模型选择与默认值);它不拥有 provider/模型配置或 settings 区块。共享服务在 agent-default-model settings 区块下注册 { provider, model, reasoningEffort? }:基础 bundle 的装配条目是下层,settings.yaml 把用户的选择叠加在它之上。

会话在每次访问时从三层中解析其模型选择:

  1. 本次进程中做出的选择,
  2. 否则是会话最新记入日志的 request/header
  3. 否则是部署默认值。

一个跑过 turn 的会话从其日志推导选择,而一个空白的会话则观察在其创建之后保存的默认值。session.selectModel 把一次被接受的切换保存为部署默认值——没有单独的额外手势——并存储解析出的 ModelSelection,其中包括一个由适配器物化的默认 effort。

原生打开与会话导出

这个包还顺带承载两个仅 host 端的能力:

  • nativePathOpenersrc/native-path-opener.ts)—— 用操作系统打开本地路径(原生目录选择器的对应侧;见 Host 平台)。
  • sessionExportsrc/session-export.ts)—— 产出可供下载的会话导出(ZIP)并服务给 client,带 sessionExportCompressionLevel 配置。

关键源文件

仓库相对路径提供什么
packages/host/apiproxy/src/api-proxy.tscreateApiProxyApiProxyServicectx.apiProxy
packages/host/apiproxy/src/api/浏览器安全契约层(零 Node 依赖)
packages/host/apiproxy/src/fetch/toFetchHandlerAbstractApiClient 加平台子类
packages/host/apiproxy/src/session-export.ts会话导出(ZIP)
packages/bundle/web-app/cordis.patch.yml随附的 Web 装配挂载它的位置

延伸阅读

  • API Gateway —— /api 路由中类型化 RPC 的那一半
  • Host 平台 —— webserver、frontend-static、plugin 清单
  • 模型选择与默认值 —— agent-default-model seam
  • 前端:Client 运行时 —— 浏览器如何消费 ctx.apiProxy
  • 仓库文件:packages/host/apiproxy/README.md.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md