mirror of
https://github.com/openai/codex.git
synced 2026-10-20 08:19:50 +00:00
Always use streamed remote compaction for supported providers (#44255)
## What changed - Route manual and automatic remote compaction through the v2 implementation. - Retire the `remote_compaction_v2` feature toggle while continuing to advertise it in the model client beta features header. - Migrate compaction tests from `/responses/compact` to streamed `/responses` requests with `compaction_trigger` items and remove legacy parity coverage. ## Testing Add streamed compaction coverage for tool search output trimming and session base instruction token estimates. Update app-server tests to verify started/completed notifications for both local and streamed remote compaction. GitOrigin-RevId: cea392a6166a9b116ff83708fd9126d5546476e4
This commit is contained in:
@@ -1497,7 +1497,7 @@ fn compaction_event_serializes_expected_shape() {
|
||||
turn_id: "turn-1".to_string(),
|
||||
trigger: CompactionTrigger::Auto,
|
||||
reason: CompactionReason::ContextLimit,
|
||||
implementation: CompactionImplementation::ResponsesCompact,
|
||||
implementation: CompactionImplementation::ResponsesCompactionV2,
|
||||
phase: CompactionPhase::MidTurn,
|
||||
strategy: CompactionStrategy::Memento,
|
||||
status: CompactionStatus::Completed,
|
||||
@@ -1550,7 +1550,7 @@ fn compaction_event_serializes_expected_shape() {
|
||||
"parent_thread_id": null,
|
||||
"trigger": "auto",
|
||||
"reason": "context_limit",
|
||||
"implementation": "responses_compact",
|
||||
"implementation": "responses_compaction_v2",
|
||||
"phase": "mid_turn",
|
||||
"strategy": "memento",
|
||||
"status": "completed",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! End-to-end compaction flow tests.
|
||||
//!
|
||||
//! Phases:
|
||||
//! 1) Arrange: mock responses/compact endpoints + config.
|
||||
//! 1) Arrange: mock Responses endpoints + config.
|
||||
//! 2) Act: start a thread and submit multiple turns to trigger auto-compaction.
|
||||
//! 3) Assert: verify item/started + item/completed notifications for context compaction.
|
||||
|
||||
@@ -30,14 +30,12 @@ use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use test_case::test_case;
|
||||
use tokio::time::timeout;
|
||||
|
||||
// macOS and Windows Bazel CI can spend tens of seconds starting app-server
|
||||
@@ -50,8 +48,16 @@ const AUTO_COMPACT_LIMIT: i64 = 1_000;
|
||||
const COMPACT_PROMPT: &str = "Summarize the conversation.";
|
||||
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum CompactionRoute {
|
||||
Local,
|
||||
Remote,
|
||||
}
|
||||
|
||||
#[test_case(CompactionRoute::Local; "local")]
|
||||
#[test_case(CompactionRoute::Remote; "streamed remote")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()> {
|
||||
async fn auto_compaction_emits_started_and_completed_items(route: CompactionRoute) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
@@ -63,99 +69,39 @@ async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()>
|
||||
responses::ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
let summary = match route {
|
||||
CompactionRoute::Local => responses::ev_assistant_message("m3", "LOCAL_SUMMARY"),
|
||||
CompactionRoute::Remote => serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "compaction",
|
||||
"encrypted_content": "ENCRYPTED_COMPACTION_SUMMARY",
|
||||
},
|
||||
}),
|
||||
};
|
||||
let sse3 = responses::sse(vec![
|
||||
responses::ev_assistant_message("m3", "LOCAL_SUMMARY"),
|
||||
summary,
|
||||
responses::ev_completed_with_tokens("r3", /*total_tokens*/ 200),
|
||||
]);
|
||||
let sse4 = responses::sse(vec![
|
||||
responses::ev_assistant_message("m4", "FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r4", /*total_tokens*/ 120),
|
||||
]);
|
||||
responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
let requests = responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
compaction_config(&server.uri(), AUTO_COMPACT_LIMIT).write(codex_home.path())?;
|
||||
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build_initialized_with_timeout(DEFAULT_READ_TIMEOUT)
|
||||
.await?;
|
||||
|
||||
let thread_id = start_thread(&mut mcp).await?;
|
||||
for message in ["first", "second", "third"] {
|
||||
send_turn_and_wait(&mut mcp, &thread_id, message).await?;
|
||||
let mut config = compaction_config(&server.uri(), /*auto_compact_limit*/ 200_000);
|
||||
if let CompactionRoute::Remote = route {
|
||||
config = config
|
||||
.with_provider_name("OpenAI")
|
||||
.with_provider_config("requires_openai_auth = true");
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("access-chatgpt").plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
}
|
||||
|
||||
let started = wait_for_context_compaction_started(&mut mcp).await?;
|
||||
let completed = wait_for_context_compaction_completed(&mut mcp).await?;
|
||||
|
||||
let ThreadItem::ContextCompaction { id: started_id } = started.item else {
|
||||
unreachable!("started item should be context compaction");
|
||||
};
|
||||
let ThreadItem::ContextCompaction { id: completed_id } = completed.item else {
|
||||
unreachable!("completed item should be context compaction");
|
||||
};
|
||||
|
||||
assert_eq!(started.thread_id, thread_id);
|
||||
assert_eq!(completed.thread_id, thread_id);
|
||||
assert_eq!(started_id, completed_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
const REMOTE_AUTO_COMPACT_LIMIT: i64 = 200_000;
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let sse1 = responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
|
||||
]);
|
||||
let sse2 = responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
let sse3 = responses::sse(vec![
|
||||
responses::ev_assistant_message("m3", "FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r3", /*total_tokens*/ 120),
|
||||
]);
|
||||
let responses_log = responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3]).await;
|
||||
|
||||
let compacted_history = vec![
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "REMOTE_COMPACT_SUMMARY".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
];
|
||||
let compact_mock = responses::mount_compact_json_once(
|
||||
&server,
|
||||
serde_json::json!({ "output": compacted_history }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
compaction_config(&server.uri(), REMOTE_AUTO_COMPACT_LIMIT)
|
||||
.disable_feature(Feature::RemoteCompactionV2)
|
||||
.with_provider_name("OpenAI")
|
||||
.with_provider_config("requires_openai_auth = true")
|
||||
.write(codex_home.path())?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("access-chatgpt").plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
config.write(codex_home.path())?;
|
||||
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
@@ -182,65 +128,31 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<()
|
||||
assert_eq!(completed.thread_id, thread_id);
|
||||
assert_eq!(started_id, completed_id);
|
||||
|
||||
let compact_requests = compact_mock.requests();
|
||||
assert_eq!(compact_requests.len(), 1);
|
||||
assert_eq!(compact_requests[0].path(), "/v1/responses/compact");
|
||||
|
||||
let response_requests = responses_log.requests();
|
||||
assert_eq!(response_requests.len(), 3);
|
||||
let turn_metadata = response_requests
|
||||
.iter()
|
||||
.map(|request| {
|
||||
request
|
||||
.header("x-codex-turn-metadata")
|
||||
.as_deref()
|
||||
.map(parse_json_header)
|
||||
.expect("turn request should include turn metadata")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for (request, metadata) in response_requests.iter().zip(&turn_metadata) {
|
||||
assert_eq!(metadata["request_kind"].as_str(), Some("turn"));
|
||||
assert!(
|
||||
metadata["turn_id"]
|
||||
.as_str()
|
||||
.is_some_and(|turn_id| !turn_id.is_empty()),
|
||||
"turn request should carry a non-empty turn id"
|
||||
);
|
||||
assert_eq!(
|
||||
metadata["window_id"].as_str(),
|
||||
request.header("x-codex-window-id").as_deref()
|
||||
);
|
||||
assert!(metadata.get("compaction").is_none());
|
||||
let requests = requests.requests();
|
||||
assert_eq!(requests.len(), 4);
|
||||
let compact_request = &requests[2];
|
||||
assert_eq!(compact_request.path(), "/v1/responses");
|
||||
match route {
|
||||
CompactionRoute::Local => {
|
||||
assert!(
|
||||
compact_request
|
||||
.inputs_of_type("compaction_trigger")
|
||||
.is_empty()
|
||||
);
|
||||
assert!(compact_request.body_contains_text(COMPACT_PROMPT));
|
||||
}
|
||||
CompactionRoute::Remote => {
|
||||
assert_eq!(
|
||||
compact_request.inputs_of_type("compaction_trigger").len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
requests[3].inputs_of_type("compaction")[0]["encrypted_content"],
|
||||
"ENCRYPTED_COMPACTION_SUMMARY"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let compact_metadata = compact_requests[0]
|
||||
.header("x-codex-turn-metadata")
|
||||
.as_deref()
|
||||
.map(parse_json_header)
|
||||
.expect("compact request should include turn metadata");
|
||||
assert_eq!(
|
||||
compact_metadata["request_kind"].as_str(),
|
||||
Some("compaction")
|
||||
);
|
||||
assert_eq!(
|
||||
compact_metadata["compaction"],
|
||||
serde_json::json!({
|
||||
"trigger": "auto",
|
||||
"reason": "context_limit",
|
||||
"implementation": "responses_compact",
|
||||
"phase": "pre_turn",
|
||||
"strategy": "memento",
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
compact_metadata["turn_id"], turn_metadata[2]["turn_id"],
|
||||
"pre-turn compaction should carry the current turn id"
|
||||
);
|
||||
assert_eq!(
|
||||
compact_metadata["window_id"].as_str(),
|
||||
compact_requests[0].header("x-codex-window-id").as_deref()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -522,10 +434,6 @@ async fn wait_for_context_compaction_completed(
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_json_header(value: &str) -> serde_json::Value {
|
||||
serde_json::from_str(value).expect("turn metadata should be JSON")
|
||||
}
|
||||
|
||||
fn compaction_config(server_uri: &str, auto_compact_limit: i64) -> MockResponsesConfig {
|
||||
MockResponsesConfig::new(server_uri)
|
||||
.with_root_config(&format!(
|
||||
|
||||
@@ -385,7 +385,26 @@ async fn parent_response(
|
||||
State(state): State<Arc<MockResponsesState>>,
|
||||
Json(request): Json<Value>,
|
||||
) -> impl IntoResponse {
|
||||
let events = if request["model"] == "gpt-5.6-luna" {
|
||||
let events = if request["input"].as_array().is_some_and(|input| {
|
||||
input
|
||||
.last()
|
||||
.is_some_and(|item| item["type"] == "compaction_trigger")
|
||||
}) {
|
||||
assert!(state.compact_root_after_answer);
|
||||
assert!(request.to_string().contains("guardian-user-input"));
|
||||
vec![
|
||||
responses::ev_response_created("root-compaction"),
|
||||
json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "compaction",
|
||||
"id": "cmp_root",
|
||||
"encrypted_content": "opaque root summary",
|
||||
},
|
||||
}),
|
||||
responses::ev_completed("root-compaction"),
|
||||
]
|
||||
} else if request["model"] == "gpt-5.6-luna" {
|
||||
luna_response(&state, request).await
|
||||
} else if request
|
||||
.pointer("/client_metadata/x-openai-subagent")
|
||||
@@ -712,30 +731,53 @@ async fn guardian_v2_routes_scoped_tool_approvals(
|
||||
let responses_url = format!("http://{}", listener.local_addr()?);
|
||||
let router = Router::new()
|
||||
.route("/v1/responses", get(luna_websocket).post(parent_response))
|
||||
.route("/v1/responses/compact", post(|Json(request): Json<Value>| async move {
|
||||
assert!(request.to_string().contains("guardian-user-input"));
|
||||
Json(json!({"output": [{"type": "compaction", "id": "cmp_root", "encrypted_content": "opaque root summary"}]}))
|
||||
}))
|
||||
.route(
|
||||
"/metrics",
|
||||
post(
|
||||
|State(state): State<Arc<MockResponsesState>>, body: String| async move {
|
||||
if matches!(state.transcript_content, TranscriptContent::MixedEvidence) {
|
||||
let payload: Value = serde_json::from_str(&body).expect("OTLP JSON");
|
||||
for metric in payload["resourceMetrics"].as_array().into_iter().flatten()
|
||||
.flat_map(|resource| resource["scopeMetrics"].as_array().into_iter().flatten())
|
||||
for metric in payload["resourceMetrics"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(|resource| {
|
||||
resource["scopeMetrics"].as_array().into_iter().flatten()
|
||||
})
|
||||
.flat_map(|scope| scope["metrics"].as_array().into_iter().flatten())
|
||||
.filter(|metric| matches!(metric["name"].as_str(), Some("codex.guardian.context.request_tokens" | "codex.guardian.context.section_cost")))
|
||||
.filter(|metric| {
|
||||
matches!(
|
||||
metric["name"].as_str(),
|
||||
Some(
|
||||
"codex.guardian.context.request_tokens"
|
||||
| "codex.guardian.context.section_cost"
|
||||
)
|
||||
)
|
||||
})
|
||||
{
|
||||
let name = metric["name"].as_str().expect("context metric name");
|
||||
for point in metric["histogram"]["dataPoints"].as_array().into_iter().flatten() {
|
||||
let attributes = point["attributes"].as_array().expect("metric attributes");
|
||||
for point in metric["histogram"]["dataPoints"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
let attributes =
|
||||
point["attributes"].as_array().expect("metric attributes");
|
||||
for target in ["sync", "async"] {
|
||||
if attributes.iter().any(|attr| attr["key"] == "target" && attr["value"]["stringValue"] == target) {
|
||||
let mut bounds = state.context_metric_bounds.lock().expect("context metric bounds");
|
||||
if attributes.iter().any(|attr| {
|
||||
attr["key"] == "target"
|
||||
&& attr["value"]["stringValue"] == target
|
||||
}) {
|
||||
let mut bounds = state
|
||||
.context_metric_bounds
|
||||
.lock()
|
||||
.expect("context metric bounds");
|
||||
bounds.insert(
|
||||
(name.to_owned(), target.to_owned()),
|
||||
point["explicitBounds"].as_array().and_then(|bounds| bounds.last()).and_then(Value::as_f64),
|
||||
point["explicitBounds"]
|
||||
.as_array()
|
||||
.and_then(|bounds| bounds.last())
|
||||
.and_then(Value::as_f64),
|
||||
);
|
||||
if bounds.len() == 4 {
|
||||
state.context_metrics_recorded.notify_one();
|
||||
@@ -852,7 +894,6 @@ async fn guardian_v2_routes_scoped_tool_approvals(
|
||||
if matches!(lifecycle, ThreadLifecycle::RootUserInputCompaction) {
|
||||
mock_config = mock_config
|
||||
.with_provider_name("OpenAI")
|
||||
.disable_feature(Feature::RemoteCompactionV2)
|
||||
.disable_feature(Feature::TokenBudget)
|
||||
.disable_feature(Feature::EnableRequestCompression);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use axum::Router;
|
||||
use axum::extract::State;
|
||||
use axum::http::header;
|
||||
use axum::routing::get;
|
||||
use axum::routing::post;
|
||||
use codex_app_server_protocol::ApprovalsReviewer;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::GuardianApprovalReview;
|
||||
@@ -184,12 +183,33 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
get(luna_websocket).post({
|
||||
let parent_requests = Arc::clone(&parent_requests);
|
||||
let review_requests = Arc::clone(&review_requests);
|
||||
let compact_requests = Arc::clone(&compact_requests);
|
||||
let checkpoint = checkpoint.clone();
|
||||
move |State(classifier): State<Arc<MockResponsesState>>,
|
||||
Json(request): Json<Value>| {
|
||||
let parent_requests = Arc::clone(&parent_requests);
|
||||
let review_requests = Arc::clone(&review_requests);
|
||||
let compact_requests = Arc::clone(&compact_requests);
|
||||
let checkpoint = checkpoint.clone();
|
||||
async move {
|
||||
let events = if request["model"] == "gpt-5.6-luna" {
|
||||
let events = if request["input"].as_array().is_some_and(|input| {
|
||||
input
|
||||
.iter()
|
||||
.any(|item| item["type"] == "compaction_trigger")
|
||||
}) {
|
||||
compact_requests
|
||||
.lock()
|
||||
.expect("request log lock")
|
||||
.push(request);
|
||||
vec![
|
||||
responses::ev_assistant_message("summary", SUMMARY),
|
||||
json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": checkpoint,
|
||||
}),
|
||||
responses::ev_completed("compact"),
|
||||
]
|
||||
} else if request["model"] == "gpt-5.6-luna" {
|
||||
luna_response(&classifier, request).await
|
||||
} else if request["client_metadata"]["x-openai-subagent"] == "guardian" {
|
||||
review_requests
|
||||
@@ -243,29 +263,6 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/v1/responses/compact",
|
||||
post({
|
||||
let compact_requests = Arc::clone(&compact_requests);
|
||||
let checkpoint = checkpoint.clone();
|
||||
move |Json(request): Json<Value>| {
|
||||
let compact_requests = Arc::clone(&compact_requests);
|
||||
let checkpoint = checkpoint.clone();
|
||||
async move {
|
||||
compact_requests
|
||||
.lock()
|
||||
.expect("request log lock")
|
||||
.push(request);
|
||||
Json(json!({"output": [
|
||||
{"type": "message", "role": "assistant", "content": [
|
||||
{"type": "output_text", "text": SUMMARY}
|
||||
]},
|
||||
checkpoint
|
||||
]}))
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.with_state(Arc::clone(&classifier));
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let responses_url = format!("http://{}", listener.local_addr()?);
|
||||
@@ -288,7 +285,7 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
.enable_feature(Feature::GuardianApproval)
|
||||
.enable_feature(Feature::GuardianReuseParentCompaction)
|
||||
.disable_feature(Feature::EnableRequestCompression)
|
||||
.disable_feature(Feature::RemoteCompactionV2)
|
||||
.enable_feature(Feature::RemoteCompactionV2)
|
||||
.disable_feature(Feature::TokenBudget)
|
||||
.with_extra_config(&format!(
|
||||
"[mcp_servers.{TEST_SERVER_NAME}]\nurl = \"{mcp_url}/mcp\"\ndefault_tools_approval_mode = \"prompt\"\n\n[features.guardianv2]\nenabled = true\nthread_context = {thread_context_enabled}\npersist_scores = true\nreuse_parent_compaction = {reuse_parent_compaction}\n\n[features.guardianv2.review_scope]\ncomputer_use_only = false"
|
||||
@@ -616,9 +613,16 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
let output = output["output"].as_str().expect("tool output text");
|
||||
assert!(output.contains(&expected_output), "{output}");
|
||||
} else {
|
||||
let parent_input = serde_json::to_string(&parent[index * 2 + 1]["input"])?;
|
||||
assert!(!parent_input.contains(RESTRICTION));
|
||||
let parent_items = parent[index * 2 + 1]["input"]
|
||||
.as_array()
|
||||
.expect("request input array");
|
||||
let parent_input = serde_json::to_string(parent_items)?;
|
||||
// V2 keeps bounded user history while replacing old tool output with a checkpoint.
|
||||
assert_eq!(parent_input.contains(RESTRICTION), index <= 3);
|
||||
assert!(!parent_input.contains(EVIDENCE));
|
||||
assert!(!parent_items.iter().any(|item| {
|
||||
item["type"] == "function_call_output" && item["call_id"] == "inspect-1"
|
||||
}));
|
||||
if index <= 3 {
|
||||
assert!(sync_text.contains(RESTRICTION));
|
||||
if matches!(context_path, ContextPath::ThreadOwned) {
|
||||
@@ -652,7 +656,7 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
compact_output.contains(&expected_output),
|
||||
"{compact_output}"
|
||||
);
|
||||
assert!(parent_input.contains(SUMMARY));
|
||||
assert!(parent_items.contains(&checkpoint));
|
||||
if matches!(context_path, ContextPath::ThreadOwned) {
|
||||
assert!(
|
||||
content.contains(RESTRICTION),
|
||||
@@ -662,7 +666,8 @@ async fn guardians_retain_evidence_after_compaction_and_discard_it_after_rollbac
|
||||
!transcript.contains(&expected_output),
|
||||
"raw tool result must not survive the parent checkpoint"
|
||||
);
|
||||
assert!(transcript.contains(SUMMARY));
|
||||
assert!(transcript.contains(RESTRICTION));
|
||||
assert!(!transcript.contains(SUMMARY));
|
||||
} else {
|
||||
assert!(
|
||||
transcript.contains(RESTRICTION),
|
||||
|
||||
@@ -1278,6 +1278,7 @@ impl Drop for ModelClientSession {
|
||||
}
|
||||
|
||||
impl ModelClientSession {
|
||||
#[expect(dead_code, reason = "the v1-only accessor is removed in the follow-up")]
|
||||
pub(crate) fn turn_state(&self) -> Arc<OnceLock<String>> {
|
||||
Arc::clone(&self.turn_state)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ pub use responses_metadata::CodexResponsesMetadata;
|
||||
pub use turn_metadata::detached_memory_responses_metadata;
|
||||
mod codex_thread;
|
||||
mod compact_model_fallback;
|
||||
#[expect(
|
||||
dead_code,
|
||||
reason = "the unrouted v1 runner is removed in the follow-up"
|
||||
)]
|
||||
mod compact_remote;
|
||||
mod compact_remote_history;
|
||||
mod compact_remote_v2;
|
||||
|
||||
@@ -1088,28 +1088,20 @@ impl Session {
|
||||
///
|
||||
/// `ModelClient` is session-scoped and intentionally does not depend on the full `Config`, so
|
||||
/// we precompute the comma-separated list of enabled experimental feature keys at session
|
||||
/// creation time and thread it into the client.
|
||||
/// creation time and thread it into the client. Remote compaction stays advertised unconditionally.
|
||||
fn build_model_client_beta_features_header(config: &Config) -> Option<String> {
|
||||
let beta_features_header = FEATURES
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
let advertise_in_model_client_header =
|
||||
spec.stage.experimental_menu_description().is_some()
|
||||
|| spec.id == Feature::RemoteCompactionV2;
|
||||
if advertise_in_model_client_header && config.features.enabled(spec.id) {
|
||||
Some(spec.key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
if beta_features_header.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(beta_features_header)
|
||||
}
|
||||
Some(
|
||||
FEATURES
|
||||
.iter()
|
||||
.filter(|spec| {
|
||||
spec.id == Feature::RemoteCompactionV2
|
||||
|| (spec.stage.experimental_menu_description().is_some()
|
||||
&& config.features.enabled(spec.id))
|
||||
})
|
||||
.map(|spec| spec.key)
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
)
|
||||
}
|
||||
|
||||
async fn start_managed_network_proxy(
|
||||
|
||||
@@ -11314,7 +11314,7 @@ async fn make_remote_compaction_session(
|
||||
#[test_case(FirstAttempt::Succeeds; "primary succeeds")]
|
||||
#[test_case(FirstAttempt::Retries; "fallback executes")]
|
||||
#[tokio::test]
|
||||
async fn legacy_compaction_retains_only_the_selected_step(first_attempt: FirstAttempt) {
|
||||
async fn remote_compaction_v2_retains_only_the_selected_step(first_attempt: FirstAttempt) {
|
||||
let server = responses::start_mock_server().await;
|
||||
let (session, turn, events) = make_remote_compaction_session(&server.uri()).await;
|
||||
session
|
||||
@@ -11360,9 +11360,15 @@ async fn legacy_compaction_retains_only_the_selected_step(first_attempt: FirstAt
|
||||
Some(Arc::as_ptr(&primary)),
|
||||
);
|
||||
|
||||
let success = ResponseTemplate::new(/*status*/ 200).set_body_json(json!({
|
||||
"output": [{ "type": "compaction", "encrypted_content": "summary" }]
|
||||
}));
|
||||
let success = ResponseTemplate::new(/*status*/ 200)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
.set_body_string(responses::sse(vec![
|
||||
json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": { "type": "compaction", "encrypted_content": "summary" },
|
||||
}),
|
||||
responses::ev_completed("compact-response"),
|
||||
]));
|
||||
let replies = match first_attempt {
|
||||
FirstAttempt::Succeeds => vec![success],
|
||||
FirstAttempt::Retries => vec![
|
||||
@@ -11371,12 +11377,13 @@ async fn legacy_compaction_retains_only_the_selected_step(first_attempt: FirstAt
|
||||
success,
|
||||
],
|
||||
};
|
||||
let requests = responses::mount_compact_response_sequence(&server, replies).await;
|
||||
crate::compact_remote::run_inline_remote_auto_compact_task(
|
||||
let requests = responses::mount_response_sequence(&server, replies).await;
|
||||
let mut client_session = session.services.model_client.new_session();
|
||||
crate::compact_remote_v2::run_inline_remote_auto_compact_task(
|
||||
Arc::clone(&session),
|
||||
Arc::clone(&primary),
|
||||
Some(Arc::clone(&fallback)),
|
||||
Arc::new(OnceLock::new()),
|
||||
&mut client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::ModelDownshift,
|
||||
CompactionPhase::PreTurn,
|
||||
@@ -11405,6 +11412,12 @@ async fn legacy_compaction_retains_only_the_selected_step(first_attempt: FirstAt
|
||||
.collect::<Vec<_>>(),
|
||||
models,
|
||||
);
|
||||
assert!(
|
||||
requests
|
||||
.requests()
|
||||
.iter()
|
||||
.all(|request| request.inputs_of_type("compaction_trigger").len() == 1),
|
||||
);
|
||||
session.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
||||
recv_terminal_event(&events, TerminalEventKind::TurnAborted).await;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,9 @@ use codex_network_proxy::NetworkProtocol;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ContentItemKind;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
@@ -1053,7 +1051,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn process_compacted_history_preserves_separate_guardian_developer_message() {
|
||||
async fn compaction_initial_context_preserves_separate_guardian_developer_message() {
|
||||
let (session, mut turn_context) = make_session_and_context().await;
|
||||
update_turn_settings_for_test(&mut turn_context, |settings| {
|
||||
update_selected_settings_for_test(settings, |selected| {
|
||||
@@ -1084,40 +1082,20 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
step_context,
|
||||
};
|
||||
|
||||
let (refreshed, _) = crate::compact_remote::process_compacted_history(
|
||||
&session,
|
||||
vec![
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "developer".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "stale developer message".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "summary".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
],
|
||||
&initial_context_injection,
|
||||
)
|
||||
.await;
|
||||
let (refreshed, _) =
|
||||
crate::compact::build_compaction_initial_context(&session, &initial_context_injection)
|
||||
.await;
|
||||
|
||||
let developer_messages = refreshed
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
.filter_map(|envelope| match &envelope.item {
|
||||
ResponseItem::Message { role, content, .. } if role == "developer" => {
|
||||
crate::content_items_to_text(content).map(|text| {
|
||||
(
|
||||
text,
|
||||
item.executed_tool_call_metadata()
|
||||
envelope
|
||||
.item
|
||||
.executed_tool_call_metadata()
|
||||
.and_then(|metadata| metadata.content_item_kinds.clone()),
|
||||
)
|
||||
})
|
||||
@@ -1126,11 +1104,6 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(
|
||||
!developer_messages
|
||||
.iter()
|
||||
.any(|(message, _)| message.contains("stale developer message"))
|
||||
);
|
||||
assert!(
|
||||
!developer_messages
|
||||
.iter()
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::client_common::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::compact::run_inline_auto_compact_task;
|
||||
use crate::compact_remote::run_inline_remote_auto_compact_task;
|
||||
use crate::compact_remote_v2::run_inline_remote_auto_compact_task as run_inline_remote_auto_compact_task_v2;
|
||||
use crate::connectors;
|
||||
use crate::context::ContextualUserFragment;
|
||||
@@ -1315,12 +1314,7 @@ async fn run_auto_compact(
|
||||
}
|
||||
|
||||
match turn_context.provider.capabilities().remote_compaction {
|
||||
RemoteCompactionSupport::V2
|
||||
if turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::RemoteCompactionV2) =>
|
||||
{
|
||||
RemoteCompactionSupport::V2 => {
|
||||
emit_compact_metric(
|
||||
&sess.services.session_telemetry,
|
||||
"remote_v2",
|
||||
@@ -1337,23 +1331,6 @@ async fn run_auto_compact(
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
RemoteCompactionSupport::V2 => {
|
||||
emit_compact_metric(
|
||||
&sess.services.session_telemetry,
|
||||
"remote",
|
||||
/*manual*/ false,
|
||||
);
|
||||
run_inline_remote_auto_compact_task(
|
||||
Arc::clone(sess),
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session.turn_state(),
|
||||
initial_context_injection,
|
||||
reason,
|
||||
phase,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
RemoteCompactionSupport::Unsupported => {
|
||||
emit_compact_metric(
|
||||
&sess.services.session_telemetry,
|
||||
|
||||
@@ -39,9 +39,7 @@ impl SessionTask for CompactTask {
|
||||
}
|
||||
|
||||
let result = match ctx.provider.capabilities().remote_compaction {
|
||||
RemoteCompactionSupport::V2
|
||||
if ctx.config.features.enabled(Feature::RemoteCompactionV2) =>
|
||||
{
|
||||
RemoteCompactionSupport::V2 => {
|
||||
emit_compact_metric(
|
||||
&session.services.session_telemetry,
|
||||
"remote_v2",
|
||||
@@ -49,14 +47,6 @@ impl SessionTask for CompactTask {
|
||||
);
|
||||
crate::compact_remote_v2::run_remote_compact_task(session.clone(), ctx).await
|
||||
}
|
||||
RemoteCompactionSupport::V2 => {
|
||||
emit_compact_metric(
|
||||
&session.services.session_telemetry,
|
||||
"remote",
|
||||
/*manual*/ true,
|
||||
);
|
||||
crate::compact_remote::run_remote_compact_task(session.clone(), ctx).await
|
||||
}
|
||||
RemoteCompactionSupport::Unsupported => {
|
||||
emit_compact_metric(
|
||||
&session.services.session_telemetry,
|
||||
|
||||
@@ -1242,9 +1242,6 @@ await new Promise(() => {});
|
||||
.disable(Feature::ExecutedToolCallMetadata)
|
||||
.expect("tool call metadata should be disabled");
|
||||
}
|
||||
if oversized {
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1280,14 +1277,34 @@ await new Promise(() => {});
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
|
||||
if oversized {
|
||||
responses::mount_compact_user_history_with_summary_once(&server, "compacted history")
|
||||
.await;
|
||||
let compact = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "compaction",
|
||||
"encrypted_content": "compacted history",
|
||||
},
|
||||
}),
|
||||
responses::ev_completed("resp-compact"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
test.codex.submit(Op::Compact).await?;
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
compact
|
||||
.single_request()
|
||||
.inputs_of_type("compaction_trigger")
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
|
||||
let wait = responses::mount_function_call_agent_response(
|
||||
&server,
|
||||
"call-2",
|
||||
@@ -4742,20 +4759,10 @@ async fn code_mode_node_repl_text_evidence_is_visible_only_to_guardian(
|
||||
const OTHER_NODE_REPL_RESULT: &str = "ECHOING: guardian-visible-other-tool-result";
|
||||
const UNRELATED_RESULT: &str = "ECHOING: guardian-hidden-unrelated-result";
|
||||
const PRIVATE_IMAGE: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==";
|
||||
const COMPACTION_SUMMARY: &str = "Guardian browser evidence summary";
|
||||
let server = responses::start_mock_server().await;
|
||||
let mcp_server_bin = remote_aware_stdio_server_bin()?;
|
||||
let reviewer_compaction = reviewer_constraint == Some("compaction");
|
||||
let compact = if reviewer_compaction {
|
||||
Some(
|
||||
responses::mount_compact_user_history_with_summary_once(
|
||||
&server,
|
||||
"Guardian browser evidence summary",
|
||||
)
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let check_detail = enhanced_transcripts && transcript_images && reviewer_constraint.is_none();
|
||||
let mut large_image = Cursor::new(Vec::new());
|
||||
if check_detail {
|
||||
@@ -4798,7 +4805,6 @@ async fn code_mode_node_repl_text_evidence_is_visible_only_to_guardian(
|
||||
.enable(Feature::CodeMode)
|
||||
.expect("enable Code Mode");
|
||||
if reviewer_compaction {
|
||||
config.features.disable(Feature::RemoteCompactionV2).expect("use remote compaction");
|
||||
config
|
||||
.features
|
||||
.enable(Feature::TokenBudget)
|
||||
@@ -4877,9 +4883,8 @@ await tools.exec_command({ cmd: "printf second", sandbox_permissions: "require_e
|
||||
.replace("SNAPSHOT_PADDING", &snapshot_padding.to_string())
|
||||
.replace("REVIEWER_COMPACTION", &reviewer_compaction.to_string())
|
||||
.replace("LARGE_IMAGE", &check_detail.to_string());
|
||||
let response_mock = responses::mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
let response_mock = responses::mount_sse_sequence(&server, {
|
||||
let mut response_bodies = vec![
|
||||
sse(vec![
|
||||
responses::ev_function_call_with_namespace(
|
||||
"node-repl-call",
|
||||
@@ -4913,13 +4918,25 @@ await tools.exec_command({ cmd: "printf second", sandbox_permissions: "require_e
|
||||
ev_completed("resp-guardian")
|
||||
},
|
||||
]),
|
||||
];
|
||||
if reviewer_compaction {
|
||||
response_bodies.push(sse(vec![
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {"type": "compaction", "encrypted_content": COMPACTION_SUMMARY},
|
||||
}),
|
||||
ev_completed("resp-guardian-compact"),
|
||||
]));
|
||||
}
|
||||
response_bodies.extend([
|
||||
sse(vec![
|
||||
ev_assistant_message("guardian-again", r#"{"outcome":"allow"}"#),
|
||||
ev_completed("resp-guardian-again"),
|
||||
]),
|
||||
sse(vec![ev_completed("resp-done")]),
|
||||
],
|
||||
)
|
||||
]);
|
||||
response_bodies
|
||||
})
|
||||
.await;
|
||||
test.submit_text_turn(&format!("review a nested {repl_server} tool response"))
|
||||
.await?;
|
||||
@@ -4928,6 +4945,7 @@ await tools.exec_command({ cmd: "printf second", sandbox_permissions: "require_e
|
||||
.iter()
|
||||
.filter(|request| {
|
||||
request.body_json()["client_metadata"]["x-openai-subagent"].as_str() == Some("guardian")
|
||||
&& request.inputs_of_type("compaction_trigger").is_empty()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(guardian_requests.len(), 2);
|
||||
@@ -5012,8 +5030,13 @@ await tools.exec_command({ cmd: "printf second", sandbox_permissions: "require_e
|
||||
reviewer_image_urls
|
||||
}
|
||||
);
|
||||
if let Some(compact) = compact {
|
||||
let compact_request = compact.single_request();
|
||||
if reviewer_compaction {
|
||||
let compact_requests = requests
|
||||
.iter()
|
||||
.filter(|request| !request.inputs_of_type("compaction_trigger").is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(compact_requests.len(), 1);
|
||||
let compact_request = compact_requests[0];
|
||||
assert!(
|
||||
compact_request
|
||||
.message_input_texts("user")
|
||||
@@ -5022,7 +5045,7 @@ await tools.exec_command({ cmd: "printf second", sandbox_permissions: "require_e
|
||||
);
|
||||
assert_eq!(
|
||||
guardian_requests[1].inputs_of_type("compaction")[0]["encrypted_content"],
|
||||
"Guardian browser evidence summary"
|
||||
COMPACTION_SUMMARY
|
||||
);
|
||||
for request in &guardian_requests {
|
||||
assert!(!request.has_content_kinds(&["token_budget.context_window"]));
|
||||
|
||||
@@ -55,8 +55,6 @@ use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_completed_with_tokens;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
use core_test_support::responses::mount_compact_json_once;
|
||||
use core_test_support::responses::mount_compact_response_sequence;
|
||||
use core_test_support::responses::mount_response_sequence;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
use core_test_support::responses::mount_sse_once_match;
|
||||
@@ -1021,6 +1019,8 @@ async fn reasoning_effort_override_remote_v2_compaction_resets_pinned_effort(
|
||||
ev_assistant_message("m4", "third reply"),
|
||||
ev_completed("r4"),
|
||||
]),
|
||||
sse(vec![ev_completed("r5")]),
|
||||
sse(vec![ev_completed("r6")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -1031,10 +1031,6 @@ async fn reasoning_effort_override_remote_v2_compaction_resets_pinned_effort(
|
||||
model.use_responses_lite = true;
|
||||
})
|
||||
.with_config(move |config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RemoteCompactionV2)
|
||||
.expect("enable remote compaction v2");
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::Medium);
|
||||
config
|
||||
@@ -1065,18 +1061,31 @@ async fn reasoning_effort_override_remote_v2_compaction_resets_pinned_effort(
|
||||
})
|
||||
.await;
|
||||
test.submit_text_turn("after compaction").await?;
|
||||
test.submit_text_turn("unchanged effort").await?;
|
||||
core_test_support::submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
effort: Some(Some(ReasoningEffort::Low)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
test.submit_text_turn("changed effort").await?;
|
||||
|
||||
let requests = request_log.requests();
|
||||
assert_eq!(requests.len(), 4);
|
||||
assert_eq!(requests.len(), 6);
|
||||
let first_body = requests[0].body_json();
|
||||
let second_body = requests[1].body_json();
|
||||
let post_compaction_body = requests[3].body_json();
|
||||
assert_eq!(first_body["reasoning"]["effort"], "medium");
|
||||
assert_eq!(second_body["reasoning"]["effort"], "medium");
|
||||
assert_eq!(requests[2].body_json()["reasoning"]["effort"], "medium");
|
||||
let post_compaction_effort = if fail_compaction { "medium" } else { "high" };
|
||||
assert_eq!(
|
||||
post_compaction_body["reasoning"]["effort"],
|
||||
if fail_compaction { "medium" } else { "high" }
|
||||
requests[3..]
|
||||
.iter()
|
||||
.map(|request| request.body_json()["reasoning"]["effort"].clone())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![json!(post_compaction_effort); 3],
|
||||
);
|
||||
let updates = requests
|
||||
.iter()
|
||||
@@ -1096,17 +1105,26 @@ async fn reasoning_effort_override_remote_v2_compaction_resets_pinned_effort(
|
||||
"type": "configuration_update",
|
||||
"reasoning": {"effort": "high"},
|
||||
});
|
||||
let low = json!({
|
||||
"type": "configuration_update",
|
||||
"reasoning": {"effort": "low"},
|
||||
});
|
||||
let retained = if fail_compaction {
|
||||
vec![medium.clone(), high.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let mut changed = retained.clone();
|
||||
changed.push(low);
|
||||
assert_eq!(
|
||||
updates,
|
||||
[
|
||||
vec![medium.clone()],
|
||||
vec![medium.clone(), high.clone()],
|
||||
vec![medium.clone(), high.clone()],
|
||||
if fail_compaction {
|
||||
vec![medium, high]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
vec![medium, high],
|
||||
retained.clone(),
|
||||
retained,
|
||||
changed,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -2175,36 +2193,16 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
||||
|
||||
let limit = 200_000;
|
||||
let over_limit_tokens = 250_000;
|
||||
let remote_summary = "REMOTE_COMPACT_SUMMARY";
|
||||
|
||||
let compacted_history = vec![
|
||||
codex_protocol::models::ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![codex_protocol::models::ContentItem::OutputText {
|
||||
text: remote_summary.to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
];
|
||||
let compact_mock =
|
||||
mount_compact_json_once(&server, serde_json::json!({ "output": compacted_history })).await;
|
||||
let remote_summary = REMOTE_V2_SUMMARY;
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
set_test_compact_prompt(config);
|
||||
config.model_auto_compact_token_limit = Some(limit);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
});
|
||||
let initial = builder.build(&server).await.unwrap();
|
||||
|
||||
// A single over-limit completion should not auto-compact until the next user message.
|
||||
mount_sse_once(
|
||||
let initial_response = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
@@ -2214,29 +2212,28 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
||||
.await;
|
||||
initial.submit_turn("OVER_LIMIT_TURN").await.unwrap();
|
||||
|
||||
let initial_requests = initial_response.requests();
|
||||
assert_eq!(initial_requests.len(), 1);
|
||||
assert!(
|
||||
compact_mock.requests().is_empty(),
|
||||
initial_requests[0]
|
||||
.inputs_of_type("compaction_trigger")
|
||||
.is_empty(),
|
||||
"remote compaction should not run before the next user message"
|
||||
);
|
||||
|
||||
let mut resume_builder = test_codex().with_config(move |config| {
|
||||
set_test_compact_prompt(config);
|
||||
config.model_auto_compact_token_limit = Some(limit);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
});
|
||||
let resumed = resume_builder.restart(&server, &initial).await.unwrap();
|
||||
|
||||
let follow_up_user = "AFTER_RESUME_USER";
|
||||
let compact_turn = remote_v2_compaction_response();
|
||||
let sse_follow_up = sse(vec![
|
||||
ev_assistant_message("m2", FINAL_REPLY),
|
||||
ev_completed("r2"),
|
||||
]);
|
||||
|
||||
let follow_up_matcher = move |req: &wiremock::Request| {
|
||||
let body = std::str::from_utf8(&req.body).unwrap_or("");
|
||||
body.contains(follow_up_user) && body.contains(remote_summary)
|
||||
};
|
||||
mount_sse_once_match(&server, follow_up_matcher, sse_follow_up).await;
|
||||
let response_mock = mount_sse_sequence(&server, vec![compact_turn, sse_follow_up]).await;
|
||||
|
||||
resumed
|
||||
.codex
|
||||
@@ -2257,17 +2254,21 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
||||
})
|
||||
.await;
|
||||
|
||||
let compact_requests = compact_mock.requests();
|
||||
let requests = response_mock.requests();
|
||||
assert_eq!(
|
||||
compact_requests.len(),
|
||||
1,
|
||||
"remote compaction should run once after resume"
|
||||
requests.len(),
|
||||
2,
|
||||
"remote compaction should run once after resume before sampling"
|
||||
);
|
||||
assert_eq!(
|
||||
compact_requests[0].path(),
|
||||
"/v1/responses/compact",
|
||||
"remote compaction should hit the compact endpoint"
|
||||
requests[0].path(),
|
||||
"/v1/responses",
|
||||
"remote compaction should use the responses endpoint"
|
||||
);
|
||||
assert_eq!(requests[0].inputs_of_type("compaction_trigger").len(), 1);
|
||||
let follow_up_body = requests[1].body_json().to_string();
|
||||
assert!(follow_up_body.contains(follow_up_user));
|
||||
assert!(follow_up_body.contains(remote_summary));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -2956,112 +2957,6 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pre_sampling_legacy_remote_compact_falls_back_after_previous_model_invalid_request() {
|
||||
skip_if_no_network!();
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let retired_model = "gpt-5.6";
|
||||
let previous_model_family = "gpt-5.6";
|
||||
let next_model = "gpt-5.5";
|
||||
let mut previous_model_info =
|
||||
model_info_with_context_window("gpt-5.4", /*context_window*/ 273_000);
|
||||
previous_model_info.slug = previous_model_family.to_string();
|
||||
let mut next_model_info =
|
||||
model_info_with_context_window("gpt-5.4", /*context_window*/ 125_000);
|
||||
next_model_info.slug = next_model.to_string();
|
||||
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![previous_model_info, next_model_info],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let request_log = mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_assistant_message("m1", "before switch"),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 120_000),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m3", "after switch"),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 100),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let compact_request_log = mount_compact_response_sequence(
|
||||
&server,
|
||||
vec![
|
||||
invalid_request_response("previous-model compaction was rejected"),
|
||||
wiremock::ResponseTemplate::new(/*status*/ 200)
|
||||
.insert_header("content-type", "application/json")
|
||||
.set_body_json(json!({
|
||||
"output": [{
|
||||
"type": "compaction",
|
||||
"encrypted_content": "DOWNSHIFT_SUMMARY",
|
||||
}],
|
||||
})),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
let model_provider = openai_model_provider(&server);
|
||||
let mut builder = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_model(retired_model)
|
||||
.with_config(move |config| {
|
||||
config.model_provider = model_provider;
|
||||
set_test_compact_prompt(config);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
test.codex
|
||||
.start_or_steer_turn(disabled_permission_user_turn(
|
||||
"before switch",
|
||||
test.cwd.path().to_path_buf(),
|
||||
retired_model.to_string(),
|
||||
))
|
||||
.await
|
||||
.expect("submit first user turn");
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
|
||||
test.codex
|
||||
.start_or_steer_turn(disabled_permission_user_turn(
|
||||
"after switch",
|
||||
test.cwd.path().to_path_buf(),
|
||||
next_model.to_string(),
|
||||
))
|
||||
.await
|
||||
.expect("submit smaller-model turn");
|
||||
assert_compaction_uses_turn_lifecycle_id(&test.codex).await;
|
||||
|
||||
let requests = request_log.requests();
|
||||
let compact_requests = compact_request_log.requests();
|
||||
assert_eq!(models_mock.requests().len(), 1);
|
||||
assert_eq!(requests.len(), 2);
|
||||
assert_eq!(compact_requests.len(), 2);
|
||||
assert_eq!(
|
||||
requests[0].body_json()["model"].as_str(),
|
||||
Some(retired_model)
|
||||
);
|
||||
assert_eq!(
|
||||
compact_requests[0].body_json()["model"].as_str(),
|
||||
Some(retired_model)
|
||||
);
|
||||
assert_eq!(
|
||||
compact_requests[1].body_json()["model"].as_str(),
|
||||
Some(next_model)
|
||||
);
|
||||
assert_eq!(requests[1].body_json()["model"].as_str(), Some(next_model));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pre_sampling_compact_keeps_unknown_previous_model_for_api_key_auth_and_custom_provider() {
|
||||
skip_if_no_network!();
|
||||
@@ -4771,8 +4666,10 @@ async fn auto_compact_body_after_prefix_still_caps_at_context_window() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case::test_case(false; "counts_encrypted_reasoning_before_last_user")]
|
||||
#[test_case::test_case(true; "compacts_after_reasoning_header_clears")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
async fn auto_compact_accounts_for_encrypted_reasoning(first_response_includes_reasoning: bool) {
|
||||
skip_if_no_network!();
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -4792,51 +4689,36 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
ev_reasoning_item("post-reasoning", &["post"], &[&post_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]);
|
||||
let compact_turn = remote_v2_compaction_response();
|
||||
let third_turn = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 1),
|
||||
]);
|
||||
|
||||
let request_log = mount_sse_sequence(
|
||||
let request_log = mount_response_sequence(
|
||||
&server,
|
||||
vec![
|
||||
// Turn 1: reasoning before last user (should count).
|
||||
first_turn,
|
||||
// Turn 2: reasoning after last user (should be ignored for compaction).
|
||||
second_turn,
|
||||
if first_response_includes_reasoning {
|
||||
sse_response(first_turn).insert_header("X-Reasoning-Included", "true")
|
||||
} else {
|
||||
sse_response(first_turn)
|
||||
},
|
||||
// Turn 2: reasoning after last user should be ignored; no header is returned.
|
||||
sse_response(second_turn),
|
||||
// Turn 3: compact the prior conversation before sampling.
|
||||
sse_response(compact_turn),
|
||||
// Turn 3: next user turn after remote compaction.
|
||||
third_turn,
|
||||
sse_response(third_turn),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
let compacted_history = vec![
|
||||
codex_protocol::models::ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![codex_protocol::models::ContentItem::OutputText {
|
||||
text: "REMOTE_COMPACT_SUMMARY".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
];
|
||||
let compact_mock =
|
||||
mount_compact_json_once(&server, serde_json::json!({ "output": compacted_history })).await;
|
||||
let chatgpt_base_url = format!("{}/backend-api", server.uri());
|
||||
|
||||
let codex = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = chatgpt_base_url;
|
||||
.with_config(|config| {
|
||||
set_test_compact_prompt(config);
|
||||
config.model_auto_compact_token_limit = Some(300);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -4853,135 +4735,41 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
text_elements: Vec::new(),
|
||||
}]))
|
||||
.await
|
||||
.unwrap();
|
||||
.expect("start user turn");
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
if idx < 2 {
|
||||
assert!(
|
||||
compact_mock.requests().is_empty(),
|
||||
request_log
|
||||
.requests()
|
||||
.iter()
|
||||
.all(|request| request.inputs_of_type("compaction_trigger").is_empty()),
|
||||
"remote compaction should not run before the next user turn"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let compact_requests = compact_mock.requests();
|
||||
assert_eq!(
|
||||
compact_requests.len(),
|
||||
1,
|
||||
"remote compaction should run once after the second turn"
|
||||
);
|
||||
assert_eq!(
|
||||
compact_requests[0].path(),
|
||||
"/v1/responses/compact",
|
||||
"remote compaction should hit the compact endpoint"
|
||||
);
|
||||
|
||||
let requests = request_log.requests();
|
||||
assert_eq!(
|
||||
requests.len(),
|
||||
3,
|
||||
"conversation should include three user turns"
|
||||
4,
|
||||
"conversation should include three user turns and remote compaction"
|
||||
);
|
||||
assert_eq!(requests[2].path(), "/v1/responses");
|
||||
assert_eq!(requests[2].inputs_of_type("compaction_trigger").len(), 1);
|
||||
assert!(requests[3].inputs_of_type("compaction_trigger").is_empty());
|
||||
let second_request_body = requests[1].body_json().to_string();
|
||||
assert!(
|
||||
!second_request_body.contains("REMOTE_COMPACT_SUMMARY"),
|
||||
!second_request_body.contains(REMOTE_V2_SUMMARY),
|
||||
"second turn should not include compacted history"
|
||||
);
|
||||
let third_request_body = requests[2].body_json().to_string();
|
||||
let third_request_body = requests[3].body_json().to_string();
|
||||
assert!(
|
||||
third_request_body.contains("REMOTE_COMPACT_SUMMARY")
|
||||
|| third_request_body.contains(FINAL_REPLY),
|
||||
"third turn should include compacted history"
|
||||
);
|
||||
assert!(
|
||||
third_request_body.contains("ENCRYPTED_COMPACTION_SUMMARY"),
|
||||
third_request_body.contains(REMOTE_V2_SUMMARY),
|
||||
"third turn should include compaction summary item"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
||||
skip_if_no_network!();
|
||||
|
||||
let server = start_mock_server().await;
|
||||
|
||||
let first_user = "SERVER_INCLUDED_FIRST";
|
||||
let second_user = "SERVER_INCLUDED_SECOND";
|
||||
let third_user = "SERVER_INCLUDED_THIRD";
|
||||
|
||||
let pre_last_reasoning_content = "a".repeat(2_400);
|
||||
let post_last_reasoning_content = "b".repeat(4_000);
|
||||
|
||||
let first_turn = sse(vec![
|
||||
ev_reasoning_item("pre-reasoning", &["pre"], &[&pre_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 10),
|
||||
]);
|
||||
let second_turn = sse(vec![
|
||||
ev_reasoning_item("post-reasoning", &["post"], &[&post_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]);
|
||||
let third_turn = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 1),
|
||||
]);
|
||||
|
||||
let responses = vec![
|
||||
sse_response(first_turn).insert_header("X-Reasoning-Included", "true"),
|
||||
sse_response(second_turn),
|
||||
sse_response(third_turn),
|
||||
];
|
||||
mount_response_sequence(&server, responses).await;
|
||||
|
||||
let compacted_history = vec![
|
||||
codex_protocol::models::ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![codex_protocol::models::ContentItem::OutputText {
|
||||
text: "REMOTE_COMPACT_SUMMARY".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
},
|
||||
];
|
||||
let compact_mock =
|
||||
mount_compact_json_once(&server, serde_json::json!({ "output": compacted_history })).await;
|
||||
|
||||
let codex = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(|config| {
|
||||
set_test_compact_prompt(config);
|
||||
config.model_auto_compact_token_limit = Some(300);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
.expect("build codex")
|
||||
.codex;
|
||||
|
||||
for user in [first_user, second_user, third_user] {
|
||||
codex
|
||||
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||
text: user.into(),
|
||||
text_elements: Vec::new(),
|
||||
}]))
|
||||
.await
|
||||
.unwrap();
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
}
|
||||
|
||||
let compact_requests = compact_mock.requests();
|
||||
assert_eq!(
|
||||
compact_requests.len(),
|
||||
1,
|
||||
"remote compaction should run once after the reasoning header clears"
|
||||
);
|
||||
}
|
||||
|
||||
#[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() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
//! Checks request-history trimming at the streamed remote compaction boundary.
|
||||
|
||||
use super::*;
|
||||
use core_test_support::apps_test_server::configure_search_capable_model;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn compact_response() -> String {
|
||||
sse(vec![
|
||||
json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {"type": "compaction", "encrypted_content": "TRIMMING_SUMMARY"},
|
||||
}),
|
||||
responses::ev_completed("compact"),
|
||||
])
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_compact_v2_trims_tool_search_output_to_empty_tools_array() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let call_id = "tool-search-1";
|
||||
let tools = json!([{
|
||||
"type": "namespace", "name": "codex_app", "description": "Codex app tools.",
|
||||
"tools": [{
|
||||
"type": "function", "name": "oversized_dynamic_tool", "description": "x".repeat(20_000),
|
||||
"parameters": {"type": "object", "properties": {}, "additionalProperties": false},
|
||||
"strict": false, "defer_loading": true,
|
||||
}],
|
||||
}]);
|
||||
for (context_window, expected_tools) in [(200_000, &tools), (2_000, &json!([]))] {
|
||||
let harness = TestCodexHarness::with_builder(
|
||||
test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
configure_search_capable_model(config);
|
||||
config.model_context_window = Some(context_window);
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let codex = &harness.test().codex;
|
||||
let history = [
|
||||
json!({"type": "message", "role": "user", "content": [{"type": "input_text", "text": "find the tool"}]}),
|
||||
json!({"type": "tool_search_call", "call_id": call_id, "execution": "client", "arguments": {"query": "oversized deferred tool"}}),
|
||||
json!({"type": "tool_search_output", "call_id": call_id, "status": "completed", "execution": "client", "tools": &tools}),
|
||||
]
|
||||
.into_iter()
|
||||
.map(serde_json::from_value)
|
||||
.collect::<serde_json::Result<Vec<ResponseItem>>>()?;
|
||||
codex.inject_response_items(history).await?;
|
||||
let mock = mount_sse_once(harness.server(), compact_response()).await;
|
||||
codex.submit(Op::Compact).await?;
|
||||
wait_for_turn_complete(codex).await;
|
||||
|
||||
let request = mock.single_request();
|
||||
assert_eq!(request.path(), "/v1/responses");
|
||||
assert_eq!(request.inputs_of_type("compaction_trigger").len(), 1);
|
||||
assert!(
|
||||
request
|
||||
.inputs_of_type("tool_search_call")
|
||||
.iter()
|
||||
.any(|item| item["call_id"] == call_id)
|
||||
);
|
||||
let output = request.tool_search_output(call_id);
|
||||
assert_eq!(&output["tools"], expected_tools);
|
||||
assert_eq!(output["call_id"], call_id);
|
||||
assert_eq!(output["status"], "completed");
|
||||
assert_eq!(output["execution"], "client");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_compact_v2_trim_estimate_uses_session_base_instructions() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let short_instructions = "session base instructions";
|
||||
let long_instructions = format!("{short_instructions} {}", "x".repeat(24_000));
|
||||
let trailing_output = "x".repeat(12_000);
|
||||
for (instructions, expected_output) in [
|
||||
(short_instructions, trailing_output.as_str()),
|
||||
(
|
||||
long_instructions.as_str(),
|
||||
CONTEXT_WINDOW_TRUNCATED_OUTPUT_MESSAGE,
|
||||
),
|
||||
] {
|
||||
let harness = TestCodexHarness::with_builder(
|
||||
test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config({
|
||||
let instructions = instructions.to_string();
|
||||
move |config| {
|
||||
config.model_context_window = Some(8_000);
|
||||
config.base_instructions = Some(instructions);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let codex = &harness.test().codex;
|
||||
let history = [
|
||||
json!({"type": "message", "role": "user", "content": [{"type": "input_text", "text": "older user"}]}),
|
||||
json!({"type": "function_call", "call_id": "retained", "name": "exec_command", "arguments": "{}"}),
|
||||
json!({"type": "function_call_output", "call_id": "retained", "output": "retained output"}),
|
||||
json!({"type": "message", "role": "user", "content": [{"type": "input_text", "text": "user boundary"}]}),
|
||||
json!({"type": "function_call", "call_id": "trailing", "name": "exec_command", "arguments": "{}"}),
|
||||
json!({"type": "function_call_output", "call_id": "trailing", "output": &trailing_output}),
|
||||
]
|
||||
.into_iter()
|
||||
.map(serde_json::from_value)
|
||||
.collect::<serde_json::Result<Vec<ResponseItem>>>()?;
|
||||
codex.inject_response_items(history).await?;
|
||||
let mock = mount_sse_once(harness.server(), compact_response()).await;
|
||||
codex.submit(Op::Compact).await?;
|
||||
wait_for_turn_complete(codex).await;
|
||||
|
||||
let request = mock.single_request();
|
||||
assert_eq!(request.path(), "/v1/responses");
|
||||
assert_eq!(request.inputs_of_type("compaction_trigger").len(), 1);
|
||||
assert_eq!(request.instructions_text(), instructions);
|
||||
assert!(request.has_function_call("retained"));
|
||||
assert!(request.has_function_call("trailing"));
|
||||
assert_eq!(
|
||||
request.function_call_output_text("retained").as_deref(),
|
||||
Some("retained output")
|
||||
);
|
||||
assert_eq!(
|
||||
request.function_call_output_text("trailing").as_deref(),
|
||||
Some(expected_output)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -188,49 +188,6 @@ async fn cyber_access_program_omits_api_key_and_spoofed_custom_provider() -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn cyber_access_program_survives_mid_turn_remote_compaction() -> Result<()> {
|
||||
core_test_support::skip_if_no_network!(Ok(()));
|
||||
let server = responses::start_mock_server().await;
|
||||
let requests = responses::mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
// A dummy tool response forces a follow-up without shell approvals.
|
||||
responses::ev_function_call("tool-1", "test_tool", "{}"),
|
||||
responses::ev_completed_with_tokens("resp-1", /*total_tokens*/ 100000000),
|
||||
]),
|
||||
responses::sse(vec![responses::ev_completed("resp-2")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let compact =
|
||||
responses::mount_compact_user_history_with_summary_once(&server, "compacted history").await;
|
||||
let test = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(|config| {
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
|
||||
submit(&test, Some(CyberAccessProgram::DaybreakBlue)).await?;
|
||||
|
||||
assert_eq!(
|
||||
compact.single_request().body_json()["access_programs"],
|
||||
json!({"cyber": "daybreak_blue"})
|
||||
);
|
||||
assert_eq!(
|
||||
requests
|
||||
.requests()
|
||||
.iter()
|
||||
.map(|request| request.body_json()["access_programs"].clone())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![json!({"cyber": "daybreak_blue"}); 2]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn cyber_access_program_survives_mid_turn_remote_compaction_v2() -> Result<()> {
|
||||
core_test_support::skip_if_no_network!(Ok(()));
|
||||
@@ -261,7 +218,7 @@ async fn cyber_access_program_survives_mid_turn_remote_compaction_v2() -> Result
|
||||
let test = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(|config| {
|
||||
let _ = config.features.enable(Feature::RemoteCompactionV2);
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
config.model_auto_compact_token_limit = Some(200);
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
|
||||
@@ -302,10 +302,6 @@ async fn guardian_review_compacts_with_summary_despite_parent_token_budget(
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let summary = "Guardian retained the user's standing authorization.";
|
||||
let compact = core_test_support::responses::mount_compact_user_history_with_summary_once(
|
||||
&server, summary,
|
||||
)
|
||||
.await;
|
||||
let mut builder = test_codex()
|
||||
.with_model_info_override("gpt-5.5", |model| {
|
||||
model.auto_review_model_override = Some(model.slug.clone());
|
||||
@@ -328,10 +324,6 @@ async fn guardian_review_compacts_with_summary_despite_parent_token_budget(
|
||||
.features
|
||||
.set_enabled(Feature::GuardianThreadContext, thread_owned)
|
||||
.expect("configure Guardian context mode");
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("use remote compaction");
|
||||
config.model_context_window = Some(100_000);
|
||||
config.model_auto_compact_token_limit = Some(50_000);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
@@ -367,6 +359,13 @@ async fn guardian_review_compacts_with_summary_despite_parent_token_budget(
|
||||
ev_function_call("exec-second", "exec_command", &command),
|
||||
ev_completed("resp-parent-second"),
|
||||
]),
|
||||
sse(vec![
|
||||
json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {"type": "compaction", "encrypted_content": summary},
|
||||
}),
|
||||
ev_completed("resp-guardian-compact"),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_response_created("resp-guardian-second"),
|
||||
ev_assistant_message("guardian-second", approval),
|
||||
@@ -389,6 +388,7 @@ async fn guardian_review_compacts_with_summary_despite_parent_token_budget(
|
||||
.iter()
|
||||
.filter(|request| {
|
||||
request.body_json()["client_metadata"]["x-openai-subagent"].as_str() == Some("guardian")
|
||||
&& request.inputs_of_type("compaction_trigger").is_empty()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(guardian_requests.len(), 2);
|
||||
@@ -402,7 +402,12 @@ async fn guardian_review_compacts_with_summary_despite_parent_token_budget(
|
||||
for request in &guardian_requests {
|
||||
assert!(!request.has_content_kinds(&["token_budget.context_window"]));
|
||||
}
|
||||
let compact_request = compact.single_request();
|
||||
let compact_requests = requests
|
||||
.iter()
|
||||
.filter(|request| !request.inputs_of_type("compaction_trigger").is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(compact_requests.len(), 1);
|
||||
let compact_request = compact_requests[0];
|
||||
assert!(
|
||||
compact_request
|
||||
.message_input_texts("user")
|
||||
|
||||
@@ -56,7 +56,6 @@ mod codex_delegate;
|
||||
mod collaboration_instructions;
|
||||
mod compact;
|
||||
mod compact_remote;
|
||||
mod compact_remote_parity;
|
||||
mod compact_resume_fork;
|
||||
mod context_annotations;
|
||||
mod current_time_reminder;
|
||||
|
||||
@@ -502,28 +502,26 @@ async fn reasoning_effort_override_disabled_on_resume_retires_update_at_compacti
|
||||
-> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let server = responses::start_mock_server().await;
|
||||
let mut mocks = Vec::new();
|
||||
for (id, reply) in [
|
||||
("initial", "initial reply"),
|
||||
("resumed", "resumed reply"),
|
||||
("after", "after compaction reply"),
|
||||
] {
|
||||
mocks.push(
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message(id, reply),
|
||||
responses::ev_completed(id),
|
||||
]),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
let compact = responses::mount_compact_json_once(
|
||||
let reply = |id, text| {
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message(id, text),
|
||||
responses::ev_completed(id),
|
||||
])
|
||||
};
|
||||
let mock = responses::mount_sse_sequence(
|
||||
&server,
|
||||
serde_json::json!({
|
||||
"output": [{"type": "compaction", "encrypted_content": "compacted-history"}]
|
||||
}),
|
||||
vec![
|
||||
reply("initial", "initial reply"),
|
||||
reply("resumed", "resumed reply"),
|
||||
responses::sse(vec![
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {"type": "compaction", "encrypted_content": "compacted-history"},
|
||||
}),
|
||||
responses::ev_completed("compact"),
|
||||
]),
|
||||
reply("after", "after compaction reply"),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let initial = override_builder().build_with_auto_env(&server).await?;
|
||||
@@ -536,10 +534,6 @@ async fn reasoning_effort_override_disabled_on_resume_retires_update_at_compacti
|
||||
.features
|
||||
.disable(Feature::ReasoningEffortOverride)
|
||||
.expect("disable overrides");
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("disable remote compaction v2");
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::High);
|
||||
config.chatgpt_base_url = chatgpt_base_url;
|
||||
})
|
||||
@@ -553,12 +547,7 @@ async fn reasoning_effort_override_disabled_on_resume_retires_update_at_compacti
|
||||
.await;
|
||||
resumed.submit_text_turn("after compaction").await?;
|
||||
|
||||
let requests = [
|
||||
mocks[0].single_request(),
|
||||
mocks[1].single_request(),
|
||||
compact.single_request(),
|
||||
mocks[2].single_request(),
|
||||
];
|
||||
let requests = mock.requests();
|
||||
assert_eq!(
|
||||
requests
|
||||
.iter()
|
||||
@@ -832,125 +821,9 @@ async fn reasoning_effort_override_resume_refreshes_selected_effort(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false; "success")]
|
||||
#[test_case(true; "failure")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reasoning_effort_override_remote_v1_compaction_uses_pin(
|
||||
fail_compaction: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let server = responses::start_mock_server().await;
|
||||
let first = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("first")]),
|
||||
)
|
||||
.await;
|
||||
let after = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("after")]),
|
||||
)
|
||||
.await;
|
||||
let unchanged = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("unchanged")]),
|
||||
)
|
||||
.await;
|
||||
let changed = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("changed")]),
|
||||
)
|
||||
.await;
|
||||
let compact = responses::mount_compact_response_once(
|
||||
&server,
|
||||
if fail_compaction {
|
||||
ResponseTemplate::new(400).set_body_json(serde_json::json!({
|
||||
"error": {"code": "invalid_request_error", "message": "compaction failed"}
|
||||
}))
|
||||
} else {
|
||||
ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||
"output": [{"type": "compaction", "encrypted_content": "compacted-history"}]
|
||||
}))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let chatgpt_base_url = format!("{}/backend-api", server.uri());
|
||||
let test = override_builder()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = chatgpt_base_url;
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("disable remote compaction v2");
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
test.submit_text_turn("first").await?;
|
||||
submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
effort: Some(Some(ReasoningEffort::High)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
test.codex.submit(Op::Compact).await?;
|
||||
if fail_compaction {
|
||||
wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await;
|
||||
}
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
test.submit_text_turn("after compaction").await?;
|
||||
test.submit_text_turn("unchanged effort").await?;
|
||||
submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
effort: Some(Some(ReasoningEffort::Low)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
test.submit_text_turn("changed effort").await?;
|
||||
let medium = effort_update(ReasoningEffort::Medium);
|
||||
let after_updates = if fail_compaction {
|
||||
vec![medium.clone(), effort_update(ReasoningEffort::High)]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut changed_updates = after_updates.clone();
|
||||
changed_updates.push(effort_update(ReasoningEffort::Low));
|
||||
let after_effort = Value::from(if fail_compaction { "medium" } else { "high" });
|
||||
assert_eq!(
|
||||
[
|
||||
first.single_request(),
|
||||
compact.single_request(),
|
||||
after.single_request(),
|
||||
unchanged.single_request(),
|
||||
changed.single_request(),
|
||||
]
|
||||
.map(|request| (
|
||||
request.body_json()["reasoning"]["effort"].clone(),
|
||||
effort_updates(&request),
|
||||
)),
|
||||
[
|
||||
(Value::from("medium"), vec![medium.clone()]),
|
||||
(Value::from("medium"), vec![medium]),
|
||||
(after_effort.clone(), after_updates.clone()),
|
||||
(after_effort.clone(), after_updates),
|
||||
(after_effort, changed_updates),
|
||||
],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(false; "remote v1")]
|
||||
#[test_case(true; "remote v2")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reasoning_effort_override_compaction_fallback_uses_each_models_effort(
|
||||
remote_v2: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
async fn reasoning_effort_override_compaction_fallback_uses_each_models_effort()
|
||||
-> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let server = responses::start_mock_server().await;
|
||||
let first = responses::mount_sse_once(
|
||||
@@ -969,30 +842,17 @@ async fn reasoning_effort_override_compaction_fallback_uses_each_models_effort(
|
||||
let failure = ResponseTemplate::new(/*s*/ 400).set_body_json(serde_json::json!({
|
||||
"error": {"message": "previous model cannot compact this history"}
|
||||
}));
|
||||
let compactions = if remote_v2 {
|
||||
responses::mount_response_sequence(
|
||||
&server,
|
||||
vec![
|
||||
failure,
|
||||
responses::sse_response(responses::sse(vec![
|
||||
serde_json::json!({"type": "response.output_item.done", "item": compaction}),
|
||||
responses::ev_completed("fallback"),
|
||||
])),
|
||||
],
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
responses::mount_compact_response_sequence(
|
||||
&server,
|
||||
vec![
|
||||
failure,
|
||||
ResponseTemplate::new(/*s*/ 200).set_body_json(serde_json::json!({
|
||||
"output": [compaction]
|
||||
})),
|
||||
],
|
||||
)
|
||||
.await
|
||||
};
|
||||
let compactions = responses::mount_response_sequence(
|
||||
&server,
|
||||
vec![
|
||||
failure,
|
||||
responses::sse_response(responses::sse(vec![
|
||||
serde_json::json!({"type": "response.output_item.done", "item": compaction}),
|
||||
responses::ev_completed("fallback"),
|
||||
])),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let after = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("after")]),
|
||||
@@ -1012,17 +872,6 @@ async fn reasoning_effort_override_compaction_fallback_uses_each_models_effort(
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = chatgpt_base_url;
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
if remote_v2 {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RemoteCompactionV2)
|
||||
.expect("enable remote compaction v2");
|
||||
} else {
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("disable remote compaction v2");
|
||||
}
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
|
||||
@@ -547,24 +547,30 @@ async fn responses_lite_compact_request_uses_lite_transport_contract() -> Result
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_once(
|
||||
let response_mock = responses::mount_sse_sequence(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "compaction",
|
||||
"encrypted_content": "RESPONSES_LITE_COMPACT_SUMMARY",
|
||||
}
|
||||
}),
|
||||
responses::ev_completed("resp-compact"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let compact_mock =
|
||||
responses::mount_compact_json_once(&server, serde_json::json!({ "output": [] })).await;
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_model_info_override("gpt-5.4", |model_info| {
|
||||
model_info.use_responses_lite = true;
|
||||
})
|
||||
.with_config(|config| {
|
||||
let _ = config.features.disable(Feature::RemoteCompactionV2);
|
||||
});
|
||||
let mut builder = test_codex().with_model_info_override("gpt-5.4", |model_info| {
|
||||
model_info.use_responses_lite = true;
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn("Compact this conversation").await?;
|
||||
@@ -574,8 +580,14 @@ async fn responses_lite_compact_request_uses_lite_transport_contract() -> Result
|
||||
})
|
||||
.await;
|
||||
|
||||
response_mock.single_request();
|
||||
let compact_request = compact_mock.single_request();
|
||||
let requests = response_mock.requests();
|
||||
assert_eq!(requests.len(), 2);
|
||||
let compact_request = &requests[1];
|
||||
assert_eq!(compact_request.path(), "/v1/responses");
|
||||
assert_eq!(
|
||||
compact_request.inputs_of_type("compaction_trigger").len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
compact_request.header(RESPONSES_LITE_HEADER).as_deref(),
|
||||
Some("true")
|
||||
|
||||
@@ -9,12 +9,10 @@ use test_case::test_case;
|
||||
#[derive(Clone, Copy)]
|
||||
enum CompactionMode {
|
||||
Local,
|
||||
Remote,
|
||||
RemoteV2,
|
||||
}
|
||||
|
||||
#[test_case(CompactionMode::Local; "local")]
|
||||
#[test_case(CompactionMode::Remote; "remote")]
|
||||
#[test_case(CompactionMode::RemoteV2; "remote v2")]
|
||||
#[tokio::test]
|
||||
async fn compaction_preserves_updated_summary(mode: CompactionMode) -> Result<()> {
|
||||
@@ -32,23 +30,16 @@ async fn compaction_preserves_updated_summary(mode: CompactionMode) -> Result<()
|
||||
responses::ev_completed_with_tokens("plan-response", /*total_tokens*/ 330_000),
|
||||
]),
|
||||
];
|
||||
let compact = match mode {
|
||||
CompactionMode::Local => {
|
||||
bodies.push(sse(vec![
|
||||
responses::ev_assistant_message("summary", "Compacted history"),
|
||||
ev_completed("compact"),
|
||||
]));
|
||||
None
|
||||
}
|
||||
CompactionMode::Remote => Some(
|
||||
responses::mount_compact_user_history_with_summary_once(&server, "encrypted-summary")
|
||||
.await,
|
||||
),
|
||||
CompactionMode::RemoteV2 => {
|
||||
bodies.push(sse(vec![json!({"type": "response.output_item.done", "item": {"type": "compaction", "encrypted_content": "encrypted-summary"}}), ev_completed("compact")]));
|
||||
None
|
||||
}
|
||||
};
|
||||
bodies.push(match mode {
|
||||
CompactionMode::Local => sse(vec![
|
||||
responses::ev_assistant_message("summary", "Compacted history"),
|
||||
ev_completed("compact"),
|
||||
]),
|
||||
CompactionMode::RemoteV2 => sse(vec![
|
||||
json!({"type": "response.output_item.done", "item": {"type": "compaction", "encrypted_content": "encrypted-summary"}}),
|
||||
ev_completed("compact"),
|
||||
]),
|
||||
});
|
||||
bodies.push(sse_completed("done"));
|
||||
let responses = mount_sse_sequence(&server, bodies).await;
|
||||
let test = step_settings_test()
|
||||
@@ -61,20 +52,9 @@ async fn compaction_preserves_updated_summary(mode: CompactionMode) -> Result<()
|
||||
.expect("disable token budget");
|
||||
config.model_provider.name = match mode {
|
||||
CompactionMode::Local => "Local compaction test",
|
||||
CompactionMode::Remote | CompactionMode::RemoteV2 => "OpenAI",
|
||||
CompactionMode::RemoteV2 => "OpenAI",
|
||||
}
|
||||
.to_string();
|
||||
if matches!(mode, CompactionMode::RemoteV2) {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RemoteCompactionV2)
|
||||
.expect("enable v2");
|
||||
} else {
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("disable v2");
|
||||
}
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
@@ -106,9 +86,6 @@ async fn compaction_preserves_updated_summary(mode: CompactionMode) -> Result<()
|
||||
],
|
||||
[json!("concise"), json!("detailed")]
|
||||
);
|
||||
if let Some(compact) = compact {
|
||||
compact.single_request();
|
||||
}
|
||||
let rollout =
|
||||
std::fs::read_to_string(test.session_configured.rollout_path.expect("rollout path"))?;
|
||||
let items = rollout
|
||||
|
||||
@@ -1297,10 +1297,6 @@ async fn grandchild_full_fork_preserves_context_baseline(
|
||||
config.update_plan_enabled = true;
|
||||
// Use local compaction so the test controls the replacement history.
|
||||
config.model_provider.name = "test-provider".to_string();
|
||||
config
|
||||
.features
|
||||
.disable(Feature::RemoteCompactionV2)
|
||||
.expect("test config should allow feature update");
|
||||
config.compact_prompt = Some(COMPACT_PROMPT.to_string());
|
||||
config.model_auto_compact_token_limit = Some(200_000);
|
||||
config.model_context_window = Some(1_000_000);
|
||||
|
||||
@@ -37,7 +37,6 @@ use core_test_support::responses::ev_completed_with_tokens;
|
||||
use core_test_support::responses::ev_exec_command_call;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_compact_json_once;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
use core_test_support::responses::mount_sse_sequence;
|
||||
use core_test_support::responses::sse;
|
||||
@@ -1058,8 +1057,6 @@ async fn token_budget_context_uses_new_window_after_compaction(
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let compact = mount_compact_json_once(&server, json!({ "output": [] })).await;
|
||||
|
||||
let mut model_provider = built_in_model_providers(/*openai_base_url*/ None)["openai"].clone();
|
||||
model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
model_provider.supports_websockets = false;
|
||||
@@ -1098,10 +1095,6 @@ async fn token_budget_context_uses_new_window_after_compaction(
|
||||
|
||||
let requests = responses.requests();
|
||||
assert_eq!(requests.len(), 2);
|
||||
assert!(
|
||||
compact.requests().is_empty(),
|
||||
"token budget compaction should not call server-side compaction"
|
||||
);
|
||||
|
||||
let initial_token_budget = token_budget_contexts(&requests[0]);
|
||||
assert_eq!(initial_token_budget.len(), 1);
|
||||
|
||||
@@ -588,7 +588,7 @@ impl Features {
|
||||
"js_repl_tools_only" => {
|
||||
continue;
|
||||
}
|
||||
"remote_control" => {
|
||||
"remote_control" | "remote_compaction_v2" => {
|
||||
continue;
|
||||
}
|
||||
"apply_patch_freeform" => {
|
||||
@@ -1732,8 +1732,8 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
FeatureSpec {
|
||||
id: Feature::RemoteCompactionV2,
|
||||
key: "remote_compaction_v2",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CompactionImageBudget,
|
||||
|
||||
Reference in New Issue
Block a user