mirror of
https://github.com/openai/codex.git
synced 2026-04-29 00:55:38 +00:00
feat: remote compaction (#6795)
Co-authored-by: pakrym-oai <pakrym@openai.com>
This commit is contained in:
@@ -119,24 +119,9 @@ async fn summarize_context_three_requests_and_instructions() {
|
||||
// SSE 3: minimal completed; we only need to capture the request body.
|
||||
let sse3 = sse(vec![ev_completed("r3")]);
|
||||
|
||||
// Mount three expectations, one per request, matched by body content.
|
||||
let first_matcher = |req: &wiremock::Request| {
|
||||
let body = std::str::from_utf8(&req.body).unwrap_or("");
|
||||
body.contains("\"text\":\"hello world\"") && !body_contains_text(body, SUMMARIZATION_PROMPT)
|
||||
};
|
||||
let first_request_mock = mount_sse_once_match(&server, first_matcher, sse1).await;
|
||||
|
||||
let second_matcher = |req: &wiremock::Request| {
|
||||
let body = std::str::from_utf8(&req.body).unwrap_or("");
|
||||
body_contains_text(body, SUMMARIZATION_PROMPT)
|
||||
};
|
||||
let second_request_mock = mount_sse_once_match(&server, second_matcher, sse2).await;
|
||||
|
||||
let third_matcher = |req: &wiremock::Request| {
|
||||
let body = std::str::from_utf8(&req.body).unwrap_or("");
|
||||
body.contains(&format!("\"text\":\"{THIRD_USER_MSG}\""))
|
||||
};
|
||||
let third_request_mock = mount_sse_once_match(&server, third_matcher, sse3).await;
|
||||
// Mount the three expected requests in sequence so the assertions below can
|
||||
// inspect them without relying on specific prompt markers.
|
||||
let request_log = mount_sse_sequence(&server, vec![sse1, sse2, sse3]).await;
|
||||
|
||||
// Build config pointing to the mock server and spawn Codex.
|
||||
let model_provider = ModelProviderInfo {
|
||||
@@ -188,13 +173,11 @@ async fn summarize_context_three_requests_and_instructions() {
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
|
||||
|
||||
// Inspect the three captured requests.
|
||||
let req1 = first_request_mock.single_request();
|
||||
let req2 = second_request_mock.single_request();
|
||||
let req3 = third_request_mock.single_request();
|
||||
|
||||
let body1 = req1.body_json();
|
||||
let body2 = req2.body_json();
|
||||
let body3 = req3.body_json();
|
||||
let requests = request_log.requests();
|
||||
assert_eq!(requests.len(), 3, "expected exactly three requests");
|
||||
let body1 = requests[0].body_json();
|
||||
let body2 = requests[1].body_json();
|
||||
let body3 = requests[2].body_json();
|
||||
|
||||
// Manual compact should keep the baseline developer instructions.
|
||||
let instr1 = body1.get("instructions").and_then(|v| v.as_str()).unwrap();
|
||||
@@ -205,16 +188,25 @@ async fn summarize_context_three_requests_and_instructions() {
|
||||
);
|
||||
|
||||
// The summarization request should include the injected user input marker.
|
||||
let body2_str = body2.to_string();
|
||||
let input2 = body2.get("input").and_then(|v| v.as_array()).unwrap();
|
||||
// The last item is the user message created from the injected input.
|
||||
let last2 = input2.last().unwrap();
|
||||
assert_eq!(last2.get("type").unwrap().as_str().unwrap(), "message");
|
||||
assert_eq!(last2.get("role").unwrap().as_str().unwrap(), "user");
|
||||
let text2 = last2["content"][0]["text"].as_str().unwrap();
|
||||
assert_eq!(
|
||||
text2, SUMMARIZATION_PROMPT,
|
||||
"expected summarize trigger, got `{text2}`"
|
||||
);
|
||||
let has_compact_prompt = body_contains_text(&body2_str, SUMMARIZATION_PROMPT);
|
||||
if has_compact_prompt {
|
||||
// The last item is the user message created from the injected input.
|
||||
let last2 = input2.last().unwrap();
|
||||
assert_eq!(last2.get("type").unwrap().as_str().unwrap(), "message");
|
||||
assert_eq!(last2.get("role").unwrap().as_str().unwrap(), "user");
|
||||
let text2 = last2["content"][0]["text"].as_str().unwrap();
|
||||
assert_eq!(
|
||||
text2, SUMMARIZATION_PROMPT,
|
||||
"expected summarize trigger, got `{text2}`"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!has_compact_prompt,
|
||||
"compaction request should not unexpectedly include the summarize trigger"
|
||||
);
|
||||
}
|
||||
|
||||
// Third request must contain the refreshed instructions, compacted user history, and new user message.
|
||||
let input3 = body3.get("input").and_then(|v| v.as_array()).unwrap();
|
||||
@@ -379,8 +371,19 @@ async fn manual_compact_uses_custom_prompt() {
|
||||
}
|
||||
}
|
||||
|
||||
assert!(found_custom_prompt, "custom prompt should be injected");
|
||||
assert!(!found_default_prompt, "default prompt should be replaced");
|
||||
let used_prompt = found_custom_prompt || found_default_prompt;
|
||||
if used_prompt {
|
||||
assert!(found_custom_prompt, "custom prompt should be injected");
|
||||
assert!(
|
||||
!found_default_prompt,
|
||||
"default prompt should be replaced when a compact prompt is used"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!found_default_prompt,
|
||||
"summarization prompt should not appear if compaction omits a prompt"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -1430,27 +1433,13 @@ async fn manual_compact_retries_after_context_window_error() {
|
||||
let retry_input = retry_attempt["input"]
|
||||
.as_array()
|
||||
.unwrap_or_else(|| panic!("retry attempt missing input array: {retry_attempt}"));
|
||||
let compact_contains_prompt =
|
||||
body_contains_text(&compact_attempt.to_string(), SUMMARIZATION_PROMPT);
|
||||
let retry_contains_prompt =
|
||||
body_contains_text(&retry_attempt.to_string(), SUMMARIZATION_PROMPT);
|
||||
assert_eq!(
|
||||
compact_input
|
||||
.last()
|
||||
.and_then(|item| item.get("content"))
|
||||
.and_then(|v| v.as_array())
|
||||
.and_then(|items| items.first())
|
||||
.and_then(|entry| entry.get("text"))
|
||||
.and_then(|text| text.as_str()),
|
||||
Some(SUMMARIZATION_PROMPT),
|
||||
"compact attempt should include summarization prompt"
|
||||
);
|
||||
assert_eq!(
|
||||
retry_input
|
||||
.last()
|
||||
.and_then(|item| item.get("content"))
|
||||
.and_then(|v| v.as_array())
|
||||
.and_then(|items| items.first())
|
||||
.and_then(|entry| entry.get("text"))
|
||||
.and_then(|text| text.as_str()),
|
||||
Some(SUMMARIZATION_PROMPT),
|
||||
"retry attempt should include summarization prompt"
|
||||
compact_contains_prompt, retry_contains_prompt,
|
||||
"compact attempts should consistently include or omit the summarization prompt"
|
||||
);
|
||||
assert_eq!(
|
||||
retry_input.len(),
|
||||
@@ -1601,10 +1590,6 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
);
|
||||
|
||||
let first_compact_input = requests[1].input();
|
||||
assert!(
|
||||
contains_user_text(&first_compact_input, SUMMARIZATION_PROMPT),
|
||||
"first compact request should include summarization prompt"
|
||||
);
|
||||
assert!(
|
||||
contains_user_text(&first_compact_input, first_user_message),
|
||||
"first compact request should include history before compaction"
|
||||
@@ -1621,15 +1606,18 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
);
|
||||
|
||||
let second_compact_input = requests[3].input();
|
||||
assert!(
|
||||
contains_user_text(&second_compact_input, SUMMARIZATION_PROMPT),
|
||||
"second compact request should include summarization prompt"
|
||||
);
|
||||
assert!(
|
||||
contains_user_text(&second_compact_input, second_user_message),
|
||||
"second compact request should include latest history"
|
||||
);
|
||||
|
||||
let first_compact_has_prompt = contains_user_text(&first_compact_input, SUMMARIZATION_PROMPT);
|
||||
let second_compact_has_prompt = contains_user_text(&second_compact_input, SUMMARIZATION_PROMPT);
|
||||
assert_eq!(
|
||||
first_compact_has_prompt, second_compact_has_prompt,
|
||||
"compact requests should consistently include or omit the summarization prompt"
|
||||
);
|
||||
|
||||
let mut final_output = requests
|
||||
.last()
|
||||
.unwrap_or_else(|| panic!("final turn request missing for {final_user_message}"))
|
||||
|
||||
Reference in New Issue
Block a user