diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 509670f61..749899498 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2289,13 +2289,6 @@ }, "type": "array" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -2437,14 +2430,6 @@ "null" ] }, - "id": { - "description": "Legacy id field retained for compatibility with older payloads.", - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -2482,13 +2467,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -2537,13 +2515,6 @@ "execution": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -2615,13 +2586,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "input": { "type": "string" }, @@ -2758,13 +2722,6 @@ } ] }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -2798,7 +2755,11 @@ { "properties": { "id": { - "type": "string" + "description": "Existing provider ID retained on serialized history for compatibility.", + "type": [ + "string", + "null" + ] }, "metadata": { "anyOf": [ @@ -2831,7 +2792,6 @@ } }, "required": [ - "id", "result", "status", "type" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 99a8a24e3..5b0c59c42 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -14791,13 +14791,6 @@ }, "type": "array" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -14939,14 +14932,6 @@ "null" ] }, - "id": { - "description": "Legacy id field retained for compatibility with older payloads.", - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -14984,13 +14969,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -15039,13 +15017,6 @@ "execution": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -15117,13 +15088,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "input": { "type": "string" }, @@ -15260,13 +15224,6 @@ } ] }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -15300,7 +15257,11 @@ { "properties": { "id": { - "type": "string" + "description": "Existing provider ID retained on serialized history for compatibility.", + "type": [ + "string", + "null" + ] }, "metadata": { "anyOf": [ @@ -15333,7 +15294,6 @@ } }, "required": [ - "id", "result", "status", "type" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index b0399a223..45cfbd95c 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -11236,13 +11236,6 @@ }, "type": "array" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -11384,14 +11377,6 @@ "null" ] }, - "id": { - "description": "Legacy id field retained for compatibility with older payloads.", - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -11429,13 +11414,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -11484,13 +11462,6 @@ "execution": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -11562,13 +11533,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "input": { "type": "string" }, @@ -11705,13 +11669,6 @@ } ] }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -11745,7 +11702,11 @@ { "properties": { "id": { - "type": "string" + "description": "Existing provider ID retained on serialized history for compatibility.", + "type": [ + "string", + "null" + ] }, "metadata": { "anyOf": [ @@ -11778,7 +11739,6 @@ } }, "required": [ - "id", "result", "status", "type" diff --git a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json index 3efca0322..f77a68dc1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -377,13 +377,6 @@ }, "type": "array" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -525,14 +518,6 @@ "null" ] }, - "id": { - "description": "Legacy id field retained for compatibility with older payloads.", - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -570,13 +555,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -625,13 +603,6 @@ "execution": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -703,13 +674,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "input": { "type": "string" }, @@ -846,13 +810,6 @@ } ] }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -886,7 +843,11 @@ { "properties": { "id": { - "type": "string" + "description": "Existing provider ID retained on serialized history for compatibility.", + "type": [ + "string", + "null" + ] }, "metadata": { "anyOf": [ @@ -919,7 +880,6 @@ } }, "required": [ - "id", "result", "status", "type" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json index 093c27044..e09dd2c6f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -448,13 +448,6 @@ }, "type": "array" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -596,14 +589,6 @@ "null" ] }, - "id": { - "description": "Legacy id field retained for compatibility with older payloads.", - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -641,13 +626,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -696,13 +674,6 @@ "execution": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -774,13 +745,6 @@ "call_id": { "type": "string" }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "input": { "type": "string" }, @@ -917,13 +881,6 @@ } ] }, - "id": { - "type": [ - "string", - "null" - ], - "writeOnly": true - }, "metadata": { "anyOf": [ { @@ -957,7 +914,11 @@ { "properties": { "id": { - "type": "string" + "description": "Existing provider ID retained on serialized history for compatibility.", + "type": [ + "string", + "null" + ] }, "metadata": { "anyOf": [ @@ -990,7 +951,6 @@ } }, "required": [ - "id", "result", "status", "type" diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts index 5bd9a1032..f0cd1c8ee 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts @@ -16,4 +16,8 @@ export type ResponseItem = { "type": "message", role: string, content: Array Result<() metadata: None, }, ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, diff --git a/codex-rs/codex-api/src/requests/responses.rs b/codex-rs/codex-api/src/requests/responses.rs index 5f3e1ba87..836f525c4 100644 --- a/codex-rs/codex-api/src/requests/responses.rs +++ b/codex-rs/codex-api/src/requests/responses.rs @@ -17,7 +17,7 @@ pub(crate) fn attach_item_ids(payload_json: &mut Value, original_items: &[Respon }; for (value, item) in items.iter_mut().zip(original_items.iter()) { - if let ResponseItem::Reasoning { id, .. } + if let ResponseItem::Reasoning { id: Some(id), .. } | ResponseItem::Message { id: Some(id), .. } | ResponseItem::WebSearchCall { id: Some(id), .. } | ResponseItem::FunctionCall { id: Some(id), .. } diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 94f7b0132..2d4b221a9 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -878,7 +878,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)), assistant_message("parent unknown phase", /*phase*/ None), ResponseItem::Reasoning { - id: "parent-reasoning".to_string(), + id: Some("parent-reasoning".to_string()), summary: Vec::new(), content: None, encrypted_content: None, diff --git a/codex-rs/core/src/client_common_tests.rs b/codex-rs/core/src/client_common_tests.rs index b71acc398..8107049ab 100644 --- a/codex-rs/core/src/client_common_tests.rs +++ b/codex-rs/core/src/client_common_tests.rs @@ -23,6 +23,7 @@ fn prompt_with_image_outputs() -> Prompt { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "function-call".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -33,6 +34,7 @@ fn prompt_with_image_outputs() -> Prompt { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "custom-call".to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ @@ -69,6 +71,7 @@ fn responses_lite_request_copies_strip_image_details() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "function-call".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -79,6 +82,7 @@ fn responses_lite_request_copies_strip_image_details() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "custom-call".to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index 08ca7efcd..c88d8062d 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -403,20 +403,24 @@ pub(crate) fn trim_function_call_history_to_fit_context_window( fn rewritten_output_for_context_window(item: &ResponseItem) -> Option { Some(match item { ResponseItem::FunctionCallOutput { + id, call_id, output, metadata, } => ResponseItem::FunctionCallOutput { + id: id.clone(), call_id: call_id.clone(), output: truncated_output_payload(output), metadata: metadata.clone(), }, ResponseItem::CustomToolCallOutput { + id, call_id, name, output, metadata, } => ResponseItem::CustomToolCallOutput { + id: id.clone(), call_id: call_id.clone(), name: name.clone(), output: truncated_output_payload(output), @@ -429,6 +433,7 @@ fn rewritten_output_for_context_window(item: &ResponseItem) -> Option ResponseItem::ToolSearchOutput { + id: item.id().map(str::to_string), call_id: call_id.clone(), status: status.clone(), execution: execution.clone(), diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index 4b4071a0d..6c3a93902 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -231,7 +231,10 @@ async fn run_remote_compact_task_inner_impl( ) .await?; let mut input = prompt_input.clone(); - input.push(ResponseItem::CompactionTrigger { metadata: None }); + input.push(ResponseItem::CompactionTrigger { + id: None, + metadata: None, + }); let prompt = Prompt { input, tools: tool_router.model_visible_specs(), @@ -609,11 +612,13 @@ mod tests { metadata: None, }, ResponseItem::Compaction { + id: None, encrypted_content: "old".to_string(), metadata: None, }, ]; let output = ResponseItem::Compaction { + id: None, encrypted_content: "new".to_string(), metadata: None, }; @@ -642,6 +647,7 @@ mod tests { new.clone(), ]; let output = ResponseItem::Compaction { + id: None, encrypted_content: "new".to_string(), metadata: None, }; @@ -673,6 +679,7 @@ mod tests { metadata: None, }]; let output = ResponseItem::Compaction { + id: None, encrypted_content: "new".to_string(), metadata: None, }; @@ -800,6 +807,7 @@ mod tests { #[tokio::test] async fn collect_compaction_output_accepts_additional_output_items() { let compaction = ResponseItem::Compaction { + id: None, encrypted_content: "encrypted".to_string(), metadata: None, }; diff --git a/codex-rs/core/src/compact_tests.rs b/codex-rs/core/src/compact_tests.rs index c1004004a..a09a6dbb3 100644 --- a/codex-rs/core/src/compact_tests.rs +++ b/codex-rs/core/src/compact_tests.rs @@ -654,6 +654,7 @@ fn insert_initial_context_before_last_real_user_or_summary_keeps_summary_last() #[test] fn insert_initial_context_before_last_real_user_or_summary_keeps_compaction_last() { let compacted_history = vec![ResponseItem::Compaction { + id: None, encrypted_content: "encrypted".to_string(), metadata: None, }]; @@ -680,6 +681,7 @@ fn insert_initial_context_before_last_real_user_or_summary_keeps_compaction_last metadata: None, }, ResponseItem::Compaction { + id: None, encrypted_content: "encrypted".to_string(), metadata: None, }, diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 47f37a32f..6ce1c6148 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -339,20 +339,24 @@ impl ContextManager { let policy_with_serialization_budget = policy * 1.2; match item { ResponseItem::FunctionCallOutput { + id, call_id, output, metadata, } => ResponseItem::FunctionCallOutput { + id: id.clone(), call_id: call_id.clone(), output: truncate_function_output_payload(output, policy_with_serialization_budget), metadata: metadata.clone(), }, ResponseItem::CustomToolCallOutput { + id, call_id, name, output, metadata, } => ResponseItem::CustomToolCallOutput { + id: id.clone(), call_id: call_id.clone(), name: name.clone(), output: truncate_function_output_payload(output, policy_with_serialization_budget), diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index bf5bfc3b7..8b9609a25 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -154,6 +154,7 @@ fn reference_context_item() -> TurnContextItem { fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem { ResponseItem::CustomToolCallOutput { + id: None, call_id: call_id.to_string(), name: None, output: FunctionCallOutputPayload::from_text(output.to_string()), @@ -163,7 +164,7 @@ fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem { fn reasoning_msg(text: &str) -> ResponseItem { ResponseItem::Reasoning { - id: String::new(), + id: None, summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "summary".to_string(), }], @@ -177,7 +178,7 @@ fn reasoning_msg(text: &str) -> ResponseItem { fn reasoning_with_encrypted_content(len: usize) -> ResponseItem { ResponseItem::Reasoning { - id: String::new(), + id: None, summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "summary".to_string(), }], @@ -222,7 +223,7 @@ fn filters_non_api_messages() { items, vec![ ResponseItem::Reasoning { - id: String::new(), + id: None, summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "summary".to_string(), }], @@ -409,6 +410,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputText { @@ -430,6 +432,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "tool-1".to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ @@ -476,6 +479,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputText { @@ -497,6 +501,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "tool-1".to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ @@ -544,7 +549,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() { fn for_prompt_preserves_image_generation_calls_when_images_are_supported() { let history = create_history_with_items(vec![ ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "generating".to_string(), revised_prompt: Some("lobster".to_string()), result: "Zm9v".to_string(), @@ -565,7 +570,7 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() { history.for_prompt(&default_input_modalities()), vec![ ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "generating".to_string(), revised_prompt: Some("lobster".to_string()), result: "Zm9v".to_string(), @@ -597,7 +602,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() { metadata: None, }, ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "completed".to_string(), revised_prompt: Some("lobster".to_string()), result: "Zm9v".to_string(), @@ -618,7 +623,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() { metadata: None, }, ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "completed".to_string(), revised_prompt: Some("lobster".to_string()), result: String::new(), @@ -662,6 +667,7 @@ fn remove_first_item_removes_matching_output_for_function_call() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -676,6 +682,7 @@ fn remove_first_item_removes_matching_output_for_function_call() { fn remove_first_item_removes_matching_call_for_output() { let items = vec![ ResponseItem::FunctionCallOutput { + id: None, call_id: "call-2".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -699,6 +706,7 @@ fn replace_last_turn_images_replaces_tool_output_images() { let items = vec![ user_input_text_msg("hi"), ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload { body: FunctionCallOutputBody::ContentItems(vec![ @@ -721,6 +729,7 @@ fn replace_last_turn_images_replaces_tool_output_images() { vec![ user_input_text_msg("hi"), ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload { body: FunctionCallOutputBody::ContentItems(vec![ @@ -771,6 +780,7 @@ fn remove_first_item_handles_local_shell_pair() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-3".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -998,6 +1008,7 @@ fn remove_first_item_handles_custom_tool_pair() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "tool-1".to_string(), name: None, output: FunctionCallOutputPayload::from_text("ok".to_string()), @@ -1026,6 +1037,7 @@ fn normalization_retains_local_shell_outputs() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "shell-1".to_string(), output: FunctionCallOutputPayload::from_text("Total output lines: 1\n\nok".to_string()), metadata: None, @@ -1047,6 +1059,7 @@ fn record_items_truncates_function_call_output_content() { let long_line = "a very long line to trigger truncation\n"; let long_output = long_line.repeat(2_500); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-100".to_string(), output: FunctionCallOutputPayload { body: FunctionCallOutputBody::Text(long_output.clone()), @@ -1086,6 +1099,7 @@ fn record_items_truncates_custom_tool_call_output_content() { let line = "custom output that is very long\n"; let long_output = line.repeat(2_500); let item = ResponseItem::CustomToolCallOutput { + id: None, call_id: "tool-200".to_string(), name: None, output: FunctionCallOutputPayload::from_text(long_output.clone()), @@ -1118,6 +1132,7 @@ fn record_items_respects_custom_token_limit() { let policy = TruncationPolicy::Tokens(10); let long_output = "tokenized content repeated many times ".repeat(200); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-custom-limit".to_string(), output: FunctionCallOutputPayload { body: FunctionCallOutputBody::Text(long_output), @@ -1261,6 +1276,7 @@ fn normalize_adds_missing_output_for_function_call() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-x".to_string(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -1296,6 +1312,7 @@ fn normalize_adds_missing_output_for_custom_tool_call() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "tool-x".to_string(), name: None, output: FunctionCallOutputPayload::from_text("aborted".to_string()), @@ -1342,6 +1359,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "shell-1".to_string(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -1354,6 +1372,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { #[test] fn normalize_removes_orphan_function_call_output() { let items = vec![ResponseItem::FunctionCallOutput { + id: None, call_id: "orphan-1".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -1369,6 +1388,7 @@ fn normalize_removes_orphan_function_call_output() { #[test] fn normalize_removes_orphan_custom_tool_call_output() { let items = vec![ResponseItem::CustomToolCallOutput { + id: None, call_id: "orphan-2".to_string(), name: None, output: FunctionCallOutputPayload::from_text("ok".to_string()), @@ -1396,6 +1416,7 @@ fn normalize_mixed_inserts_and_removals() { }, // Orphan output that should be removed ResponseItem::FunctionCallOutput { + id: None, call_id: "c2".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -1440,6 +1461,7 @@ fn normalize_mixed_inserts_and_removals() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "c1".to_string(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -1453,6 +1475,7 @@ fn normalize_mixed_inserts_and_removals() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "t1".to_string(), name: None, output: FunctionCallOutputPayload::from_text("aborted".to_string()), @@ -1472,6 +1495,7 @@ fn normalize_mixed_inserts_and_removals() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "s1".to_string(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -1504,6 +1528,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-x".to_string(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -1538,6 +1563,7 @@ fn normalize_adds_missing_output_for_tool_search_call() { metadata: None, }, ResponseItem::ToolSearchOutput { + id: None, call_id: Some("search-call-x".to_string()), status: "completed".to_string(), execution: "client".to_string(), @@ -1590,6 +1616,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() #[should_panic] fn normalize_removes_orphan_function_call_output_panics_in_debug() { let items = vec![ResponseItem::FunctionCallOutput { + id: None, call_id: "orphan-1".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -1603,6 +1630,7 @@ fn normalize_removes_orphan_function_call_output_panics_in_debug() { #[should_panic] fn normalize_removes_orphan_custom_tool_call_output_panics_in_debug() { let items = vec![ResponseItem::CustomToolCallOutput { + id: None, call_id: "orphan-2".to_string(), name: None, output: FunctionCallOutputPayload::from_text("ok".to_string()), @@ -1616,6 +1644,7 @@ fn normalize_removes_orphan_custom_tool_call_output_panics_in_debug() { #[test] fn normalize_removes_orphan_client_tool_search_output() { let items = vec![ResponseItem::ToolSearchOutput { + id: None, call_id: Some("orphan-search".to_string()), status: "completed".to_string(), execution: "client".to_string(), @@ -1634,6 +1663,7 @@ fn normalize_removes_orphan_client_tool_search_output() { #[should_panic] fn normalize_removes_orphan_client_tool_search_output_panics_in_debug() { let items = vec![ResponseItem::ToolSearchOutput { + id: None, call_id: Some("orphan-search".to_string()), status: "completed".to_string(), execution: "client".to_string(), @@ -1647,6 +1677,7 @@ fn normalize_removes_orphan_client_tool_search_output_panics_in_debug() { #[test] fn normalize_keeps_server_tool_search_output_without_matching_call() { let items = vec![ResponseItem::ToolSearchOutput { + id: None, call_id: Some("server-search".to_string()), status: "completed".to_string(), execution: "server".to_string(), @@ -1660,6 +1691,7 @@ fn normalize_keeps_server_tool_search_output_without_matching_call() { assert_eq!( h.raw_items(), vec![ResponseItem::ToolSearchOutput { + id: None, call_id: Some("server-search".to_string()), status: "completed".to_string(), execution: "server".to_string(), @@ -1683,6 +1715,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "c2".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -1757,6 +1790,7 @@ fn image_data_url_payload_does_not_dominate_function_call_output_estimate() { let payload = "B".repeat(50_000); let image_url = format!("data:image/png;base64,{payload}"); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-abc".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputText { @@ -1783,6 +1817,7 @@ fn image_data_url_payload_does_not_dominate_custom_tool_call_output_estimate() { let payload = "C".repeat(50_000); let image_url = format!("data:image/png;base64,{payload}"); let item = ResponseItem::CustomToolCallOutput { + id: None, call_id: "call-js-repl".to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ @@ -1818,6 +1853,7 @@ fn non_base64_image_urls_are_unchanged() { metadata: None, }; let function_output_item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -1842,6 +1878,7 @@ fn non_base64_image_urls_are_unchanged() { fn encrypted_function_output_uses_plaintext_byte_estimate() { let encrypted_content = "A".repeat(1_868); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-encrypted".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::EncryptedContent { @@ -1883,6 +1920,7 @@ fn non_image_base64_data_url_is_unchanged() { let payload = "C".repeat(4_096); let image_url = format!("data:application/octet-stream;base64,{payload}"); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-octet".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -1971,6 +2009,7 @@ fn original_detail_images_scale_with_dimensions() { let payload = BASE64_STANDARD.encode(bytes.get_ref()); let image_url = format!("data:image/png;base64,{payload}"); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-original".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -2002,6 +2041,7 @@ fn original_detail_images_are_capped_at_max_patch_count() { let payload = BASE64_STANDARD.encode(bytes.get_ref()); let image_url = format!("data:image/png;base64,{payload}"); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-original-capped".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -2036,6 +2076,7 @@ fn original_detail_webp_images_scale_with_dimensions() { let payload = BASE64_STANDARD.encode(bytes.get_ref()); let image_url = format!("data:image/webp;base64,{payload}"); let item = ResponseItem::FunctionCallOutput { + id: None, call_id: "call-original-webp".to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { diff --git a/codex-rs/core/src/context_manager/normalize.rs b/codex-rs/core/src/context_manager/normalize.rs index 1da58b619..13aacca1e 100644 --- a/codex-rs/core/src/context_manager/normalize.rs +++ b/codex-rs/core/src/context_manager/normalize.rs @@ -47,6 +47,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { missing_outputs_to_insert.push(( idx, ResponseItem::FunctionCallOutput { + id: None, call_id: call_id.clone(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, @@ -61,6 +62,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { missing_outputs_to_insert.push(( idx, ResponseItem::ToolSearchOutput { + id: None, call_id: Some(call_id.clone()), status: "completed".to_string(), execution: "client".to_string(), @@ -78,6 +80,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { missing_outputs_to_insert.push(( idx, ResponseItem::CustomToolCallOutput { + id: None, call_id: call_id.clone(), name: None, output: FunctionCallOutputPayload::from_text("aborted".to_string()), @@ -96,6 +99,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { missing_outputs_to_insert.push(( idx, ResponseItem::FunctionCallOutput { + id: None, call_id: call_id.clone(), output: FunctionCallOutputPayload::from_text("aborted".to_string()), metadata: None, diff --git a/codex-rs/core/src/event_mapping.rs b/codex-rs/core/src/event_mapping.rs index 11e0294c3..9e96ebff5 100644 --- a/codex-rs/core/src/event_mapping.rs +++ b/codex-rs/core/src/event_mapping.rs @@ -178,7 +178,7 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option { }) .collect(); Some(TurnItem::Reasoning(ReasoningItem { - id: id.clone(), + id: id.clone().unwrap_or_default(), summary_text, raw_content, })) @@ -202,7 +202,7 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option { .. } => Some(TurnItem::ImageGeneration( codex_protocol::items::ImageGenerationItem { - id: id.clone(), + id: id.clone()?, status: status.clone(), revised_prompt: revised_prompt.clone(), result: result.clone(), diff --git a/codex-rs/core/src/event_mapping_tests.rs b/codex-rs/core/src/event_mapping_tests.rs index ad5b4c877..b7453c902 100644 --- a/codex-rs/core/src/event_mapping_tests.rs +++ b/codex-rs/core/src/event_mapping_tests.rs @@ -391,7 +391,7 @@ fn parses_agent_message() { #[test] fn parses_reasoning_summary_and_raw_content() { let item = ResponseItem::Reasoning { - id: "reasoning_1".to_string(), + id: Some("reasoning_1".to_string()), summary: vec![ ReasoningItemReasoningSummary::SummaryText { text: "Step 1".to_string(), @@ -424,7 +424,7 @@ fn parses_reasoning_summary_and_raw_content() { #[test] fn parses_reasoning_including_raw_content() { let item = ResponseItem::Reasoning { - id: "reasoning_2".to_string(), + id: Some("reasoning_2".to_string()), summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "Summarized step".to_string(), }], diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index b19cd9d35..d6eb93dd1 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -309,6 +309,7 @@ async fn seed_guardian_parent_history(session: &Arc, turn: &Arc Opti match input { ResponseInputItem::FunctionCallOutput { call_id, output } => { Some(ResponseItem::FunctionCallOutput { + id: None, call_id: call_id.clone(), output: output.clone(), metadata: None, @@ -634,6 +635,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti name, output, } => Some(ResponseItem::CustomToolCallOutput { + id: None, call_id: call_id.clone(), name: name.clone(), output: output.clone(), @@ -642,6 +644,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti ResponseInputItem::McpToolCallOutput { call_id, output } => { let output = output.as_function_call_output_payload(); Some(ResponseItem::FunctionCallOutput { + id: None, call_id: call_id.clone(), output, metadata: None, @@ -653,6 +656,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti execution, tools, } => Some(ResponseItem::ToolSearchOutput { + id: None, call_id: Some(call_id.clone()), status: status.clone(), execution: execution.clone(), diff --git a/codex-rs/core/src/stream_events_utils_tests.rs b/codex-rs/core/src/stream_events_utils_tests.rs index b89d4f5c6..45c5d18e6 100644 --- a/codex-rs/core/src/stream_events_utils_tests.rs +++ b/codex-rs/core/src/stream_events_utils_tests.rs @@ -64,6 +64,7 @@ fn external_context_pollution_items_include_web_search_and_tool_search() { metadata: None, }, ResponseItem::ToolSearchOutput { + id: None, call_id: Some("search-1".to_string()), status: "completed".to_string(), execution: "client".to_string(), @@ -104,6 +105,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, @@ -117,6 +119,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "custom-1".to_string(), name: Some("apply_patch".to_string()), output: FunctionCallOutputPayload::from_text("ok".to_string()), @@ -418,7 +421,7 @@ fn completed_item_keeps_mailbox_delivery_open_for_commentary_messages() { #[test] fn completed_item_defers_mailbox_delivery_for_image_generation_calls() { let item = ResponseItem::ImageGenerationCall { - id: "ig-1".to_string(), + id: Some("ig-1".to_string()), status: "completed".to_string(), revised_prompt: None, result: "Zm9v".to_string(), diff --git a/codex-rs/core/src/thread_manager_tests.rs b/codex-rs/core/src/thread_manager_tests.rs index cec54b5e4..be952bbcb 100644 --- a/codex-rs/core/src/thread_manager_tests.rs +++ b/codex-rs/core/src/thread_manager_tests.rs @@ -76,7 +76,7 @@ fn truncates_before_requested_user_message() { user_msg("u2"), assistant_msg("a3"), ResponseItem::Reasoning { - id: "r1".to_string(), + id: Some("r1".to_string()), summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "s".to_string(), }], diff --git a/codex-rs/core/src/thread_rollout_truncation_tests.rs b/codex-rs/core/src/thread_rollout_truncation_tests.rs index bb6c3ac03..bdbdbc4c7 100644 --- a/codex-rs/core/src/thread_rollout_truncation_tests.rs +++ b/codex-rs/core/src/thread_rollout_truncation_tests.rs @@ -73,7 +73,7 @@ fn truncates_rollout_from_start_before_nth_user_only() { user_msg("u2"), assistant_msg("a3"), ResponseItem::Reasoning { - id: "r1".to_string(), + id: Some("r1".to_string()), summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "s".to_string(), }], diff --git a/codex-rs/core/src/tools/code_mode/delegate.rs b/codex-rs/core/src/tools/code_mode/delegate.rs index 15177944c..7dd661509 100644 --- a/codex-rs/core/src/tools/code_mode/delegate.rs +++ b/codex-rs/core/src/tools/code_mode/delegate.rs @@ -299,6 +299,7 @@ impl CoreTurnHost { self.exec .session .inject_if_running(vec![ResponseItem::CustomToolCallOutput { + id: None, call_id, name: Some(PUBLIC_TOOL_NAME.to_string()), output: FunctionCallOutputPayload::from_text(text), diff --git a/codex-rs/core/src/turn_timing_tests.rs b/codex-rs/core/src/turn_timing_tests.rs index af5a54fec..9f1bcd31b 100644 --- a/codex-rs/core/src/turn_timing_tests.rs +++ b/codex-rs/core/src/turn_timing_tests.rs @@ -149,6 +149,7 @@ fn response_item_records_turn_ttft_ignores_empty_non_output_items() { })); assert!(!response_item_records_turn_ttft( &ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_text("ok".to_string()), metadata: None, diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index c5dfd96cf..54e24d49c 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -611,6 +611,7 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() { RolloutLine { timestamp: "2024-01-01T00:00:02.000Z".to_string(), item: RolloutItem::ResponseItem(ResponseItem::CustomToolCallOutput { + id: None, call_id: "legacy-js-call".to_string(), name: None, output: FunctionCallOutputPayload::from_text("legacy js_repl stdout".to_string()), @@ -751,6 +752,7 @@ async fn resume_replays_image_tool_outputs_with_detail() { RolloutLine { timestamp: "2024-01-01T00:00:01.500Z".to_string(), item: RolloutItem::ResponseItem(ResponseItem::FunctionCallOutput { + id: None, call_id: function_call_id.to_string(), output: FunctionCallOutputPayload::from_content_items(vec![ FunctionCallOutputContentItem::InputImage { @@ -775,6 +777,7 @@ async fn resume_replays_image_tool_outputs_with_detail() { RolloutLine { timestamp: "2024-01-01T00:00:02.500Z".to_string(), item: RolloutItem::ResponseItem(ResponseItem::CustomToolCallOutput { + id: None, call_id: custom_call_id.to_string(), name: None, output: FunctionCallOutputPayload::from_content_items(vec![ @@ -2518,7 +2521,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { let mut prompt = Prompt::default(); prompt.input.push(ResponseItem::Reasoning { - id: "reasoning-id".into(), + id: Some("reasoning-id".into()), summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "summary".into(), }], @@ -2555,6 +2558,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { metadata: None, }); prompt.input.push(ResponseItem::FunctionCallOutput { + id: None, call_id: "function-call-id".into(), output: FunctionCallOutputPayload::from_text("ok".into()), metadata: None, @@ -2581,6 +2585,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { metadata: None, }); prompt.input.push(ResponseItem::CustomToolCallOutput { + id: None, call_id: "custom-tool-call-id".into(), name: None, output: FunctionCallOutputPayload::from_text("ok".into()), diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index cff88f57a..3b709d54a 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -1995,6 +1995,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() { metadata: None, }, codex_protocol::models::ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, @@ -3994,6 +3995,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() { metadata: None, }, codex_protocol::models::ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, @@ -4121,6 +4123,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() { metadata: None, }, codex_protocol::models::ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index f9b35f686..365cf35b0 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -157,6 +157,7 @@ fn format_labeled_requests_snapshot( fn compacted_summary_only_output(summary: &str) -> Vec { vec![ResponseItem::Compaction { + id: None, encrypted_content: summary_with_prefix(summary), metadata: None, }] @@ -329,6 +330,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { .await; let compacted_history = vec![ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }]; @@ -2356,6 +2358,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> let compacted_history = vec![ ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, @@ -2511,6 +2514,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res metadata: None, }, ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, @@ -2653,6 +2657,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() metadata: None, }, ResponseItem::Compaction { + id: None, encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), metadata: None, }, @@ -4059,6 +4064,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject .await; let compacted_history = vec![ResponseItem::Compaction { + id: None, encrypted_content: summary_with_prefix("REMOTE_SUMMARY_ONLY"), metadata: None, }]; diff --git a/codex-rs/ext/image-generation/src/tests.rs b/codex-rs/ext/image-generation/src/tests.rs index 449c7beb0..e2f5ac666 100644 --- a/codex-rs/ext/image-generation/src/tests.rs +++ b/codex-rs/ext/image-generation/src/tests.rs @@ -87,6 +87,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() { metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "mcp-call".to_string(), output: image_output("mcp"), metadata: None, @@ -100,19 +101,21 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() { metadata: None, }, ResponseItem::CustomToolCallOutput { + id: None, call_id: "code-mode-call".to_string(), name: Some("exec".to_string()), output: image_output("code-mode"), metadata: None, }, ResponseItem::ImageGenerationCall { - id: "generated-call".to_string(), + id: Some("generated-call".to_string()), status: "completed".to_string(), revised_prompt: None, result: "generated".to_string(), metadata: None, }, ResponseItem::FunctionCallOutput { + id: None, call_id: "orphan-call".to_string(), output: image_output("orphan"), metadata: None, diff --git a/codex-rs/memories/write/src/phase1.rs b/codex-rs/memories/write/src/phase1.rs index 775e89887..026826b7c 100644 --- a/codex-rs/memories/write/src/phase1.rs +++ b/codex-rs/memories/write/src/phase1.rs @@ -742,6 +742,7 @@ mod tests { let serialized = job::serialize_filtered_rollout_response_items(&[RolloutItem::ResponseItem( ResponseItem::FunctionCallOutput { + id: None, call_id: "call_123".to_string(), output: codex_protocol::models::FunctionCallOutputPayload { body: codex_protocol::models::FunctionCallOutputBody::Text( diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 9443866c6..3c0753cb4 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -920,6 +920,7 @@ pub enum ResponseItem { Message { #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, role: String, content: Vec, @@ -934,6 +935,10 @@ pub enum ResponseItem { metadata: Option, }, AgentMessage { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, author: String, recipient: String, content: Vec, @@ -945,7 +950,7 @@ pub enum ResponseItem { #[serde(default, skip_serializing)] #[ts(skip)] #[schemars(skip)] - id: String, + id: Option, summary: Vec, #[serde(default, skip_serializing_if = "should_serialize_reasoning_content")] #[ts(optional)] @@ -959,6 +964,7 @@ pub enum ResponseItem { /// Legacy id field retained for compatibility with older payloads. #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, /// Set when using the Responses API. call_id: Option, @@ -971,6 +977,7 @@ pub enum ResponseItem { FunctionCall { #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, name: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -988,6 +995,7 @@ pub enum ResponseItem { ToolSearchCall { #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, call_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1006,6 +1014,10 @@ pub enum ResponseItem { // - an array of structured content items (`content_items`) // We keep this behavior centralized in `FunctionCallOutputPayload`. FunctionCallOutput { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, call_id: String, #[ts(as = "FunctionCallOutputBody")] #[schemars(with = "FunctionCallOutputBody")] @@ -1017,6 +1029,7 @@ pub enum ResponseItem { CustomToolCall { #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -1033,6 +1046,10 @@ pub enum ResponseItem { // `function_call_output.output` so freeform tools can return either plain // text or structured content items. CustomToolCallOutput { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, call_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -1045,6 +1062,10 @@ pub enum ResponseItem { metadata: Option, }, ToolSearchOutput { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, call_id: Option, status: String, execution: String, @@ -1065,6 +1086,7 @@ pub enum ResponseItem { WebSearchCall { #[serde(default, skip_serializing)] #[ts(skip)] + #[schemars(skip)] id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -1086,7 +1108,10 @@ pub enum ResponseItem { // "result":"..." // } ImageGenerationCall { - id: String, + /// Existing provider ID retained on serialized history for compatibility. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + id: Option, status: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -1098,17 +1123,29 @@ pub enum ResponseItem { }, #[serde(alias = "compaction_summary")] Compaction { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, encrypted_content: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] metadata: Option, }, CompactionTrigger { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] metadata: Option, }, ContextCompaction { + #[serde(default, skip_serializing)] + #[ts(skip)] + #[schemars(skip)] + id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] encrypted_content: Option, @@ -1126,6 +1163,50 @@ impl ResponseItem { matches!(self, Self::Message { role, .. } if role == "user") } + /// Returns the non-empty Responses API item ID, if present. + pub fn id(&self) -> Option<&str> { + match self { + Self::Message { id, .. } + | Self::AgentMessage { id, .. } + | Self::LocalShellCall { id, .. } + | Self::FunctionCall { id, .. } + | Self::ToolSearchCall { id, .. } + | Self::FunctionCallOutput { id, .. } + | Self::CustomToolCall { id, .. } + | Self::CustomToolCallOutput { id, .. } + | Self::ToolSearchOutput { id, .. } + | Self::WebSearchCall { id, .. } + | Self::Reasoning { id, .. } + | Self::ImageGenerationCall { id, .. } + | Self::Compaction { id, .. } + | Self::CompactionTrigger { id, .. } + | Self::ContextCompaction { id, .. } => id.as_deref().filter(|id| !id.is_empty()), + Self::Other => None, + } + } + + /// Sets the Responses API item ID for variants that carry one. + pub fn set_id(&mut self, new_id: String) { + match self { + Self::Message { id, .. } + | Self::AgentMessage { id, .. } + | Self::LocalShellCall { id, .. } + | Self::FunctionCall { id, .. } + | Self::ToolSearchCall { id, .. } + | Self::FunctionCallOutput { id, .. } + | Self::CustomToolCall { id, .. } + | Self::CustomToolCallOutput { id, .. } + | Self::ToolSearchOutput { id, .. } + | Self::WebSearchCall { id, .. } + | Self::Reasoning { id, .. } + | Self::ImageGenerationCall { id, .. } + | Self::Compaction { id, .. } + | Self::CompactionTrigger { id, .. } + | Self::ContextCompaction { id, .. } => *id = Some(new_id), + Self::Other => {} + } + } + /// Returns the non-empty turn ID stamped onto this item, if present. pub fn turn_id(&self) -> Option<&str> { self.metadata() @@ -1168,7 +1249,7 @@ impl ResponseItem { | Self::WebSearchCall { metadata, .. } | Self::ImageGenerationCall { metadata, .. } | Self::Compaction { metadata, .. } - | Self::CompactionTrigger { metadata } + | Self::CompactionTrigger { metadata, .. } | Self::ContextCompaction { metadata, .. } => metadata.as_ref(), Self::Other => None, } @@ -1189,7 +1270,7 @@ impl ResponseItem { | Self::WebSearchCall { metadata, .. } | Self::ImageGenerationCall { metadata, .. } | Self::Compaction { metadata, .. } - | Self::CompactionTrigger { metadata } + | Self::CompactionTrigger { metadata, .. } | Self::ContextCompaction { metadata, .. } => Some(metadata), Self::Other => None, } @@ -1435,6 +1516,7 @@ impl From for ResponseItem { metadata: None, }, ResponseInputItem::FunctionCallOutput { call_id, output } => Self::FunctionCallOutput { + id: None, call_id, output, metadata: None, @@ -1442,6 +1524,7 @@ impl From for ResponseItem { ResponseInputItem::McpToolCallOutput { call_id, output } => { let output = output.into_function_call_output_payload(); Self::FunctionCallOutput { + id: None, call_id, output, metadata: None, @@ -1452,6 +1535,7 @@ impl From for ResponseItem { name, output, } => Self::CustomToolCallOutput { + id: None, call_id, name, output, @@ -1467,6 +1551,7 @@ impl From for ResponseItem { status, execution, tools, + id: None, metadata: None, }, } @@ -2060,6 +2145,16 @@ mod tests { Ok(()) } + #[test] + fn response_item_id_getter_and_setter() { + let mut item = response_item_with_metadata(/*metadata*/ None); + assert_eq!(item.id(), None); + + item.set_id("msg_test".to_string()); + + assert_eq!(item.id(), Some("msg_test")); + } + fn response_item_with_metadata(metadata: Option) -> ResponseItem { ResponseItem::Message { id: None, @@ -2176,7 +2271,7 @@ mod tests { assert_eq!( item, ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "completed".to_string(), revised_prompt: Some("A small blue square".to_string()), result: "Zm9v".to_string(), @@ -2198,7 +2293,7 @@ mod tests { assert_eq!( item, ResponseItem::ImageGenerationCall { - id: "ig_123".to_string(), + id: Some("ig_123".to_string()), status: "completed".to_string(), revised_prompt: None, result: "Zm9v".to_string(), @@ -2900,6 +2995,7 @@ mod tests { assert_eq!( item, ResponseItem::Compaction { + id: None, encrypted_content: "abc".into(), metadata: None, } @@ -2916,6 +3012,7 @@ mod tests { assert_eq!( item, ResponseItem::ContextCompaction { + id: None, encrypted_content: Some("abc".into()), metadata: None, } @@ -2925,7 +3022,10 @@ mod tests { #[test] fn serializes_compaction_trigger_without_payload() -> Result<()> { - let item = ResponseItem::CompactionTrigger { metadata: None }; + let item = ResponseItem::CompactionTrigger { + id: None, + metadata: None, + }; assert_eq!( serde_json::to_value(item)?, @@ -2938,7 +3038,10 @@ mod tests { #[test] fn serializes_stamped_compaction_trigger_metadata() -> Result<()> { - let mut item = ResponseItem::CompactionTrigger { metadata: None }; + let mut item = ResponseItem::CompactionTrigger { + id: None, + metadata: None, + }; item.stamp_turn_id_if_missing("turn-1"); assert_eq!( @@ -2959,7 +3062,13 @@ mod tests { let item: ResponseItem = serde_json::from_str(json)?; - assert_eq!(item, ResponseItem::CompactionTrigger { metadata: None }); + assert_eq!( + item, + ResponseItem::CompactionTrigger { + id: None, + metadata: None, + } + ); Ok(()) } @@ -3188,6 +3297,7 @@ mod tests { assert_eq!( ResponseItem::from(input.clone()), ResponseItem::ToolSearchOutput { + id: None, call_id: Some("search-1".to_string()), status: "completed".to_string(), execution: "client".to_string(), @@ -3275,6 +3385,7 @@ mod tests { assert_eq!( parsed_output, ResponseItem::ToolSearchOutput { + id: None, call_id: None, status: "completed".to_string(), execution: "server".to_string(), diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 0a9271c9f..a0e5b24d4 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -762,6 +762,7 @@ impl InterAgentCommunication { }], }; ResponseItem::AgentMessage { + id: None, author: self.author.to_string(), recipient: self.recipient.to_string(), content, @@ -4284,6 +4285,7 @@ mod tests { assert_eq!( communication.to_model_input_item(), ResponseItem::AgentMessage { + id: None, author: "/root/worker".to_string(), recipient: "/root".to_string(), content: vec![ diff --git a/codex-rs/rollout-trace/src/inference.rs b/codex-rs/rollout-trace/src/inference.rs index f826d9f47..7329fd3cf 100644 --- a/codex-rs/rollout-trace/src/inference.rs +++ b/codex-rs/rollout-trace/src/inference.rs @@ -496,7 +496,7 @@ mod tests { #[test] fn traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer() { let item = ResponseItem::Reasoning { - id: "rs-1".to_string(), + id: Some("rs-1".to_string()), summary: vec![ReasoningItemReasoningSummary::SummaryText { text: "summary".to_string(), }], diff --git a/codex-rs/thread-store/src/local/mod.rs b/codex-rs/thread-store/src/local/mod.rs index 550e7d56b..19a0d2955 100644 --- a/codex-rs/thread-store/src/local/mod.rs +++ b/codex-rs/thread-store/src/local/mod.rs @@ -510,6 +510,7 @@ mod tests { memory_citation: None, })), RolloutItem::ResponseItem(ResponseItem::FunctionCallOutput { + id: None, call_id: "call-1".to_string(), output: FunctionCallOutputPayload::from_text("tool output".to_string()), metadata: None,