Repository navigation
WS client lane: fall back to HTTP/SSE egress when the upstream refuses the upgrade - #3447
Conversation
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.4064-pr.3447.3a4ee92This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr3447.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重(前置):按失败签名/主题检索 open+closed issue 与近期 PR(「WS 上游 / HTTP fallback / handshake refused / transport decouple」,不只标题)。相关但不重复:#2121 是客户端腿(pi native 拦截 codex WS 流量,记录"上游拒绝 WS 表现为 mid-stream 失败、无同轮 fallback");#1859 提供独立 client/upstream checkpoint + ACP-aware WS 但明确无网络 HTTP bridge;#1866 是 sparse terminal/tool 保真目标。没有任何其他条目实现 bili→supplier 的 WS→HTTP/SSE 出口回退。查重:不重复——本 PR 即 #3342(owner 已拍板走 B:agent 独立实现)的实现交付。 这是带 |
…refuses the upgrade A Codex/OpenCode Responses WebSocket client pointed at an HTTP/SSE-only supplier now works through bili: when the supplier refuses the WS upgrade before any generation request is sent, bili's upstream leg re-issues the same turn over plain HTTP /responses+SSE instead of failing. The client leg (WS deltas, local checkpoints, ACP) is untouched; only the bili-to-supplier egress changes. egressHttp sends the full expanded input (no previous_response_id, no WS-only type field), forces stream:true, and returns the supplier response unchanged so the relay forwards it exactly like the non-WS codex lane. Fallback fires only on a genuine pre-generation WS-upgrade refusal; abort and mid-stream errors still hard-fail so a started generation is never replayed. Empty/nonempty generate:false warmups establish a usable client checkpoint and surface incomplete streams explicitly. forceDirect on fetchWithTimeout breaks the ALS transport recursion, and the idle watchdog is released on body settle so a fallback turn does not pin the event loop. Closes #3342
78a70c3 to
4b0795e
Compare
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) Review 进度(独立审核,未照抄 #3342 线程结论):
完整 CI 正在新 head 上跑(静态门禁 + test 分片 + e2e lane),绿后我给最终八问结论。 |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) Review 完成(独立审核:diff、既有行为、测试均自己重读重判,未照抄 #3342 线程结论)。判定:可合并。 先说一件我动过的事:分支原 head 结构化八问:
测试有效性:新 suite 7 个场景断言的都是线上可观测行为(egress 请求形状/auth 随行、tool-call 字节级 args 与顺序、warmup checkpoint 续接、400 透传、未终结流显式错误帧而非挂死、跨连接丢 checkpoint 的全量恢复),不是恒真断言;e2e scenario C 的反转有 mock 契约支撑(拒升级但同端口服务 POST/SSE)且已在 收工判定:可合并 —— 独立审核通过、行为变更已披露、rebase 后合并树完整 CI 全绿。请 owner 自行合并(PR 合并为人工操作)。 |
Review follow-up for #3447 (Closes #3342): the review flagged that no test covered a client disconnect while the HTTP/SSE fallback egress is in flight. The fixture gains a stall mode holding the supplier request open without responding; the new test starts a turn, waits for the egress to land, terminates the WS client, and asserts the turn rejects with the client-close error, the server-side request observes the abort, and no idle-watchdog timer is left behind by the aborted leg.
(ai: vllm-qwen/qwen3.8-27b) Review 收尾补充:此前结论中指出的唯一缺口——客户端在 HTTP fallback egress 飞行中断开时,abort 链路没有测试钉住——已按「小问题直接修」直接补进 PR 分支。 新增 commit
验证:
行为变更:本 commit 纯测试,零行为/配置/wire/kernel 面变化;PR 整体披露的行为变更维持原回复所列两条(pre-generation WS 拒绝 → 同 URL HTTP/SSE 回退; 八问速览(相对本轮补充):① 什么:abort 覆盖缺口;② 原因:原 PR 未覆盖该路径(非缺陷,是测试面遗漏);③ 影响:仅测试面,无运行时影响;④ 修法:stall fixture + 端到端断言 abort 传播与计时器回收;⑤ 回归:无(只加不改);⑥ 缓存:不涉及;⑦ 设计方向:不涉及;⑧ 配置:不涉及。 判定不变:可合并。合并由人来执行:#3447 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.564-master.6ef1928 # this exact build
npm install -g billion-context@master # always the newest merged build
|
What & why
A Codex/OpenCode Responses WebSocket client pointed at an HTTP/SSE-only supplier now works through bili. When the supplier refuses the WS upgrade before any generation request is sent, bili's upstream leg re-issues the same turn over plain
POST /responses+ SSE instead of failing the turn.The client leg is untouched — WS deltas, local checkpoints, and ACP behave exactly as before. Only the bili→supplier egress gains an HTTP/SSE path. Suppliers that do support WS are unaffected: the WS leg still wins whenever its handshake succeeds.
Responses WebSocket handshake failed/responsesWhy: the point of the issue — WS clients should reach suppliers that only speak HTTP/SSE. A pre-generation refusal means the supplier simply isn't offering WS, so retrying over its HTTP endpoint is correct. Once any bytes of a generation have been sent, replaying over a different transport would corrupt the exchange, so those still hard-fail.
How it works
ResponsesWsUpstream.fetch()tries the WS handshake; on a genuine pre-generation refusal (isHandshakeRefusal) it calls the newegressHttp().egressHttp()sends the full expanded input (noprevious_response_id, no WS-onlytypefield — a fresh connection shares no checkpoint), forcesstream:true, and returns the supplier response unchanged, so the existing relay forwards it exactly like the proven non-WS codex lane (2xx → SSE→WS frames, non-2xx verbatim with status preserved).generate:falsewarmups establish a usable client checkpoint (a laterprevious_response_idcontinuation resolves instead of throwingnot_found) and surface incomplete streams explicitly rather than hanging.fetchWithTimeout(..., forceDirect)skips the ALS transport lookup so the fallback doesn't recurse back into the intercepted WS transport; the idle watchdog is released on body settle so a fallback turn doesn't pin the event loop.Scope / non-goals
kernel/**changes).Tests
Targeted suites green locally (full regression = remote CI on the pushed head):
tests/responses-ws-http-fallback.test.ts(new): hermetic upgrade-refusing HTTP/SSE supplier covering probe+turn, tool-call byte-exact args/order/usage, warmup checkpoint + continuation, nonemptygenerate:false, upstream-400 passthrough, unterminated stream, missing-checkpoint recovery.tests/responses-websocket.test.ts: the refused-handshake fault test updated to assert the new fallback while preserving its diagnostic-hygiene and clean-reconnect guarantees.codex-responses-ws,responses-prewarm-generate,ws-bridge: unchanged, still green.tests/e2e/e2e-pi-codex-ws.test.tsscenario C: expectation inverted from “refusal surfaces as failure” to “refusal falls back to HTTP egress and the turn completes.” Reasoned against the mock contract (refuses the upgrade, serves POST/SSE); verified by theci-e2e-pi-wslane — not run locally, per the E2E-is-CI test discipline.Closes #3342