From efe6965cde453ac22862ad1e664c878eebe438cb Mon Sep 17 00:00:00 2001 From: Mark Steinbrick Date: Thu, 5 Feb 2026 10:34:32 -0800 Subject: [PATCH] core: forward windows sandbox headers on Responses requests --- codex-rs/core/src/client.rs | 27 +++++++ codex-rs/core/src/codex.rs | 3 + codex-rs/core/src/lib.rs | 2 + codex-rs/core/tests/responses_headers.rs | 73 +++++++++++++++++++ codex-rs/core/tests/suite/client.rs | 2 + .../core/tests/suite/client_websockets.rs | 2 + 6 files changed, 109 insertions(+) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 570cdc830b..5796c21158 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -50,6 +50,7 @@ use codex_otel::OtelManager; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::config_types::Verbosity as VerbosityConfig; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; @@ -87,6 +88,10 @@ pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = "x-responsesapi-include-timing-metrics"; +pub const X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER: &str = + "x-openai-internal-codex-experimental-windows-sandbox"; +pub const X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER: &str = + "x-openai-internal-codex-elevated-windows-sandbox"; /// Session-scoped state shared by all [`ModelClient`] clones. /// @@ -103,6 +108,7 @@ struct ModelClientState { enable_request_compression: bool, include_timing_metrics: bool, beta_features_header: Option, + windows_sandbox_level: WindowsSandboxLevel, disable_websockets: AtomicBool, } @@ -171,6 +177,7 @@ impl ModelClient { enable_request_compression: bool, include_timing_metrics: bool, beta_features_header: Option, + windows_sandbox_level: WindowsSandboxLevel, ) -> Self { Self { state: Arc::new(ModelClientState { @@ -183,6 +190,7 @@ impl ModelClient { enable_request_compression, include_timing_metrics, beta_features_header, + windows_sandbox_level, disable_websockets: AtomicBool::new(false), }), } @@ -406,6 +414,7 @@ impl ModelClientSession { self.client.state.beta_features_header.as_deref(), web_search_eligible, Some(&self.turn_state), + self.client.state.windows_sandbox_level, turn_metadata_header.as_ref(), ), compression, @@ -775,10 +784,12 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec, web_search_eligible: bool, turn_state: Option<&Arc>>, + windows_sandbox_level: WindowsSandboxLevel, turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { let mut headers = ApiHeaderMap::new(); @@ -788,6 +799,22 @@ fn build_responses_headers( { headers.insert("x-codex-beta-features", header_value); } + headers.insert( + X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER, + HeaderValue::from_static(if windows_sandbox_level == WindowsSandboxLevel::Disabled { + "false" + } else { + "true" + }), + ); + headers.insert( + X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER, + HeaderValue::from_static(if windows_sandbox_level == WindowsSandboxLevel::Elevated { + "true" + } else { + "false" + }), + ); headers.insert( WEB_SEARCH_ELIGIBLE_HEADER, HeaderValue::from_static(if web_search_eligible { "true" } else { "false" }), diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 95c13ec9df..9145fe5083 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1039,6 +1039,7 @@ impl Session { config.features.enabled(Feature::EnableRequestCompression), config.features.enabled(Feature::RuntimeMetrics), Self::build_model_client_beta_features_header(config.as_ref()), + session_configuration.windows_sandbox_level, ), }; @@ -5755,6 +5756,7 @@ mod tests { config.features.enabled(Feature::EnableRequestCompression), config.features.enabled(Feature::RuntimeMetrics), Session::build_model_client_beta_features_header(config.as_ref()), + session_configuration.windows_sandbox_level, ), }; @@ -5885,6 +5887,7 @@ mod tests { config.features.enabled(Feature::EnableRequestCompression), config.features.enabled(Feature::RuntimeMetrics), Session::build_model_client_beta_features_header(config.as_ref()), + session_configuration.windows_sandbox_level, ), }; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index ed7fc6e3e3..041d8a119c 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -132,6 +132,8 @@ pub mod util; pub use apply_patch::CODEX_APPLY_PATCH_ARG1; pub use client::WEB_SEARCH_ELIGIBLE_HEADER; pub use client::X_CODEX_TURN_METADATA_HEADER; +pub use client::X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER; +pub use client::X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER; pub use command_safety::is_dangerous_command; pub use command_safety::is_safe_command; pub use exec_policy::ExecPolicyError; diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 9e70264bf9..57168fd18f 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -12,11 +12,15 @@ use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::WEB_SEARCH_ELIGIBLE_HEADER; use codex_core::WireApi; +use codex_core::X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER; +use codex_core::X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER; +use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_otel::OtelManager; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::WebSearchMode; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use core_test_support::load_default_config_for_test; @@ -98,6 +102,7 @@ async fn responses_stream_includes_subagent_header_on_review() { false, false, None, + WindowsSandboxLevel::Disabled, ); let mut client_session = client.new_session(); @@ -209,6 +214,7 @@ async fn responses_stream_includes_subagent_header_on_other() { false, false, None, + WindowsSandboxLevel::Disabled, ); let mut client_session = client.new_session(); @@ -308,6 +314,72 @@ async fn responses_stream_includes_web_search_eligible_header_false_when_disable ); } +#[tokio::test] +async fn responses_stream_includes_windows_sandbox_headers_false_by_default() { + core_test_support::skip_if_no_network!(); + + let server = responses::start_mock_server().await; + let response_body = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); + + let request_recorder = responses::mount_sse_once(&server, response_body).await; + + let test = test_codex().build(&server).await.expect("build test codex"); + test.submit_turn("hello").await.expect("submit test prompt"); + + let request = request_recorder.single_request(); + assert_eq!( + request + .header(X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER) + .as_deref(), + Some("false") + ); + assert_eq!( + request + .header(X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER) + .as_deref(), + Some("false") + ); +} + +#[tokio::test] +async fn responses_stream_includes_windows_sandbox_headers_when_enabled() { + core_test_support::skip_if_no_network!(); + + let server = responses::start_mock_server().await; + let response_body = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); + + let request_recorder = responses::mount_sse_once(&server, response_body).await; + + let test = test_codex() + .with_config(|config| { + config.features.enable(Feature::WindowsSandboxElevated); + }) + .build(&server) + .await + .expect("build test codex"); + test.submit_turn("hello").await.expect("submit test prompt"); + + let request = request_recorder.single_request(); + assert_eq!( + request + .header(X_OPENAI_INTERNAL_CODEX_EXPERIMENTAL_WINDOWS_SANDBOX_HEADER) + .as_deref(), + Some("true") + ); + assert_eq!( + request + .header(X_OPENAI_INTERNAL_CODEX_ELEVATED_WINDOWS_SANDBOX_HEADER) + .as_deref(), + Some("true") + ); +} + #[tokio::test] async fn responses_respects_model_info_overrides_from_config() { core_test_support::skip_if_no_network!(); @@ -378,6 +450,7 @@ async fn responses_respects_model_info_overrides_from_config() { false, false, None, + WindowsSandboxLevel::Disabled, ); let mut client_session = client.new_session(); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 731ce97c0d..d33fd2b21c 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -27,6 +27,7 @@ use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::Settings; use codex_protocol::config_types::Verbosity; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::MessagePhase; use codex_protocol::models::ReasoningItemContent; @@ -1273,6 +1274,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { false, false, None, + WindowsSandboxLevel::Disabled, ); let mut client_session = client.new_session(); diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index e3a1ef0a8e..f869fb38d7 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -19,6 +19,7 @@ use codex_otel::metrics::MetricsConfig; use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use core_test_support::load_default_config_for_test; @@ -465,6 +466,7 @@ async fn websocket_harness_with_runtime_metrics( false, runtime_metrics_enabled, None, + WindowsSandboxLevel::Disabled, ); WebsocketTestHarness {