Skip to content

websocket-client: add proxy-aware connector#31622

Merged
bolinfest merged 1 commit into
mainfrom
codex/websocket-client
Jul 8, 2026
Merged

websocket-client: add proxy-aware connector#31622
bolinfest merged 1 commit into
mainfrom
codex/websocket-client

Conversation

@bolinfest

@bolinfest bolinfest commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Why

The route-aware WebSocket connection setup in #31441 is transport infrastructure rather than Responses API protocol logic. Landing it first in a dedicated crate keeps codex-api focused on request and response behavior and makes the transport reusable by future WebSocket clients.

WebSockets must also apply the same effective outbound proxy and custom-CA policy as HTTP without disabling the lower-latency WebSocket path. Requiring an HttpClientFactory when constructing the connector makes proxy-policy resolution part of the API instead of an optional call-site convention.

This PR is an independent prerequisite based directly on main. After it merges, #31441 can rebase onto it and replace its in-crate connector with this API.

What changed

  • Add a new codex-websocket-client workspace crate with a WebSocketConnector constructed from the effective HttpClientFactory.
  • Resolve every destination through that factory before connecting, then support direct connections, transport-default routing, HTTP proxies, and TLS-encrypted HTTPS proxies.
  • Preserve custom-CA trust for proxy and target TLS handshakes and preserve Happy Eyeballs fallback for explicit direct and proxy routes.
  • Expose an established WebSocketConnection as a uniform Stream and Sink, hiding route-specific transport types from protocol clients.
  • Add focused integration-style coverage for the public connector and message stream, real WSS over direct and CONNECT routes, implicit and explicit HTTPS proxy ports, and stalled-address-family fallback.

Review guide

  1. codex-rs/websocket-client/src/lib.rs defines the small public API and the factory-required policy invariant.
  2. codex-rs/websocket-client/src/dialer.rs contains DNS, TCP, proxy tunneling, TLS, and WebSocket handshake setup.
  3. codex-rs/websocket-client/src/dialer_tests.rs verifies the public stream, direct and proxied WSS paths, HTTPS port preservation, and Happy Eyeballs timing.
  4. There is intentionally no consumer migration here; core: preserve Responses WebSockets with system proxy #31441 will become the first consumer after this prerequisite merges.

Test plan

  • cargo check -p codex-websocket-client --tests
  • just test -p codex-websocket-client
  • cargo shear
  • just bazel-lock-check

) -> Result<(ConnectionInner, Response), WebSocketError> {
let stream: Box<dyn AsyncIo> = match proxy_route {
OutboundProxyRoute::TransportDefault => {
let (stream, response) = connect_async_tls_with_config(

@viyatb-oai viyatb-oai Jul 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TransportDefault can bypass environment proxies here. Both the default ReqwestDefault policy and the system-proxy fallback can reach this branch, but Tungstenite opens a direct TCP connection and does not read HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, or NO_PROXY. Could we resolve the proxy settings before this point, so the dialer always gets either Proxy or Direct? It would be good to add a test for each path too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging this. This repository enables the proxy feature on our pinned tokio-tungstenite fork. In that revision, the default connector calls ProxyConfig::from_env before opening TCP: https://github.com/openai-oss-forks/tokio-tungstenite/blob/0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186/src/connect.rs#L108-L117. from_env honors HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY: https://github.com/openai-oss-forks/tungstenite-rs/blob/4fffad30fe373adbdcffab9545e9e9bf4f2fc19f/src/proxy.rs#L54-L68 and https://github.com/openai-oss-forks/tungstenite-rs/blob/4fffad30fe373adbdcffab9545e9e9bf4f2fc19f/src/proxy.rs#L95-L119. So TransportDefault here means delegate environment fallback to the transport, rather than connect directly.

I added a comment beside this branch in 2df2dfa to make that contract explicit. The crate tests cover each route through public_connector_uses_factory_and_exposes_stream_and_sink (ReqwestDefault -> TransportDefault), direct_route_connects_secure_websocket, and the HTTP/HTTPS proxy tunnel tests. The pinned transport also has its own environment-proxy integration coverage: https://github.com/openai-oss-forks/tokio-tungstenite/blob/0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186/tests/proxy_integration.rs#L13-L69. I avoided duplicating process-environment mutation in this crate, consistent with the repository test guidance.

@bolinfest
bolinfest force-pushed the codex/websocket-client branch from c35143f to 2df2dfa Compare July 8, 2026 19:47
@bolinfest
bolinfest requested a review from viyatb-oai July 8, 2026 19:49
@bolinfest
bolinfest marked this pull request as ready for review July 8, 2026 19:52
@bolinfest
bolinfest merged commit 2780bd5 into main Jul 8, 2026
46 checks passed
@bolinfest
bolinfest deleted the codex/websocket-client branch July 8, 2026 19:57
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants