diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index fd9a4720bfa4..de8f69408911 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -1577,7 +1577,9 @@ fn normalize_adds_missing_output_for_function_call() { ResponseItem::FunctionCallOutput { id: None, call_id: "call-x".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1616,7 +1618,9 @@ fn normalize_adds_missing_output_for_custom_tool_call() { id: None, call_id: "tool-x".to_string(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1662,7 +1666,9 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { ResponseItem::FunctionCallOutput { id: None, call_id: "shell-1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1767,7 +1773,9 @@ fn normalize_mixed_inserts_and_removals() { ResponseItem::FunctionCallOutput { id: None, call_id: "c1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ResponseItem::CustomToolCall { @@ -1783,7 +1791,9 @@ fn normalize_mixed_inserts_and_removals() { id: None, call_id: "t1".to_string(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ResponseItem::LocalShellCall { @@ -1802,7 +1812,9 @@ fn normalize_mixed_inserts_and_removals() { ResponseItem::FunctionCallOutput { id: None, call_id: "s1".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1837,7 +1849,9 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() { ResponseItem::FunctionCallOutput { id: None, call_id: "call-x".to_string(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER.to_string() + ), internal_chat_message_metadata_passthrough: None, }, ] @@ -1954,7 +1968,10 @@ fn normalize_fills_missing_output_for_custom_tool_call() { ); }; assert_eq!(call_id, "tool-x"); - assert_eq!(output.body.to_text().as_deref(), Some("aborted")); + assert_eq!( + output.body.to_text().as_deref(), + Some(crate::context_manager::normalize::MISSING_OUTPUT_PLACEHOLDER) + ); } #[test] diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index f2bdd89c4167..f2973442765b 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -1,5 +1,5 @@ mod history; -mod normalize; +pub(crate) mod normalize; pub(crate) mod updates; pub(crate) use history::ContextManager; diff --git a/codex-rs/core/src/context_manager/normalize.rs b/codex-rs/core/src/context_manager/normalize.rs index 0213f7d022c0..c7e08951dd88 100644 --- a/codex-rs/core/src/context_manager/normalize.rs +++ b/codex-rs/core/src/context_manager/normalize.rs @@ -18,6 +18,15 @@ const AUDIO_CONTENT_OMITTED_PLACEHOLDER: &str = // Changing this value would change model-visible IDs and invalidate prompt caches. const SYNTHETIC_OUTPUT_ID_NAMESPACE: Uuid = Uuid::from_u128(0x90d38d3e_6a5b_4d52_bfe2_2f1e634bfac4); +/// Stands in for a tool call that never reported a result, so the payload stays well formed. +/// +/// It says unknown rather than failed on purpose. The call is missing its result because +/// something stopped between dispatch and the write, and neither an interrupt nor a crash can +/// promise the command did not run first, so the model has to check before repeating it. +pub(crate) const MISSING_OUTPUT_PLACEHOLDER: &str = "The outcome of this call is unknown: it started and no \ + result was recorded. It may have taken effect. Check the current state before running it \ + again."; + /// Fill in the output of any tool call that never reported one. /// /// A gap here is expected, not a defect: an interrupt or a crash between dispatching a tool and @@ -62,7 +71,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) ResponseItemEnvelope::new(ResponseItem::FunctionCallOutput { id: synthetic_output_id("fco", id.as_deref()), call_id: call_id.clone(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), )); @@ -95,7 +106,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) id: synthetic_output_id("ctco", id.as_deref()), call_id: call_id.clone(), name: None, - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), )); @@ -112,7 +125,9 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) ResponseItemEnvelope::new(ResponseItem::FunctionCallOutput { id: synthetic_output_id("fco", id.as_deref()), call_id: call_id.clone(), - output: FunctionCallOutputPayload::from_text("aborted".to_string()), + output: FunctionCallOutputPayload::from_text( + MISSING_OUTPUT_PLACEHOLDER.to_string(), + ), internal_chat_message_metadata_passthrough: None, }), ));