diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 23ee7a722a..e69655c5ba 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -18,6 +18,7 @@ use crate::session::session::Session; use crate::session::step_context::StepContext; use crate::session::turn::get_last_assistant_message_from_turn; use crate::session::turn_context::TurnContext; +use crate::state::AutoCompactWindowIds; use crate::util::backoff; use codex_analytics::CodexCompactionEvent; use codex_analytics::CompactionImplementation; @@ -35,7 +36,6 @@ use codex_protocol::models::ContentItem; use codex_protocol::models::InternalChatMessageMetadataPassthrough; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::CompactedItem; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RawResponseCompletedEvent; use codex_protocol::protocol::TurnStartedEvent; @@ -72,6 +72,16 @@ pub(crate) enum InitialContextInjection { DoNotInject, } +/// Metadata for a new compaction checkpoint, kept separate from its replacement history. +/// +/// `Session::replace_compacted_history` assigns missing item IDs before constructing the persisted +/// `CompactedItem`, ensuring the live and persisted histories remain identical. +pub(crate) struct CompactedHistoryMetadata { + pub(crate) message: String, + pub(crate) window_number: u64, + pub(crate) window_ids: AutoCompactWindowIds, +} + pub(crate) async fn build_compaction_initial_context( sess: &Session, initial_context_injection: &InitialContextInjection, @@ -357,20 +367,16 @@ async fn run_compact_task_inner_impl( Some(turn_context.to_turn_context_item()) } }; - let compacted_item = CompactedItem { - message: summary_text.clone(), - replacement_history: Some(new_history.clone()), - window_number: Some(window_number), - first_window_id: Some(window_ids.first_window_id.to_string()), - previous_window_id: window_ids.previous_window_id.map(|id| id.to_string()), - window_id: Some(window_ids.window_id.to_string()), - }; sess.replace_compacted_history( turn_context.as_ref(), new_history, reference_context_item, world_state_baseline, - compacted_item, + CompactedHistoryMetadata { + message: summary_text, + window_number, + window_ids, + }, ) .await; sess.recompute_token_usage(&turn_context).await; diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index 07fc669c97..91716ff78e 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use std::sync::OnceLock; +use crate::compact::CompactedHistoryMetadata; use crate::compact::CompactionAnalyticsAttempt; use crate::compact::CompactionAnalyticsDetails; use crate::compact::InitialContextInjection; @@ -32,7 +33,6 @@ use codex_protocol::models::BaseInstructions; use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::CompactedItem; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::TurnStartedEvent; use codex_rollout_trace::CompactionCheckpointTracePayload; @@ -269,14 +269,6 @@ async fn run_remote_compact_task_inner_impl( Some(compaction_turn_context.to_turn_context_item()) } }; - let compacted_item = CompactedItem { - message: String::new(), - replacement_history: Some(new_history.clone()), - window_number: Some(new_window_number), - first_window_id: Some(new_window_ids.first_window_id.to_string()), - previous_window_id: new_window_ids.previous_window_id.map(|id| id.to_string()), - window_id: Some(new_window_ids.window_id.to_string()), - }; // Install is the semantic boundary where the compact endpoint's output becomes live // thread history. Keep it distinct from the later inference request so the reducer can // still represent repeated developer/context prefix items exactly as the model saw them. @@ -291,7 +283,11 @@ async fn run_remote_compact_task_inner_impl( new_history, reference_context_item, world_state_baseline, - compacted_item, + CompactedHistoryMetadata { + message: String::new(), + window_number: new_window_number, + window_ids: new_window_ids, + }, ) .await; sess.recompute_token_usage(compaction_turn_context).await; diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index b6340732e2..86b827731d 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -4,6 +4,7 @@ use crate::Prompt; use crate::ResponseStream; use crate::client::ModelClientSession; use crate::client_common::ResponseEvent; +use crate::compact::CompactedHistoryMetadata; use crate::compact::CompactionAnalyticsAttempt; use crate::compact::CompactionAnalyticsDetails; use crate::compact::InitialContextInjection; @@ -33,7 +34,6 @@ use codex_protocol::items::ContextCompactionItem; use codex_protocol::items::TurnItem; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::CompactedItem; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::TokenUsage; use codex_protocol::protocol::TruncationPolicy; @@ -293,14 +293,6 @@ async fn run_remote_compact_task_inner_impl( Some(compaction_turn_context.to_turn_context_item()) } }; - let compacted_item = CompactedItem { - message: String::new(), - replacement_history: Some(new_history.clone()), - window_number: Some(new_window_number), - first_window_id: Some(new_window_ids.first_window_id.to_string()), - previous_window_id: new_window_ids.previous_window_id.map(|id| id.to_string()), - window_id: Some(new_window_ids.window_id.to_string()), - }; if let Some(trace_input_history) = trace_input_history.as_deref() { compaction_trace.record_installed(&CompactionCheckpointTracePayload { input_history: trace_input_history, @@ -312,7 +304,11 @@ async fn run_remote_compact_task_inner_impl( new_history, reference_context_item, world_state_baseline, - compacted_item, + CompactedHistoryMetadata { + message: String::new(), + window_number: new_window_number, + window_ids: new_window_ids, + }, ) .await; sess.recompute_token_usage(compaction_turn_context).await; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index cca0385e01..5a9724521d 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -20,6 +20,7 @@ use crate::attestation::AttestationProvider; use crate::audio_preparation::prepare_response_items as prepare_audio_response_items; use crate::build_available_skills; use crate::compact; +use crate::compact::CompactedHistoryMetadata; use crate::config::ManagedFeatures; use crate::config::resolve_tool_suggest_config_from_layer_stack; use crate::context::ApprovedCommandPrefixSaved; @@ -3052,7 +3053,7 @@ impl Session { items: Vec, reference_context_item: Option, world_state_baseline: Option>, - compacted_item: CompactedItem, + metadata: CompactedHistoryMetadata, ) { let items = if turn_context.item_ids_enabled() { Self::assign_missing_response_item_ids(Cow::Owned(items)).into_owned() @@ -3060,8 +3061,15 @@ impl Session { items }; let compacted_item = CompactedItem { + message: metadata.message, replacement_history: Some(items.clone()), - ..compacted_item + window_number: Some(metadata.window_number), + first_window_id: Some(metadata.window_ids.first_window_id.to_string()), + previous_window_id: metadata + .window_ids + .previous_window_id + .map(|id| id.to_string()), + window_id: Some(metadata.window_ids.window_id.to_string()), }; // Compaction starts a new history window, so its WorldState baseline must be full. let mut world_state_item = None; @@ -3544,13 +3552,10 @@ impl Session { context_items, Some(turn_context_item), Some(world_state), - CompactedItem { + CompactedHistoryMetadata { message: String::new(), - replacement_history: None, - window_number: Some(window_number), - first_window_id: Some(window_ids.first_window_id.to_string()), - previous_window_id: window_ids.previous_window_id.map(|id| id.to_string()), - window_id: Some(window_ids.window_id.to_string()), + window_number, + window_ids, }, ) .await;