From 36b557b2e61ab163408b3b13406c7f4769a31a33 Mon Sep 17 00:00:00 2001 From: chen-985211 <102537086+chen-985211@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:59:16 +0800 Subject: [PATCH] fix(codex): restore cached tool call fields (#4160) * fix(codex): restore cached tool call fields * refactor(codex): merge duplicate enrich loops in chat history enrich_call_item_from_cache copied the fill-if-empty loop for reasoning_content/reasoning. The two loops are identical and key order is irrelevant, so fold both key sets into a single loop. Pure refactor, no behavior change; codex_chat_history tests pass. --------- Co-authored-by: Jason --- src-tauri/src/proxy/forwarder.rs | 2 +- .../src/proxy/providers/codex_chat_history.rs | 61 +++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index e83e2f68e..593b19807 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -1350,7 +1350,7 @@ impl RequestForwarder { .await; if restored > 0 { log::debug!( - "[Codex] Restored {restored} cached function call(s) for Chat upstream" + "[Codex] Restored or enriched {restored} cached function call item(s) for Chat upstream" ); } super::providers::apply_codex_chat_upstream_model(provider, &mut mapped_body); diff --git a/src-tauri/src/proxy/providers/codex_chat_history.rs b/src-tauri/src/proxy/providers/codex_chat_history.rs index d14b52754..783d2be17 100644 --- a/src-tauri/src/proxy/providers/codex_chat_history.rs +++ b/src-tauri/src/proxy/providers/codex_chat_history.rs @@ -150,7 +150,7 @@ impl CodexChatHistoryStore { Some(item_type) if is_call_item_type(item_type) => { if let Some(call_id) = response_item_call_id(&item) { if let Some(cached) = lookup.call(&call_id) { - if enrich_call_item_reasoning(&mut item, cached) { + if enrich_call_item_from_cache(&mut item, cached) { enriched += 1; } } @@ -466,17 +466,26 @@ fn is_call_output_item_type(item_type: &str) -> bool { ) } -fn enrich_call_item_reasoning(item: &mut Value, cached: &Value) -> bool { +fn enrich_call_item_from_cache(item: &mut Value, cached: &Value) -> bool { let mut changed = false; - for key in ["reasoning_content", "reasoning"] { + for key in [ + "name", + "namespace", + "arguments", + "input", + "status", + "execution", + "reasoning_content", + "reasoning", + ] { if item.get(key).is_some_and(|value| !is_empty_value(value)) { continue; } - let Some(reasoning) = cached.get(key).filter(|value| !is_empty_value(value)) else { + let Some(value) = cached.get(key).filter(|value| !is_empty_value(value)) else { continue; }; if let Some(object) = item.as_object_mut() { - object.insert(key.to_string(), reasoning.clone()); + object.insert(key.to_string(), value.clone()); changed = true; } } @@ -675,6 +684,48 @@ mod tests { assert_eq!(input.len(), 2); } + #[tokio::test] + async fn enriches_existing_function_call_missing_name_and_arguments() { + let history = CodexChatHistoryStore::default(); + history + .record_response(&json!({ + "id": "resp_1", + "output": [ + { + "type": "function_call", + "call_id": "call_1", + "name": "read_file", + "arguments": "{\"path\":\"README.md\"}", + "reasoning_content": "Need to inspect the file." + } + ] + })) + .await; + + let mut request = json!({ + "previous_response_id": "resp_1", + "input": [ + { + "type": "function_call", + "call_id": "call_1" + }, + { + "type": "function_call_output", + "call_id": "call_1", + "output": "ok" + } + ] + }); + + assert_eq!(history.enrich_request(&mut request).await, 1); + let input = request["input"].as_array().unwrap(); + assert_eq!(input[0]["type"], "function_call"); + assert_eq!(input[0]["name"], "read_file"); + assert_eq!(input[0]["arguments"], "{\"path\":\"README.md\"}"); + assert_eq!(input[0]["reasoning_content"], "Need to inspect the file."); + assert_eq!(input[1]["type"], "function_call_output"); + } + #[tokio::test] async fn restores_parallel_tool_calls_as_one_assistant_group() { let history = CodexChatHistoryStore::default();