Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions codex-rs/core/src/context_manager/history_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1925,10 +1925,11 @@ fn normalize_adds_missing_output_for_tool_search_call() {
);
}

#[cfg(debug_assertions)]
/// Code mode dispatches custom tool calls, so a crash between the call and its result is the
/// ordinary way this gap appears. Filling it in has to be quiet, or a resumed thread cannot be
/// sent to the model at all.
#[test]
#[should_panic]
fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {
fn normalize_fills_missing_output_for_custom_tool_call() {
let items = vec![ResponseItem::CustomToolCall {
id: None,
status: None,
Expand All @@ -1940,12 +1941,24 @@ fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {
}];
let mut h = create_history_with_items(items);
h.normalize_history(&default_input_modalities());

let filled = raw_items(&h);
assert_eq!(filled.len(), 2);
let ResponseItem::CustomToolCallOutput {
call_id, output, ..
} = &filled[1]
else {
panic!(
"expected a synthesized custom tool call output, got {:?}",
filled[1]
);
};
assert_eq!(call_id, "tool-x");
assert_eq!(output.body.to_text().as_deref(), Some("aborted"));
}

#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() {
fn normalize_fills_missing_output_for_local_shell_call_with_id() {
let items = vec![ResponseItem::LocalShellCall {
id: None,
call_id: Some("shell-1".to_string()),
Expand Down
14 changes: 8 additions & 6 deletions codex-rs/core/src/context_manager/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ 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);

/// 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
/// writing its result leaves the call alone in the transcript, and a resumed thread has to be
/// able to send that history to the model. Every kind of call is treated the same way for that
/// reason, including the custom tool calls that code mode dispatches.
pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItemEnvelope>) {
let mut function_output_ids = HashSet::new();
let mut tool_search_output_ids = HashSet::new();
Expand Down Expand Up @@ -82,9 +88,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItemEnvelope>)
ResponseItem::CustomToolCall { id, call_id, .. }
if !custom_tool_output_ids.contains(call_id.as_str()) =>
{
error_or_panic(format!(
"Custom tool call output is missing for call id: {call_id}"
));
info!("Custom tool call output is missing for call id: {call_id}");
missing_outputs_to_insert.push((
idx,
ResponseItemEnvelope::new(ResponseItem::CustomToolCallOutput {
Expand All @@ -102,9 +106,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItemEnvelope>)
call_id: Some(call_id),
..
} if !function_output_ids.contains(call_id.as_str()) => {
error_or_panic(format!(
"Local shell call output is missing for call id: {call_id}"
));
info!("Local shell call output is missing for call id: {call_id}");
missing_outputs_to_insert.push((
idx,
ResponseItemEnvelope::new(ResponseItem::FunctionCallOutput {
Expand Down
Loading