From 6736d1828deb4216376281e52333ac0ece9472ae Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 5 Dec 2025 00:46:56 +0000 Subject: [PATCH] fix: sse for chat (#7594) --- codex-rs/codex-api/src/sse/chat.rs | 47 ++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/codex-rs/codex-api/src/sse/chat.rs b/codex-rs/codex-api/src/sse/chat.rs index 7f50bb634..5e48c57bd 100644 --- a/codex-rs/codex-api/src/sse/chat.rs +++ b/codex-rs/codex-api/src/sse/chat.rs @@ -161,8 +161,10 @@ pub async fn process_chat_sse( } if let Some(func) = tool_call.get("function") { - if let Some(fname) = func.get("name").and_then(|n| n.as_str()) { - call_state.name = Some(fname.to_string()); + if let Some(fname) = func.get("name").and_then(|n| n.as_str()) + && !fname.is_empty() + { + call_state.name.get_or_insert_with(|| fname.to_string()); } if let Some(arguments) = func.get("arguments").and_then(|a| a.as_str()) { @@ -432,6 +434,47 @@ mod tests { ); } + #[tokio::test] + async fn preserves_tool_call_name_when_empty_deltas_arrive() { + let delta_with_name = json!({ + "choices": [{ + "delta": { + "tool_calls": [{ + "id": "call_a", + "function": { "name": "do_a" } + }] + } + }] + }); + + let delta_with_empty_name = json!({ + "choices": [{ + "delta": { + "tool_calls": [{ + "id": "call_a", + "function": { "name": "", "arguments": "{}" } + }] + } + }] + }); + + let finish = json!({ + "choices": [{ + "finish_reason": "tool_calls" + }] + }); + + let body = build_body(&[delta_with_name, delta_with_empty_name, finish]); + let events = collect_events(&body).await; + assert_matches!( + &events[..], + [ + ResponseEvent::OutputItemDone(ResponseItem::FunctionCall { name, arguments, .. }), + ResponseEvent::Completed { .. } + ] if name == "do_a" && arguments == "{}" + ); + } + #[tokio::test] async fn emits_tool_calls_even_when_content_and_reasoning_present() { let delta_content_and_tools = json!({