mirror of
https://github.com/openai/codex.git
synced 2026-05-23 20:44:50 +00:00
Align compaction tests with incoming-item and empty-history behavior
This commit is contained in:
@@ -203,11 +203,6 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let sse = responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"),
|
||||
responses::ev_completed_with_tokens("r1", 200),
|
||||
]);
|
||||
responses::mount_sse_sequence(&server, vec![sse]).await;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
write_mock_responses_config_toml(
|
||||
|
||||
@@ -4311,7 +4311,15 @@ pub(crate) async fn run_turn(
|
||||
);
|
||||
EventMsg::Error(CodexErr::ContextWindowExceeded.to_error_event(Some(message)))
|
||||
}
|
||||
other => EventMsg::Error(other.to_error_event(None)),
|
||||
other => {
|
||||
let compact_error_prefix =
|
||||
if should_use_remote_compact_task(&turn_context.provider) {
|
||||
"Error running remote compact task"
|
||||
} else {
|
||||
"Error running local compact task"
|
||||
};
|
||||
EventMsg::Error(other.to_error_event(Some(compact_error_prefix.to_string())))
|
||||
}
|
||||
};
|
||||
sess.send_event(&turn_context, event).await;
|
||||
return None;
|
||||
|
||||
@@ -510,6 +510,10 @@ async fn manual_compact_emits_api_and_local_token_usage_events() {
|
||||
|
||||
let server = start_mock_server().await;
|
||||
|
||||
let sse_turn = sse(vec![
|
||||
ev_assistant_message("m0", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r0", 0),
|
||||
]);
|
||||
// Compact run where the API reports zero tokens in usage. Our local
|
||||
// estimator should still compute a non-zero context size for the compacted
|
||||
// history.
|
||||
@@ -517,7 +521,7 @@ async fn manual_compact_emits_api_and_local_token_usage_events() {
|
||||
ev_assistant_message("m1", SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r1", 0),
|
||||
]);
|
||||
mount_sse_once(&server, sse_compact).await;
|
||||
mount_sse_sequence(&server, vec![sse_turn, sse_compact]).await;
|
||||
|
||||
let model_provider = non_openai_model_provider(&server);
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
@@ -526,39 +530,42 @@ async fn manual_compact_emits_api_and_local_token_usage_events() {
|
||||
});
|
||||
let codex = builder.build(&server).await.unwrap().codex;
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "seed compact history".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
// Trigger manual compact and collect TokenCount events for the compact turn.
|
||||
codex.submit(Op::Compact).await.unwrap();
|
||||
|
||||
// First TokenCount: from the compact API call (usage.total_tokens = 0).
|
||||
let first = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::TokenCount(tc) => tc
|
||||
.info
|
||||
.as_ref()
|
||||
.map(|info| info.last_token_usage.total_tokens),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
let mut compact_turn_token_totals = Vec::new();
|
||||
loop {
|
||||
let event = wait_for_event(&codex, |_| true).await;
|
||||
match event {
|
||||
EventMsg::TokenCount(tc) => {
|
||||
if let Some(info) = tc.info {
|
||||
compact_turn_token_totals.push(info.last_token_usage.total_tokens);
|
||||
}
|
||||
}
|
||||
EventMsg::TurnComplete(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Second TokenCount: from the local post-compaction estimate.
|
||||
let last = wait_for_event_match(&codex, |ev| match ev {
|
||||
EventMsg::TokenCount(tc) => tc
|
||||
.info
|
||||
.as_ref()
|
||||
.map(|info| info.last_token_usage.total_tokens),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
|
||||
// Ensure the compact task itself completes.
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
assert_eq!(
|
||||
first, 0,
|
||||
"expected first TokenCount from compact API usage to be zero"
|
||||
assert!(
|
||||
compact_turn_token_totals.contains(&0),
|
||||
"expected compact turn token events to include API-reported zero usage"
|
||||
);
|
||||
assert!(
|
||||
last > 0,
|
||||
"second TokenCount should reflect a non-zero estimated context size after compaction"
|
||||
compact_turn_token_totals.iter().any(|total| *total > 0),
|
||||
"expected compact turn token events to include a non-zero local estimate"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2414,35 +2421,33 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
"compact requests should consistently include or omit the summarization prompt"
|
||||
);
|
||||
|
||||
let first_request_user_texts = requests[0].message_input_texts("user");
|
||||
let first_turn_user_index = first_request_user_texts
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.unwrap_or_else(|| panic!("first turn request missing user messages"));
|
||||
assert_eq!(
|
||||
first_request_user_texts[first_turn_user_index], first_user_message,
|
||||
"first turn request should end with the submitted user message"
|
||||
);
|
||||
let seeded_user_prefix = &first_request_user_texts[..first_turn_user_index];
|
||||
|
||||
let final_request_user_texts = requests
|
||||
.last()
|
||||
.unwrap_or_else(|| panic!("final turn request missing for {final_user_message}"))
|
||||
.message_input_texts("user");
|
||||
assert!(
|
||||
final_request_user_texts
|
||||
.as_slice()
|
||||
.starts_with(seeded_user_prefix),
|
||||
"final request should start with seeded user prefix from first request: {seeded_user_prefix:?}"
|
||||
);
|
||||
let final_output = &final_request_user_texts[seeded_user_prefix.len()..];
|
||||
let Some(first_user_index) = final_request_user_texts
|
||||
.iter()
|
||||
.position(|text| text == first_user_message)
|
||||
else {
|
||||
panic!("final request missing first user message: {final_request_user_texts:?}");
|
||||
};
|
||||
let final_output = &final_request_user_texts[first_user_index..];
|
||||
let expected = vec![
|
||||
first_user_message.to_string(),
|
||||
second_user_message.to_string(),
|
||||
expected_second_summary,
|
||||
final_user_message.to_string(),
|
||||
];
|
||||
assert_eq!(final_output, expected.as_slice());
|
||||
let mut final_output_iter = final_output.iter();
|
||||
for expected_text in &expected {
|
||||
final_output_iter
|
||||
.position(|text| text == expected_text)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"final request should preserve expected user-message order; missing `{expected_text}` in {final_output:?}"
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -2713,8 +2718,8 @@ async fn auto_compact_clamps_config_limit_to_context_window() {
|
||||
|
||||
let auto_compact_body = auto_compact_mock.single_request().body_json().to_string();
|
||||
assert!(
|
||||
body_contains_text(&auto_compact_body, SUMMARIZATION_PROMPT),
|
||||
"auto compact should run with the summarization prompt when config limit exceeds context"
|
||||
body_contains_text(&auto_compact_body, "OVER_LIMIT_TURN"),
|
||||
"auto compact should run when the configured limit clamps to the model context window"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2928,7 +2933,6 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
// TODO(ccunningham): Update once pre-turn compaction includes incoming user input.
|
||||
async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_message() {
|
||||
skip_if_no_network!();
|
||||
|
||||
@@ -3016,7 +3020,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
||||
insta::assert_snapshot!(
|
||||
"pre_turn_compaction_including_incoming_shapes",
|
||||
format_labeled_requests_snapshot(
|
||||
"Pre-turn auto-compaction with a context override emits the context diff in the compact request while the incoming user message is still excluded.",
|
||||
"Pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.",
|
||||
&[
|
||||
("Local Compaction Request", &requests[2]),
|
||||
("Local Post-Compaction History Layout", &requests[3]),
|
||||
@@ -3025,10 +3029,17 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
||||
);
|
||||
let compact_request_user_texts = requests[2].message_input_texts("user");
|
||||
assert!(
|
||||
!compact_request_user_texts
|
||||
compact_request_user_texts
|
||||
.iter()
|
||||
.any(|text| text == "USER_THREE"),
|
||||
"current behavior excludes incoming user message from pre-turn compaction input"
|
||||
"pre-turn compaction request should include the incoming user message"
|
||||
);
|
||||
let compact_request_user_images = requests[2].message_input_image_urls("user");
|
||||
assert!(
|
||||
compact_request_user_images
|
||||
.iter()
|
||||
.any(|url| url == image_url.as_str()),
|
||||
"pre-turn compaction request should include incoming user image content"
|
||||
);
|
||||
let follow_up_user_texts = requests[3].message_input_texts("user");
|
||||
assert!(
|
||||
@@ -3478,15 +3489,11 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
|
||||
|
||||
let server = start_mock_server().await;
|
||||
|
||||
let compact_turn = sse(vec![
|
||||
ev_assistant_message("m1", "MANUAL_EMPTY_SUMMARY"),
|
||||
ev_completed_with_tokens("r1", 90),
|
||||
]);
|
||||
let follow_up_turn = sse(vec![
|
||||
ev_assistant_message("m2", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r2", 80),
|
||||
ev_assistant_message("m1", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r1", 80),
|
||||
]);
|
||||
let request_log = mount_sse_sequence(&server, vec![compact_turn, follow_up_turn]).await;
|
||||
let request_log = mount_sse_once(&server, follow_up_turn).await;
|
||||
|
||||
let model_provider = non_openai_model_provider(&server);
|
||||
let codex = test_codex()
|
||||
@@ -3517,18 +3524,15 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
|
||||
let requests = request_log.requests();
|
||||
assert_eq!(
|
||||
requests.len(),
|
||||
2,
|
||||
"expected manual /compact request and follow-up turn request"
|
||||
1,
|
||||
"manual /compact with no prior user should be a no-op; only the follow-up turn should hit /responses"
|
||||
);
|
||||
|
||||
insta::assert_snapshot!(
|
||||
"manual_compact_without_prev_user_shapes",
|
||||
format_labeled_requests_snapshot(
|
||||
"Manual /compact with no prior user turn currently still issues a compaction request; follow-up turn carries canonical context and the new user message.",
|
||||
&[
|
||||
("Local Compaction Request", &requests[0]),
|
||||
("Local Post-Compaction History Layout", &requests[1]),
|
||||
]
|
||||
"Manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.",
|
||||
&[("Local Post-Compaction History Layout", &requests[0]),]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -579,9 +579,9 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> {
|
||||
insta::assert_snapshot!(
|
||||
"remote_pre_turn_compaction_failure_shapes",
|
||||
format_labeled_requests_snapshot(
|
||||
"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 includes incoming user content and the turn stops.",
|
||||
&[(
|
||||
"Remote Compaction Request (Incoming User Excluded)",
|
||||
"Remote Compaction Request (Incoming User Included)",
|
||||
&first_compact_mock.single_request()
|
||||
),]
|
||||
)
|
||||
@@ -1310,7 +1310,6 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
// TODO(ccunningham): Update once remote pre-turn compaction includes incoming user input.
|
||||
async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_user_message()
|
||||
-> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
@@ -1390,13 +1389,20 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
|
||||
insta::assert_snapshot!(
|
||||
"remote_pre_turn_compaction_including_incoming_shapes",
|
||||
format_labeled_requests_snapshot(
|
||||
"Remote pre-turn auto-compaction with a context override emits the context diff in the compact request while excluding the incoming user message.",
|
||||
"Remote pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.",
|
||||
&[
|
||||
("Remote Compaction Request", &compact_request),
|
||||
("Remote Post-Compaction History Layout", &requests[2]),
|
||||
]
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
compact_request
|
||||
.message_input_texts("user")
|
||||
.iter()
|
||||
.any(|text| text == "USER_THREE"),
|
||||
"remote pre-turn compaction request should include incoming user message"
|
||||
);
|
||||
assert_eq!(
|
||||
requests[2]
|
||||
.message_input_texts("user")
|
||||
@@ -1928,10 +1934,6 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
|
||||
)
|
||||
.await;
|
||||
|
||||
let compact_mock =
|
||||
responses::mount_compact_json_once(harness.server(), serde_json::json!({ "output": [] }))
|
||||
.await;
|
||||
|
||||
codex.submit(Op::Compact).await?;
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
@@ -1946,21 +1948,12 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
|
||||
.await?;
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
assert_eq!(
|
||||
compact_mock.requests().len(),
|
||||
1,
|
||||
"current behavior still issues remote compaction for manual /compact without prior user"
|
||||
);
|
||||
let compact_request = compact_mock.single_request();
|
||||
let follow_up_request = responses_mock.single_request();
|
||||
insta::assert_snapshot!(
|
||||
"remote_manual_compact_without_prev_user_shapes",
|
||||
format_labeled_requests_snapshot(
|
||||
"Remote manual /compact with no prior user turn still issues a compact request; follow-up turn carries canonical context and new user message.",
|
||||
&[
|
||||
("Remote Compaction Request", &compact_request),
|
||||
("Remote Post-Compaction History Layout", &follow_up_request),
|
||||
]
|
||||
"Remote manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.",
|
||||
&[("Remote Post-Compaction History Layout", &follow_up_request),]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -200,24 +200,11 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
&fork_arr[..compact_arr.len()]
|
||||
);
|
||||
|
||||
let expected_model = requests[0]["model"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let prompt = requests[0]["instructions"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let permissions_message = requests[0]["input"][0].clone();
|
||||
let user_instructions = requests[0]["input"][1]["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let environment_context = requests[0]["input"][2]["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let tool_calls = json!(requests[0]["tools"].as_array());
|
||||
assert_eq!(requests.len(), 5);
|
||||
let expected_model = requests[0]["model"].as_str();
|
||||
for request in &requests {
|
||||
assert_eq!(request["model"].as_str(), expected_model);
|
||||
}
|
||||
let prompt_cache_key = requests[0]["prompt_cache_key"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
@@ -226,433 +213,46 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
assert_ne!(
|
||||
prompt_cache_key, fork_prompt_cache_key,
|
||||
"forked request should use a new prompt cache key"
|
||||
);
|
||||
let summary_after_compact = extract_summary_message(&requests[2], SUMMARY_TEXT);
|
||||
let summary_after_resume = extract_summary_message(&requests[3], SUMMARY_TEXT);
|
||||
let summary_after_fork = extract_summary_message(&requests[4], SUMMARY_TEXT);
|
||||
let user_turn_1 = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"effort": "medium",
|
||||
"summary": "auto"
|
||||
},
|
||||
"store": false,
|
||||
"stream": true,
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let compact_1 = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "output_text",
|
||||
"text": "FIRST_REPLY"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": SUMMARIZATION_PROMPT
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"effort": "medium",
|
||||
"summary": "auto"
|
||||
},
|
||||
"store": false,
|
||||
"stream": true,
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let user_turn_2_after_compact = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
},
|
||||
summary_after_compact,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_COMPACT"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"effort": "medium",
|
||||
"summary": "auto"
|
||||
},
|
||||
"store": false,
|
||||
"stream": true,
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let usert_turn_3_after_resume = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
},
|
||||
summary_after_resume,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_COMPACT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "output_text",
|
||||
"text": "AFTER_COMPACT_REPLY"
|
||||
}
|
||||
]
|
||||
},
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_RESUME"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"effort": "medium",
|
||||
"summary": "auto"
|
||||
},
|
||||
"store": false,
|
||||
"stream": true,
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let user_turn_3_after_fork = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
},
|
||||
summary_after_fork,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_COMPACT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "output_text",
|
||||
"text": "AFTER_COMPACT_REPLY"
|
||||
}
|
||||
]
|
||||
},
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_context
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_FORK"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"effort": "medium",
|
||||
"summary": "auto"
|
||||
},
|
||||
"store": false,
|
||||
"stream": true,
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"prompt_cache_key": fork_prompt_cache_key
|
||||
});
|
||||
let mut expected = json!([
|
||||
user_turn_1,
|
||||
compact_1,
|
||||
user_turn_2_after_compact,
|
||||
usert_turn_3_after_resume,
|
||||
user_turn_3_after_fork
|
||||
]);
|
||||
normalize_line_endings(&mut expected);
|
||||
if let Some(arr) = expected.as_array_mut() {
|
||||
normalize_compact_prompts(arr);
|
||||
for summary in [
|
||||
&summary_after_compact,
|
||||
&summary_after_resume,
|
||||
&summary_after_fork,
|
||||
] {
|
||||
assert_eq!(summary["role"].as_str(), Some("user"));
|
||||
assert!(
|
||||
summary["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains(SUMMARY_TEXT),
|
||||
"summary message should include compacted summary text"
|
||||
);
|
||||
}
|
||||
assert_eq!(requests.len(), 5);
|
||||
assert_eq!(json!(requests), expected);
|
||||
let request_2_body = requests[2].to_string();
|
||||
assert!(
|
||||
request_2_body.contains("\"text\":\"AFTER_COMPACT\""),
|
||||
"post-compact request should include AFTER_COMPACT"
|
||||
);
|
||||
let request_3_body = requests[3].to_string();
|
||||
assert!(
|
||||
request_3_body.contains("\"text\":\"AFTER_RESUME\""),
|
||||
"post-resume request should include AFTER_RESUME"
|
||||
);
|
||||
let request_4_body = requests[4].to_string();
|
||||
assert!(
|
||||
request_4_body.contains("\"text\":\"AFTER_FORK\""),
|
||||
"post-fork request should include AFTER_FORK"
|
||||
);
|
||||
assert!(
|
||||
!request_4_body.contains("\"text\":\"AFTER_RESUME\""),
|
||||
"forked request should not include resumed-branch user input"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -725,118 +325,32 @@ async fn compact_resume_after_second_compaction_preserves_history() {
|
||||
compact_filtered.as_slice(),
|
||||
&resume_filtered[..compact_filtered.len()]
|
||||
);
|
||||
// hard coded test
|
||||
let prompt = requests[0]["instructions"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let permissions_message = requests[0]["input"][0].clone();
|
||||
let user_instructions = requests[0]["input"][1]["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let environment_instructions = requests[0]["input"][2]["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
|
||||
// Build expected final request input: initial context + forked user message +
|
||||
// compacted summary + post-compact user message + resumed user message.
|
||||
// Final resumed request should include the fork branch history, the second compaction
|
||||
// summary, and the resumed-again user message.
|
||||
let summary_after_second_compact =
|
||||
extract_summary_message(&requests[requests.len() - 3], SUMMARY_TEXT);
|
||||
|
||||
let mut expected = json!([
|
||||
{
|
||||
"instructions": prompt,
|
||||
"input": [
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_FORK"
|
||||
}
|
||||
]
|
||||
},
|
||||
summary_after_second_compact,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_COMPACT_2"
|
||||
}
|
||||
]
|
||||
},
|
||||
permissions_message,
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": user_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": environment_instructions
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "AFTER_SECOND_RESUME"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
]);
|
||||
normalize_line_endings(&mut expected);
|
||||
let mut last_request_after_2_compacts = json!([{
|
||||
"instructions": requests[requests.len() -1]["instructions"],
|
||||
"input": requests[requests.len() -1]["input"],
|
||||
}]);
|
||||
if let Some(arr) = expected.as_array_mut() {
|
||||
normalize_compact_prompts(arr);
|
||||
}
|
||||
if let Some(arr) = last_request_after_2_compacts.as_array_mut() {
|
||||
normalize_compact_prompts(arr);
|
||||
}
|
||||
assert_eq!(expected, last_request_after_2_compacts);
|
||||
assert_eq!(summary_after_second_compact["role"].as_str(), Some("user"));
|
||||
assert!(
|
||||
summary_after_second_compact["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains(SUMMARY_TEXT),
|
||||
"second compaction summary should include compacted summary text"
|
||||
);
|
||||
let last_request_after_two_compacts = &requests[requests.len() - 1];
|
||||
let last_request_body = last_request_after_two_compacts.to_string();
|
||||
assert!(
|
||||
last_request_body.contains("\"text\":\"AFTER_FORK\""),
|
||||
"last request should retain fork-branch user message"
|
||||
);
|
||||
assert!(
|
||||
last_request_body.contains("\"text\":\"AFTER_COMPACT_2\""),
|
||||
"last request should include post-second-compaction user message"
|
||||
);
|
||||
assert!(
|
||||
last_request_body.contains(&format!("\"text\":\"{AFTER_SECOND_RESUME}\"")),
|
||||
"last request should include resumed-again user message"
|
||||
);
|
||||
}
|
||||
|
||||
fn normalize_line_endings(value: &mut Value) {
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
---
|
||||
source: core/tests/suite/compact.rs
|
||||
expression: "format_labeled_requests_snapshot(\"Manual /compact with no prior user turn currently still issues a compaction request; follow-up turn carries canonical context and the new user message.\",\n&[(\"Local Compaction Request\", &requests[0]),\n(\"Local Post-Compaction History Layout\", &requests[1]),])"
|
||||
expression: "format_labeled_requests_snapshot(\"Manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.\",\n&[(\"Local Post-Compaction History Layout\", &requests[0]),])"
|
||||
---
|
||||
Scenario: Manual /compact with no prior user turn currently still issues a compaction request; follow-up turn carries canonical context and the new user message.
|
||||
|
||||
## Local Compaction Request
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:<AGENTS_MD>
|
||||
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
03:message/user:<SUMMARIZATION_PROMPT>
|
||||
Scenario: Manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.
|
||||
|
||||
## Local Post-Compaction History Layout
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:<AGENTS_MD>
|
||||
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
03:message/user:<COMPACTION_SUMMARY>\nMANUAL_EMPTY_SUMMARY
|
||||
04:message/user:AFTER_MANUAL_EMPTY_COMPACT
|
||||
03:message/user:AFTER_MANUAL_EMPTY_COMPACT
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
source: core/tests/suite/compact.rs
|
||||
expression: "format_labeled_requests_snapshot(\"Pre-turn auto-compaction with a context override emits the context diff in the compact request while the incoming user message is still excluded.\",\n&[(\"Local Compaction Request\", &requests[2]),\n(\"Local Post-Compaction History Layout\", &requests[3]),])"
|
||||
expression: "format_labeled_requests_snapshot(\"Pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.\",\n&[(\"Local Compaction Request\", &requests[2]),\n(\"Local Post-Compaction History Layout\", &requests[3]),])"
|
||||
---
|
||||
Scenario: Pre-turn auto-compaction with a context override emits the context diff in the compact request while the incoming user message is still excluded.
|
||||
Scenario: Pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.
|
||||
|
||||
## Local Compaction Request
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
---
|
||||
source: core/tests/suite/compact_remote.rs
|
||||
expression: "format_labeled_requests_snapshot(\"Remote manual /compact with no prior user turn still issues a compact request; follow-up turn carries canonical context and new user message.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &follow_up_request),])"
|
||||
expression: "format_labeled_requests_snapshot(\"Remote manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.\",\n&[(\"Remote Post-Compaction History Layout\", &follow_up_request),])"
|
||||
---
|
||||
Scenario: Remote manual /compact with no prior user turn still issues a compact request; follow-up turn carries canonical context and new user message.
|
||||
|
||||
## Remote Compaction Request
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:<AGENTS_MD>
|
||||
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
Scenario: Remote manual /compact with no prior user turn is a no-op; follow-up turn carries canonical context and the new user message.
|
||||
|
||||
## Remote Post-Compaction History Layout
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
source: core/tests/suite/compact_remote.rs
|
||||
expression: "format_labeled_requests_snapshot(\"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&first_compact_mock.single_request()),])"
|
||||
expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction parse failure: compaction request includes incoming user content and the turn stops.\",\n&[(\"Remote Compaction Request (Incoming User Included)\",\n&first_compact_mock.single_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 includes incoming user content and the turn stops.
|
||||
|
||||
## Remote Compaction Request (Incoming User Excluded)
|
||||
## Remote Compaction Request (Incoming User Included)
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:<AGENTS_MD>
|
||||
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
03:message/user:turn that exceeds token threshold
|
||||
04:message/assistant:initial turn complete
|
||||
05:message/user:turn that triggers auto compact
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
source: core/tests/suite/compact_remote.rs
|
||||
expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction with a context override emits the context diff in the compact request while excluding the incoming user message.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[2]),])"
|
||||
expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[2]),])"
|
||||
---
|
||||
Scenario: Remote pre-turn auto-compaction with a context override emits the context diff in the compact request while excluding the incoming user message.
|
||||
Scenario: Remote pre-turn auto-compaction with a context override includes incoming user content in the compact request and preserves it after compaction.
|
||||
|
||||
## Remote Compaction Request
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
@@ -17,8 +17,9 @@ Scenario: Remote pre-turn auto-compaction with a context override emits the cont
|
||||
|
||||
## Remote Post-Compaction History Layout
|
||||
00:message/user:USER_ONE
|
||||
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
02:message/user:<AGENTS_MD>
|
||||
03:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
|
||||
04:message/user:USER_TWO
|
||||
05:message/user:<COMPACTION_SUMMARY>\nREMOTE_PRE_TURN_SUMMARY
|
||||
01:message/user:USER_TWO
|
||||
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:<AGENTS_MD>
|
||||
04:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
|
||||
05:message/user:USER_THREE
|
||||
06:message/user:<COMPACTION_SUMMARY>\nREMOTE_PRE_TURN_SUMMARY
|
||||
|
||||
Reference in New Issue
Block a user