diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 6c8fdc7c89..6ab4b87311 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -20,7 +20,6 @@ use crate::apps::render_apps_section; use crate::commit_attribution::commit_message_trailer_instruction; use crate::compact; use crate::compact::AutoCompactCallsite; -use crate::compact::TurnContextReinjection; use crate::compact::run_inline_auto_compact_task; use crate::compact::should_use_remote_compact_task; use crate::compact_remote::run_inline_remote_auto_compact_task; @@ -2407,16 +2406,9 @@ impl Session { pub(crate) async fn process_compacted_history( &self, - turn_context: &TurnContext, compacted_history: Vec, - turn_context_reinjection: TurnContextReinjection, ) -> Vec { - let initial_context = self.build_initial_context(turn_context).await; - compact::process_compacted_history( - compacted_history, - &initial_context, - turn_context_reinjection, - ) + compact::process_compacted_history(compacted_history) } /// Append ResponseItems to the in-memory conversation history only. @@ -4533,7 +4525,6 @@ pub(crate) async fn run_turn( &sess, &turn_context, AutoCompactCallsite::MidTurnContinuation, - TurnContextReinjection::Skip, None, ) .await @@ -4687,7 +4678,6 @@ async fn maybe_run_previous_model_inline_compact( // We use previous turn context here because we compact with the previous model &previous_turn_context, AutoCompactCallsite::PreTurnExcludingIncomingUserMessage, - TurnContextReinjection::Skip, None, ) .await @@ -4811,7 +4801,6 @@ async fn run_pre_turn_auto_compaction_if_needed( sess, turn_context, AutoCompactCallsite::PreTurnIncludingIncomingUserMessage, - TurnContextReinjection::Skip, Some(incoming_turn_items.to_vec()), ) .await; @@ -4887,7 +4876,6 @@ async fn run_auto_compact( sess: &Arc, turn_context: &Arc, auto_compact_callsite: AutoCompactCallsite, - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { let result = if should_use_remote_compact_task(&turn_context.provider) { @@ -4895,7 +4883,6 @@ async fn run_auto_compact( Arc::clone(sess), Arc::clone(turn_context), auto_compact_callsite, - turn_context_reinjection, incoming_items, ) .await @@ -4904,7 +4891,6 @@ async fn run_auto_compact( Arc::clone(sess), Arc::clone(turn_context), auto_compact_callsite, - turn_context_reinjection, incoming_items, ) .await @@ -6192,7 +6178,8 @@ mod tests { .await; let actual = session.clone_history().await.raw_items().to_vec(); - let expected = vec![stale_pre_turn_context_items[0].clone(), response_item]; + let mut expected = session.build_initial_context(turn_context.as_ref()).await; + expected.push(response_item); assert_eq!(actual, expected); } diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index fdc76109c3..b3f3675fa3 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -48,19 +48,6 @@ pub(crate) enum AutoCompactCallsite { MidTurnContinuation, } -/// Controls whether compacted-history processing should reinsert canonical turn context. -/// -/// When callers exclude incoming user/context from the compaction request, they should typically -/// set reinjection to `Skip` and append canonical context together with the next user message. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum TurnContextReinjection { - /// Insert canonical context immediately above the last real user message in compacted history. - #[allow(dead_code)] - ReinjectAboveLastRealUser, - /// Do not reinsert canonical context while processing compacted history. - Skip, -} - pub(crate) fn should_use_remote_compact_task(provider: &ModelProviderInfo) -> bool { provider.is_openai() } @@ -105,7 +92,6 @@ pub(crate) async fn run_inline_auto_compact_task( sess: Arc, turn_context: Arc, auto_compact_callsite: AutoCompactCallsite, - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { let prompt = turn_context.compact_prompt().to_string(); @@ -120,7 +106,6 @@ pub(crate) async fn run_inline_auto_compact_task( turn_context, input, Some(auto_compact_callsite), - turn_context_reinjection, incoming_items, ) .await?; @@ -138,17 +123,7 @@ pub(crate) async fn run_compact_task( collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, start_event).await; - run_compact_task_inner( - sess, - turn_context, - input, - None, - // Manual `/compact` should not reinsert turn context into compacted history; we reseed - // canonical initial context before the next user turn. - TurnContextReinjection::Skip, - None, - ) - .await + run_compact_task_inner(sess, turn_context, input, None, None).await } async fn run_compact_task_inner( @@ -156,7 +131,6 @@ async fn run_compact_task_inner( turn_context: Arc, input: Vec, auto_compact_callsite: Option, - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new()); @@ -302,22 +276,12 @@ async fn run_compact_task_inner( let summary_suffix = get_last_assistant_message_from_turn(history_items).unwrap_or_default(); let summary_text = format!("{SUMMARY_PREFIX}\n{summary_suffix}"); let user_messages = collect_user_messages(history_items); - let initial_context = match turn_context_reinjection { - TurnContextReinjection::ReinjectAboveLastRealUser => { - sess.build_initial_context(turn_context.as_ref()).await - } - TurnContextReinjection::Skip => Vec::new(), - }; let compacted_history = build_compacted_history_with_limit( &user_messages, &summary_text, COMPACT_USER_MESSAGE_MAX_TOKENS, ); - let mut new_history = process_compacted_history( - compacted_history, - &initial_context, - turn_context_reinjection, - ); + let mut new_history = process_compacted_history(compacted_history); // 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 // compaction in run_turn. @@ -391,41 +355,14 @@ pub(crate) fn is_summary_message(message: &str) -> bool { pub(crate) fn process_compacted_history( mut compacted_history: Vec, - initial_context: &[ResponseItem], - turn_context_reinjection: TurnContextReinjection, ) -> Vec { // Keep only model-visible transcript items that we allow from remote compaction output. compacted_history.retain(should_keep_compacted_history_item); - match turn_context_reinjection { - TurnContextReinjection::ReinjectAboveLastRealUser => { - // Prefer inserting immediately above the last real user message so turn context - // applies to that user input rather than an earlier turn. If compaction output has no - // real user messages, insert before the last summary user message to keep canonical - // context present for the next sampling request. - let insertion_index = if let Some(last_real_user_index) = - compacted_history.iter().rposition(is_real_user_message) - { - last_real_user_index - } else if let Some(last_summary_index) = compacted_history.iter().rposition(|item| { - matches!( - crate::event_mapping::parse_turn_item(item), - Some(TurnItem::UserMessage(user_message)) - if is_summary_message(&user_message.message()) - ) - }) { - last_summary_index - } else { - compacted_history.len() - }; - compacted_history.splice(insertion_index..insertion_index, initial_context.to_vec()); - } - TurnContextReinjection::Skip => {} - } - compacted_history } +#[cfg(test)] fn is_real_user_message(item: &ResponseItem) -> bool { matches!( crate::event_mapping::parse_turn_item(item), @@ -1057,7 +994,7 @@ do things } #[test] - fn process_compacted_history_replaces_developer_messages() { + fn process_compacted_history_drops_developer_messages() { let compacted_history = vec![ ResponseItem::Message { id: None, @@ -1087,93 +1024,9 @@ do things phase: None, }, ]; - let initial_context = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - /tmp - zsh -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh personality".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - /tmp - zsh -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh personality".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "summary".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - assert_eq!(refreshed, expected); - } - - #[test] - fn process_compacted_history_reinjects_full_initial_context() { - let compacted_history = vec![ResponseItem::Message { + let refreshed = process_compacted_history(compacted_history); + let expected = vec![ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { @@ -1182,123 +1035,6 @@ do things end_turn: None, phase: None, }]; - let initial_context = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#"# AGENTS.md instructions for /repo - - -keep me updated -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - /repo - zsh -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - turn-1 - interrupted -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#"# AGENTS.md instructions for /repo - - -keep me updated -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - /repo - zsh -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: r#" - turn-1 - interrupted -"# - .to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "summary".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; assert_eq!(refreshed, expected); } @@ -1364,197 +1100,17 @@ keep me updated phase: None, }, ]; - let initial_context = vec![ResponseItem::Message { + + let refreshed = process_compacted_history(compacted_history); + let expected = vec![ResponseItem::Message { id: None, - role: "developer".to_string(), + role: "user".to_string(), content: vec![ContentItem::InputText { - text: "fresh developer instructions".to_string(), + text: "summary".to_string(), }], end_turn: None, phase: None, }]; - - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh developer instructions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "summary".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - assert_eq!(refreshed, expected); - } - - #[test] - fn process_compacted_history_inserts_context_before_last_real_user_message_only() { - let compacted_history = vec![ - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "latest user".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - let initial_context = vec![ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }]; - - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "latest user".to_string(), - }], - end_turn: None, - phase: None, - }, - ]; - assert_eq!(refreshed, expected); - } - - #[test] - fn process_compacted_history_pre_turn_places_summary_last() { - let compacted_history = vec![ - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ]; - let initial_context = vec![ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }]; - - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ]; assert_eq!(refreshed, expected); } @@ -1608,8 +1164,7 @@ keep me updated }, ]; - let refreshed = - process_compacted_history(compacted_history, &[], TurnContextReinjection::Skip); + let refreshed = process_compacted_history(compacted_history); let expected = vec![ ResponseItem::Message { id: None, @@ -1652,7 +1207,7 @@ keep me updated } #[test] - fn process_compacted_history_skips_context_insertion_without_real_user_message() { + fn process_compacted_history_keeps_summary_only_history() { let compacted_history = vec![ResponseItem::Message { id: None, role: "user".to_string(), @@ -1662,21 +1217,8 @@ keep me updated end_turn: None, phase: None, }]; - let initial_context = vec![ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }]; - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::Skip, - ); + let refreshed = process_compacted_history(compacted_history); let expected = vec![ResponseItem::Message { id: None, role: "user".to_string(), @@ -1688,73 +1230,4 @@ keep me updated }]; assert_eq!(refreshed, expected); } - - #[test] - fn process_compacted_history_mid_turn_without_orphan_user_places_summary_last() { - let compacted_history = vec![ - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ]; - let initial_context = vec![ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }]; - - let refreshed = process_compacted_history( - compacted_history, - &initial_context, - TurnContextReinjection::ReinjectAboveLastRealUser, - ); - let expected = vec![ - ResponseItem::Message { - id: None, - role: "developer".to_string(), - content: vec![ContentItem::InputText { - text: "fresh permissions".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "older user".to_string(), - }], - end_turn: None, - phase: None, - }, - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: format!("{SUMMARY_PREFIX}\nsummary text"), - }], - end_turn: None, - phase: None, - }, - ]; - assert_eq!(refreshed, expected); - } } diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index f9c88538aa..c0a289288a 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -4,7 +4,6 @@ use crate::Prompt; use crate::codex::Session; use crate::codex::TurnContext; use crate::compact::AutoCompactCallsite; -use crate::compact::TurnContextReinjection; use crate::compact::extract_latest_model_switch_update_from_items; use crate::compact::extract_trailing_model_switch_update_for_compaction_request; use crate::compact::should_keep_compacted_history_item; @@ -32,18 +31,10 @@ pub(crate) async fn run_inline_remote_auto_compact_task( sess: Arc, turn_context: Arc, auto_compact_callsite: AutoCompactCallsite, - // Controls whether canonical turn context should be reinserted into compacted history. - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { - run_remote_compact_task_inner( - &sess, - &turn_context, - auto_compact_callsite, - turn_context_reinjection, - incoming_items, - ) - .await?; + run_remote_compact_task_inner(&sess, &turn_context, auto_compact_callsite, incoming_items) + .await?; Ok(()) } @@ -62,9 +53,6 @@ pub(crate) async fn run_remote_compact_task( &sess, &turn_context, AutoCompactCallsite::PreTurnExcludingIncomingUserMessage, - // Manual `/compact` should not reinsert turn context into compacted history; we reseed - // canonical initial context before the next user turn. - TurnContextReinjection::Skip, None, ) .await @@ -74,14 +62,12 @@ async fn run_remote_compact_task_inner( sess: &Arc, turn_context: &Arc, auto_compact_callsite: AutoCompactCallsite, - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { if let Err(err) = run_remote_compact_task_inner_impl( sess, turn_context, auto_compact_callsite, - turn_context_reinjection, incoming_items, ) .await @@ -101,7 +87,6 @@ async fn run_remote_compact_task_inner_impl( sess: &Arc, turn_context: &Arc, auto_compact_callsite: AutoCompactCallsite, - turn_context_reinjection: TurnContextReinjection, incoming_items: Option>, ) -> CodexResult<()> { let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new()); @@ -180,9 +165,7 @@ async fn run_remote_compact_task_inner_impl( Err(err) }) .await?; - new_history = sess - .process_compacted_history(turn_context, new_history, turn_context_reinjection) - .await; + new_history = sess.process_compacted_history(new_history).await; if let Some(incoming_items) = incoming_items.as_ref() { let incoming_history_items: Vec = incoming_items .iter()