From 4fedef88e0ae76cab0e2695933d0c6ae3602a802 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 25 Feb 2026 16:35:53 -0800 Subject: [PATCH] Use websocket v2 as model-preferred websocket protocol (#12838) --- codex-rs/core/src/client.rs | 6 ++-- .../core/tests/suite/client_websockets.rs | 34 +++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 25de3d33e..26fbf5e07 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -395,8 +395,8 @@ impl ModelClient { /// This combines provider capability and feature gating; both must be true for websocket paths /// to be eligible. /// - /// If websockets are only enabled via model preference (no explicit feature flag), default to - /// v1 behavior. + /// If websockets are only enabled via model preference (no explicit feature flag), prefer the + /// current v2 behavior. pub fn active_ws_version(&self, model_info: &ModelInfo) -> Option { if !self.state.provider.supports_websockets || self.state.disable_websockets.load(Ordering::Relaxed) @@ -406,7 +406,7 @@ impl ModelClient { match self.state.responses_websocket_version { Some(version) => Some(version), - None if model_info.prefer_websockets => Some(ResponsesWebsocketVersion::V1), + None if model_info.prefer_websockets => Some(ResponsesWebsocketVersion::V2), None => None, } } diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 369d69ed2..8ded6da3e 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -300,7 +300,7 @@ async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes( } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_websocket_prewarm_uses_model_preference_when_feature_disabled() { +async fn responses_websocket_prewarm_uses_v2_when_model_prefers_websockets_and_feature_disabled() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![vec![ @@ -324,20 +324,40 @@ async fn responses_websocket_prewarm_uses_model_preference_when_feature_disabled .await .expect("websocket prewarm failed"); - // V1 prewarm only preconnects and should not issue a request. + // V2 prewarm issues a request on the websocket connection. assert_eq!(server.handshakes().len(), 1); - assert_eq!(server.single_connection().len(), 0); + assert_eq!(server.single_connection().len(), 1); + + let handshake = server.single_handshake(); + let openai_beta_header = handshake + .header(OPENAI_BETA_HEADER) + .expect("missing OpenAI-Beta header"); + assert!( + openai_beta_header + .split(',') + .map(str::trim) + .any(|value| value == WS_V2_BETA_HEADER_VALUE) + ); + assert!( + !openai_beta_header + .split(',') + .map(str::trim) + .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) + ); stream_until_complete(&mut client_session, &harness, &prompt).await; assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); assert_eq!(connection.len(), 1); - let turn = connection + let prewarm = connection .first() - .expect("missing turn request") + .expect("missing prewarm request") .body_json(); - assert_eq!(turn["type"].as_str(), Some("response.create")); - assert_eq!(turn["input"], serde_json::to_value(&prompt.input).unwrap()); + assert_eq!(prewarm["type"].as_str(), Some("response.create")); + assert_eq!( + prewarm["input"], + serde_json::to_value(&prompt.input).unwrap() + ); server.shutdown().await; }