@@ -161,8 +161,10 @@ pub async fn process_chat_sse<S>(
161161 }
162162
163163 if let Some ( func) = tool_call. get ( "function" ) {
164- if let Some ( fname) = func. get ( "name" ) . and_then ( |n| n. as_str ( ) ) {
165- call_state. name = Some ( fname. to_string ( ) ) ;
164+ if let Some ( fname) = func. get ( "name" ) . and_then ( |n| n. as_str ( ) )
165+ && !fname. is_empty ( )
166+ {
167+ call_state. name . get_or_insert_with ( || fname. to_string ( ) ) ;
166168 }
167169 if let Some ( arguments) = func. get ( "arguments" ) . and_then ( |a| a. as_str ( ) )
168170 {
@@ -432,6 +434,47 @@ mod tests {
432434 ) ;
433435 }
434436
437+ #[ tokio:: test]
438+ async fn preserves_tool_call_name_when_empty_deltas_arrive ( ) {
439+ let delta_with_name = json ! ( {
440+ "choices" : [ {
441+ "delta" : {
442+ "tool_calls" : [ {
443+ "id" : "call_a" ,
444+ "function" : { "name" : "do_a" }
445+ } ]
446+ }
447+ } ]
448+ } ) ;
449+
450+ let delta_with_empty_name = json ! ( {
451+ "choices" : [ {
452+ "delta" : {
453+ "tool_calls" : [ {
454+ "id" : "call_a" ,
455+ "function" : { "name" : "" , "arguments" : "{}" }
456+ } ]
457+ }
458+ } ]
459+ } ) ;
460+
461+ let finish = json ! ( {
462+ "choices" : [ {
463+ "finish_reason" : "tool_calls"
464+ } ]
465+ } ) ;
466+
467+ let body = build_body ( & [ delta_with_name, delta_with_empty_name, finish] ) ;
468+ let events = collect_events ( & body) . await ;
469+ assert_matches ! (
470+ & events[ ..] ,
471+ [
472+ ResponseEvent :: OutputItemDone ( ResponseItem :: FunctionCall { name, arguments, .. } ) ,
473+ ResponseEvent :: Completed { .. }
474+ ] if name == "do_a" && arguments == "{}"
475+ ) ;
476+ }
477+
435478 #[ tokio:: test]
436479 async fn emits_tool_calls_even_when_content_and_reasoning_present ( ) {
437480 let delta_content_and_tools = json ! ( {
0 commit comments