From 583dee056d51d2d3b5cc0b14c840f3de9fc1bcb8 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Tue, 17 Feb 2026 23:12:56 -0800 Subject: [PATCH] Refactor remote compaction incoming-item dedup --- codex-rs/core/src/compact_remote.rs | 79 +++++++++++++++++------------ 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index f91b270497..f9c88538aa 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -189,39 +189,7 @@ async fn run_remote_compact_task_inner_impl( .filter(|item| should_keep_compacted_history_item(item)) .cloned() .collect(); - for incoming_item in incoming_history_items { - if let Some(index) = - new_history - .iter() - .rposition(|candidate| match (candidate, &incoming_item) { - ( - ResponseItem::Message { - role: candidate_role, - content: candidate_content, - .. - }, - ResponseItem::Message { - role: incoming_role, - content: incoming_content, - .. - }, - ) => { - candidate_role == incoming_role && candidate_content == incoming_content - } - ( - ResponseItem::Compaction { - encrypted_content: candidate_content, - }, - ResponseItem::Compaction { - encrypted_content: incoming_content, - }, - ) => candidate_content == incoming_content, - _ => candidate == &incoming_item, - }) - { - new_history.remove(index); - } - } + remove_incoming_echoes_from_compacted_history(&mut new_history, &incoming_history_items); } // Reattach stripped model-switch updates only for compaction paths that do not carry // incoming turn items. Pre-turn compaction appends turn context and user input after @@ -272,6 +240,51 @@ fn build_compact_request_log_data( } } +/// Remote compaction may echo incoming items in `new_history`. Because incoming items are +/// appended after compaction at the caller, remove one semantic duplicate for each incoming item +/// so turn-context/user entries do not appear twice. +fn remove_incoming_echoes_from_compacted_history( + new_history: &mut Vec, + incoming_history_items: &[ResponseItem], +) { + for incoming_item in incoming_history_items { + if let Some(index) = new_history.iter().rposition(|candidate| { + response_items_match_for_compaction_merge(candidate, incoming_item) + }) { + new_history.remove(index); + } + } +} + +fn response_items_match_for_compaction_merge( + candidate: &ResponseItem, + incoming_item: &ResponseItem, +) -> bool { + match (candidate, incoming_item) { + ( + ResponseItem::Message { + role: candidate_role, + content: candidate_content, + .. + }, + ResponseItem::Message { + role: incoming_role, + content: incoming_content, + .. + }, + ) => candidate_role == incoming_role && candidate_content == incoming_content, + ( + ResponseItem::Compaction { + encrypted_content: candidate_content, + }, + ResponseItem::Compaction { + encrypted_content: incoming_content, + }, + ) => candidate_content == incoming_content, + _ => candidate == incoming_item, + } +} + fn log_remote_compact_failure( turn_context: &TurnContext, auto_compact_callsite: AutoCompactCallsite,