diff --git a/codex-rs/core/tests/common/responses.rs b/codex-rs/core/tests/common/responses.rs index 9faca23888..c6ab83922e 100644 --- a/codex-rs/core/tests/common/responses.rs +++ b/codex-rs/core/tests/common/responses.rs @@ -818,15 +818,24 @@ where } pub async fn mount_compact_json_once(server: &MockServer, body: serde_json::Value) -> ResponseMock { - let (mock, response_mock) = compact_mock(); - mock.respond_with( + mount_compact_response_once( + server, ResponseTemplate::new(200) .insert_header("content-type", "application/json") - .set_body_json(body.clone()), + .set_body_json(body), ) - .up_to_n_times(1) - .mount(server) - .await; + .await +} + +pub async fn mount_compact_response_once( + server: &MockServer, + response: ResponseTemplate, +) -> ResponseMock { + let (mock, response_mock) = compact_mock(); + mock.respond_with(response) + .up_to_n_times(1) + .mount(server) + .await; response_mock } diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index d5dbcd6d03..e9711866fd 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -3307,7 +3307,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { insta::assert_snapshot!( "pre_turn_compaction_context_window_exceeded_shapes", sectioned_request_shapes( - "Pre-turn auto-compaction context-window failure : compaction request excludes the incoming user message and the turn errors.", + "Pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.", &[( "Local Compaction Request (Incoming User Excluded)", &requests[1] diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index fcc96cde08..931c9c8798 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -26,6 +26,7 @@ use core_test_support::wait_for_event; use core_test_support::wait_for_event_match; use pretty_assertions::assert_eq; use serde_json::Value; +use wiremock::ResponseTemplate; fn approx_token_count(text: &str) -> i64 { i64::try_from(text.len().saturating_add(3) / 4).unwrap_or(i64::MAX) @@ -1689,7 +1690,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_failure_stops_without insta::assert_snapshot!( "remote_pre_turn_compaction_failure_shapes", sectioned_request_shapes( - "Remote pre-turn auto-compaction parse failure : compaction request excludes the incoming user message and the turn stops.", + "Remote pre-turn auto-compaction parse failure: compaction request excludes the incoming user message and the turn stops.", &[( "Remote Compaction Request (Incoming User Excluded)", &include_attempt_request @@ -1709,6 +1710,113 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_failure_stops_without Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +// TODO(ccunningham): Update once remote pre-turn compaction context-overflow handling includes +// incoming user input and emits richer oversized-input messaging. +async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceeded() -> Result<()> { + skip_if_no_network!(Ok(())); + + let harness = TestCodexHarness::with_builder( + test_codex() + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config(|config| { + config.model_auto_compact_token_limit = Some(200); + }), + ) + .await?; + let codex = harness.test().codex.clone(); + + let responses_mock = responses::mount_sse_sequence( + harness.server(), + vec![responses::sse(vec![ + responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"), + responses::ev_completed_with_tokens("r1", 500), + ])], + ) + .await; + + let compact_mock = responses::mount_compact_response_once( + harness.server(), + ResponseTemplate::new(400).set_body_json(serde_json::json!({ + "error": { + "code": "context_length_exceeded", + "message": "Your input exceeds the context window of this model. Please adjust your input and try again." + } + })), + ) + .await; + let post_compact_turn_mock = responses::mount_sse_once( + harness.server(), + responses::sse(vec![ + responses::ev_assistant_message("m2", "REMOTE_POST_COMPACT_SHOULD_NOT_RUN"), + responses::ev_completed_with_tokens("r2", 80), + ]), + ) + .await; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "USER_ONE".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "USER_TWO".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + let error_message = wait_for_event_match(&codex, |event| match event { + EventMsg::Error(err) => Some(err.message.clone()), + _ => None, + }) + .await; + wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + assert_eq!(compact_mock.requests().len(), 1); + let requests = responses_mock.requests(); + assert_eq!( + requests.len(), + 1, + "expected no post-compaction follow-up turn request after compact failure" + ); + assert!( + post_compact_turn_mock.requests().is_empty(), + "expected turn to stop after compaction failure" + ); + + let include_attempt_request = compact_mock.single_request(); + let include_attempt_shape = request_input_shape(&include_attempt_request); + insta::assert_snapshot!( + "remote_pre_turn_compaction_context_window_exceeded_shapes", + sectioned_request_shapes( + "Remote pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.", + &[( + "Remote Compaction Request (Incoming User Excluded)", + &include_attempt_request + ),] + ) + ); + assert!( + !include_attempt_shape.contains("USER_TWO"), + "current behavior excludes incoming user message from remote pre-turn compaction input" + ); + assert!( + error_message.to_lowercase().contains("context window"), + "expected context window failure to surface, got {error_message}" + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap index 7c44b9db2f..b8c711a3d6 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap @@ -1,8 +1,8 @@ --- source: core/tests/suite/compact.rs -expression: "sectioned_request_shapes(\"Pre-turn auto-compaction context-window failure : compaction request excludes the incoming user message and the turn errors.\",\n&[(\"Local Compaction Request (Incoming User Excluded)\",\n&requests[1]),])" +expression: "sectioned_request_shapes(\"Pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.\",\n&[(\"Local Compaction Request (Incoming User Excluded)\", &requests[1]),])" --- -Scenario: Pre-turn auto-compaction context-window failure : compaction request excludes the incoming user message and the turn errors. +Scenario: Pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors. ## Local Compaction Request (Incoming User Excluded) 00:message/developer: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_context_window_exceeded_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_context_window_exceeded_shapes.snap new file mode 100644 index 0000000000..0cd97191b2 --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_context_window_exceeded_shapes.snap @@ -0,0 +1,12 @@ +--- +source: core/tests/suite/compact_remote.rs +expression: "sectioned_request_shapes(\"Remote pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.\",\n&[(\"Remote Compaction Request (Incoming User Excluded)\",\n&include_attempt_request),])" +--- +Scenario: Remote pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors. + +## Remote Compaction Request (Incoming User Excluded) +00:message/developer: +01:message/user: +02:message/user:> +03:message/user:USER_ONE +04:message/assistant:REMOTE_FIRST_REPLY diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_failure_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_failure_shapes.snap index ead21e9797..69472afb03 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_failure_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact_remote__remote_pre_turn_compaction_failure_shapes.snap @@ -1,8 +1,8 @@ --- source: core/tests/suite/compact_remote.rs -expression: "sectioned_request_shapes(\"Remote pre-turn auto-compaction parse failure : compaction request excludes the incoming user message and the turn stops.\",\n&[(\"Remote Compaction Request (Incoming User Excluded)\",\n&include_attempt_request),])" +expression: "sectioned_request_shapes(\"Remote pre-turn auto-compaction parse failure: compaction request excludes the incoming user message and the turn stops.\",\n&[(\"Remote Compaction Request (Incoming User Excluded)\",\n&include_attempt_request),])" --- -Scenario: Remote pre-turn auto-compaction parse failure : compaction request excludes the incoming user message and the turn stops. +Scenario: Remote pre-turn auto-compaction parse failure: compaction request excludes the incoming user message and the turn stops. ## Remote Compaction Request (Incoming User Excluded) 00:message/developer: