Skip to content

v0.3.3 — Local WS stateful protocol implementation + transport-layer refactor

Choose a tag to compare

@dzshzx dzshzx released this 06 Jul 09:55

Fix

  • Local implementation of Codex 0.142's stateful responses_websockets protocol. Each turn, Codex first sends a generate:false prewarm frame, and when the connection is reused it compresses the request into a previous_response_id + incremental (possibly empty) input. The old version forwarded these verbatim to the stateless SSE upstream, so prewarm frames were rejected with a 400 (Unsupported parameter: generate) and incremental frames were forwarded as if they were full input, making the model lose its history/context. Now WsSession (one per downstream connection) digests this protocol locally: prewarm frames are answered locally with a synthetic completed response (resp_codexcomp_prewarm_*); incremental frames are rebuilt to full input from last_input + last_output + delta before entering fold(); unknown ids fail loudly and close the connection, forcing Codex to reconnect with full input. Failed/aborted requests invalidate the stored state, matching Codex's behavior of only recording LastResponse on Completed. The upstream only ever sees stateless, full requests.
  • Real-hardware verification (codex 0.142.5, official subscription): against v0.3.2 this reproduced 2 400s; the fixed version ran 63 requests with all 8 518n−2 folds completing, full context across multi-turn resume, and 0 failures.

Refactor

  • Terminal events are now solely owned by fold(): when round 1 is rejected by the upstream, fold() mints its own response.failed, and RoundOpenError no longer escapes to the transport layer; the three response.incomplete emission sites are converged into a single helper. Every downstream terminal event shape now has exactly one owner, and the error paths are testable directly through the fold() interface.
  • Extracted the drive_fold fold driver: the UpstreamRounds lifecycle (construction, iterating the fold, teardown/close) is collected into one shared async generator, and the SSE and WebSocket handlers degenerate into thin adapters that only do frame encoding, removing two copies of byte-for-byte duplicated driver logic. Along the way the accept-header "force-set then pop" round trip is dropped, and the passthrough path keeps the client's original accept.
  • Explicit assembly: build_app(upstream_base) takes the parameter explicitly (falling back to the CODEXCOMP_UPSTREAM_BASE env var, then the official backend), removing the module-level global app and the "CLI writes the env var, import reads it" load-order coupling; DEFAULT_HOST / DEFAULT_PORT / DEFAULT_UPSTREAM are converged into a single source of truth in codexcomp/__init__.py.
  • Removed --auto-port: a wired proxy must exclusively own the exact port that matches openai_base_url, so a port-drift branch contradicts the contract; when the port is busy it keeps failing loudly and exiting.

Tests

  • Added test_ws.py: covers prewarm, empty incremental, tool-loop, unknown previous_response_id, and post-failure state invalidation.
  • test_fold.py adds error-path cases: round 1 rejected → response.failed, continuation open failed → incomplete/upstream_error (reasoning retained, truncated message not released), and EOF with no terminal → incomplete/upstream_eof.

Logging additionally emits cached_tokens.


中文说明

本版在代理侧本地实现了 Codex 0.142 的有状态 responses_websockets 协议(prewarm 本地应答、增量帧重建为全量、未知 id 响亮失败),使上游始终只见无状态全量请求,真机 63 请求 8 次折叠全通过。同时把所有下游终局事件收归 fold() 唯一所有、提取共享的 drive_fold 驱动器、显式化 build_app 装配并移除 --auto-port,另补 test_ws.pytest_fold.py 错误路径用例。