fix(openai): pass through websocket control frames

Forward non-create text frames unchanged over the original upstream WebSocket while preserving response.create admission and serialization. Keep the control transport available after completion and relay idle upstream events without creating another inference attempt.

Cover unknown frames, late interrupts, terminal settlement, binding revocation, and connection reuse.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-10-09 16:26:31 +08:00
co-authored by Codex
parent d875a62652
commit a4b73099b5
16 changed files with 652 additions and 359 deletions
+9 -6
View File
@@ -309,13 +309,16 @@ turn metadata 的 `workspaces` 也会保留仓库绝对路径、Git 远端地址
已升级的 Responses 连接在入站解析前和出站写入前经过 `websocket` 中间件,完整消息与主动发送接口见
[SDK 洋葱中间件](../backend/crates/gateway-plugin/sdk/docs/capabilities.md#洋葱中间件)。下述规则描述默认协议处理
Responses WebSocket 接受文本 `response.create` 和 `response.interrupt`,创建请求在同一连接串行执行。当前响应期间收到的后续业务帧
Responses WebSocket 接受文本消息,`response.create` 在同一连接串行执行。当前响应期间收到的后续创建请求
留在有界接收队列中,待当前响应完成终结和写出后再逐条校验、准入与执行,不因请求提前到达而断开。
接收队列容量为 32 个事件,超载仍关闭连接;Ping/Pong、客户端关闭和服务关闭不等待队列中的请求执行。
活动响应期间会即时处理 `{"type":"response.interrupt","response_id":"当前响应 ID","mode":"discard_partial_items"}`。
中断只能发送到该执行占用的原上游 WS,不重新选号或创建推理 attempt;重复中断合并为一次发送。
ID 不匹配、没有可中断响应、实际走 HTTP 或 Provider 不支持控制时返回 `400` 协议错误,原执行继续;
客户端需要终止这类执行时可关闭连接,后续按既有续接合同恢复。
接收队列容量为 32 个事件,超载仍关闭连接;Ping/Pong、客户端关闭和服务关闭不等待队列中的请求执行
其余文本消息原样发送到当前执行绑定的原上游 WebSocket,包括
`response.interrupt` 和未知类型;类型、响应 ID、模式及扩展字段由上游校验。
控制帧不重新选号、不创建推理 attempt,也不合并重复消息。响应结束后原连接仍可接收控制帧并返回上游事件;
下一轮执行使用新绑定的控制入口。尚未建立原连接、原连接已失效、实际走 HTTP 或 Provider 未提供控制通道时返回 `400`,
不能通过控制帧新建上游连接
中断后继续转发上游事件与终态;只有 `response.incomplete` 的 `incomplete_details.reason` 为
`interrupted` 时,才按官方中断语义保留原连接续接能力。部分输出是否被丢弃以上游终态为准。
控制帧发送成功不等于上游已确认中断;若上游仍返回 `response.completed`,按正常完成处理
+4 -3
View File
@@ -708,9 +708,10 @@ Admin 的手动重置复用同一账本与 Key 行锁,在一个事务中清零
- SSE 持有并发名额直到终态;WebSocket 每个 `response.create` 独立准入,空闲连接不占名额。
Core 在每次请求开始时重新鉴权并冻结当前策略,既有连接也应用已发布的限额与授权范围变更。
换号和内部重试复用同一名额,Key 与账号并发上限同时生效;预算拒绝或启动失败释放名额
- 响应中断入口由 Core 的根执行持有,API 只控制当前连接正在交付的执行。Provider 在收到上游
`response.created` 后注册活动响应 owner,在终态、失败或断连时释放;控制句柄本身不延长 owner
生命周期。中断信号交给占用原账号、原 socket 的 exchange,不经过全局响应查询、新选号或独立 attempt。
- Core 的根执行携带不解释协议的连接控制端口,Provider 在请求写入上游后绑定原 socket。
API 在响应期间发送控制帧,终态后还可读取原连接事件;空闲读取必须在下一轮执行前取消,
与 Provider 正文消费共用单一有界接收通道。连接 owner 持有控制目标,复用时替换,销毁时释放;
控制句柄使用弱引用,不延长连接生命周期,也不经过全局响应查询、新选号或独立 attempt。
API 暂存的下一轮请求和连接泵接收队列共用容量名额,控制帧处理不取消中间件正在读取响应体的 future
完成、失败、取消和断连使用同一结算路径,在结束时以网关请求 ID 幂等累计已取得费用。