@@ -106,6 +106,46 @@ def _set_llm_session_id(
106106 _apply_session_identity (llm_invocation , session_id )
107107
108108
109+ def _normalize_model (model : Any ) -> Optional [str ]:
110+ if model is None :
111+ return None
112+ model_text = str (model ).strip ()
113+ return model_text or None
114+
115+
116+ def _is_unknown_model (model : Any ) -> bool :
117+ model_text = _normalize_model (model )
118+ return model_text is None or model_text == "unknown"
119+
120+
121+ def _resolve_assistant_model (msg : Any , fallback_model : str ) -> str :
122+ """Use AssistantMessage.model when options/env did not provide a model."""
123+ normalized_fallback = _normalize_model (fallback_model ) or "unknown"
124+ message_model = _normalize_model (getattr (msg , "model" , None ))
125+ if _is_unknown_model (normalized_fallback ) and message_model :
126+ return message_model
127+ return normalized_fallback
128+
129+
130+ def _apply_assistant_model (
131+ agent_invocation : InvokeAgentInvocation , model : str
132+ ) -> None :
133+ """Late-fill the agent request model once the SDK reveals its default."""
134+ if _is_unknown_model (
135+ agent_invocation .request_model
136+ ) and not _is_unknown_model (model ):
137+ agent_invocation .request_model = model
138+
139+
140+ def _extract_model_from_result_message (msg : Any ) -> Optional [str ]:
141+ model_usage = getattr (msg , "model_usage" , None )
142+ if not isinstance (model_usage , dict ) or not model_usage :
143+ return None
144+ if len (model_usage ) == 1 :
145+ return _normalize_model (next (iter (model_usage )))
146+ return None
147+
148+
109149def _clear_client_managed_runs (
110150 handler : ExtendedTelemetryHandler ,
111151 client_managed_runs : Dict [str , ExecuteToolInvocation ],
@@ -482,6 +522,9 @@ def _process_assistant_message(
482522 cwd : Optional [str ] = None ,
483523) -> None :
484524 """Process AssistantMessage: create LLM turn, extract parts, create tool spans."""
525+ message_model = _resolve_assistant_model (msg , model )
526+ _apply_assistant_model (agent_invocation , message_model )
527+
485528 parts = _extract_message_parts (msg )
486529 has_text_content = any (isinstance (p , Text ) for p in parts )
487530 has_tool_calls = any (isinstance (p , ToolCall ) for p in parts )
@@ -494,10 +537,11 @@ def _process_assistant_message(
494537 turn_tracker .close_llm_turn ()
495538
496539 message_arrival_time = time .time ()
540+ finish_reason = "tool_calls" if has_tool_calls else "stop"
497541
498542 turn_tracker .start_llm_turn (
499543 msg ,
500- model ,
544+ message_model ,
501545 prompt ,
502546 collected_messages ,
503547 provider = infer_provider_from_base_url (),
@@ -506,9 +550,11 @@ def _process_assistant_message(
506550 )
507551
508552 if parts :
509- turn_tracker .add_assistant_output (parts )
553+ turn_tracker .add_assistant_output (parts , finish_reason )
510554 output_msg = OutputMessage (
511- role = "assistant" , parts = list (parts ), finish_reason = "stop"
555+ role = "assistant" ,
556+ parts = list (parts ),
557+ finish_reason = finish_reason ,
512558 )
513559 agent_invocation .output_messages .append (output_msg )
514560
@@ -527,14 +573,8 @@ def _process_assistant_message(
527573 last_output_msg .parts .extend (parts )
528574 last_output_msg .finish_reason = "tool_calls"
529575 else :
530- turn_tracker .add_assistant_output (parts )
531- output_msg = OutputMessage (
532- role = "assistant" ,
533- parts = list (parts ),
534- finish_reason = "tool_calls" ,
535- )
536- turn_tracker .current_llm_invocation .output_messages .append (
537- output_msg
576+ turn_tracker .add_assistant_output (
577+ parts , finish_reason = "tool_calls"
538578 )
539579
540580 # Only add to collected_messages if not inside a Task
@@ -557,7 +597,8 @@ def _process_assistant_message(
557597 {"role" : "assistant" , "parts" : list (parts )}
558598 )
559599
560- # Close LLM turn before creating tool spans to ensure correct timeline
600+ # Close an existing LLM turn before creating tool spans to keep tool spans
601+ # under the agent context and avoid leaking the LLM context into tools.
561602 if has_tool_calls and turn_tracker .current_llm_invocation :
562603 turn_tracker .close_llm_turn ()
563604
@@ -749,25 +790,27 @@ def _process_user_message(
749790def _process_system_message (
750791 msg : Any ,
751792 agent_invocation : InvokeAgentInvocation ,
752- ) -> Optional [str ]:
793+ ) -> tuple [ Optional [str ], Optional [ str ] ]:
753794 """Process SystemMessage: extract session_id and cwd early in the stream.
754795
755796 SystemMessage appears at the beginning of the message stream and contains
756797 the session_id and cwd in its data field. We extract them here so they are
757798 available for all subsequent spans (cwd is needed to locate project-level
758799 SKILL.md files for Skill tool telemetry).
759800
760- Returns the cwd if present, otherwise ``None`` .
801+ Returns ``( cwd, model)`` when present .
761802 """
762803 if hasattr (msg , "subtype" ) and msg .subtype == "init" :
763804 if hasattr (msg , "data" ) and isinstance (msg .data , dict ):
764805 session_id = msg .data .get ("session_id" )
765806 if session_id :
766807 _set_session_id (agent_invocation , session_id )
767808 cwd = msg .data .get ("cwd" )
768- if cwd :
769- return str (cwd )
770- return None
809+ model = _normalize_model (msg .data .get ("model" ))
810+ if model :
811+ _apply_assistant_model (agent_invocation , model )
812+ return str (cwd ) if cwd else None , model
813+ return None , None
771814
772815
773816def _process_stream_event_message (
@@ -795,6 +838,11 @@ def _process_result_message(
795838) -> None :
796839 """Process ResultMessage: update session_id (fallback), token usage, and close any open LLM turn."""
797840
841+ result_model = _extract_model_from_result_message (msg )
842+ if result_model :
843+ _apply_assistant_model (agent_invocation , result_model )
844+ turn_tracker .apply_model (result_model )
845+
798846 _set_session_id (agent_invocation , getattr (msg , "session_id" , None ))
799847 turn_tracker .set_session_id (agent_invocation .conversation_id )
800848 _update_token_usage (agent_invocation , turn_tracker , msg )
@@ -846,6 +894,7 @@ async def _process_agent_invocation_stream(
846894 # cwd captured from SystemMessage.data.cwd, used to locate project-level
847895 # SKILL.md files for Skill tool telemetry.
848896 session_cwd : Optional [str ] = None
897+ current_model = model
849898 agent_closed = False
850899
851900 def close_agent_successfully () -> None :
@@ -854,20 +903,30 @@ def close_agent_successfully() -> None:
854903 handler .stop_invoke_agent (agent_invocation )
855904 agent_closed = True
856905
906+ def fail_agent (error : Error ) -> None :
907+ nonlocal agent_closed
908+ if not agent_closed :
909+ handler .fail_invoke_agent (agent_invocation , error = error )
910+ agent_closed = True
911+
857912 try :
858913 async for msg in wrapped_stream :
859914 msg_type = type (msg ).__name__
860915
861916 if msg_type == "SystemMessage" :
862- cwd = _process_system_message (msg , agent_invocation )
917+ cwd , system_model = _process_system_message (
918+ msg , agent_invocation
919+ )
863920 if cwd :
864921 session_cwd = cwd
922+ if system_model and _is_unknown_model (current_model ):
923+ current_model = system_model
865924 elif msg_type == "StreamEvent" :
866925 _process_stream_event_message (msg , agent_invocation )
867926 elif msg_type == "AssistantMessage" :
868927 _process_assistant_message (
869928 msg ,
870- model ,
929+ current_model ,
871930 prompt ,
872931 agent_invocation ,
873932 turn_tracker ,
@@ -896,17 +955,15 @@ def close_agent_successfully() -> None:
896955
897956 except BaseException as e :
898957 error_msg = str (e )
958+ error = Error (message = error_msg , type = type (e ))
899959 if not agent_closed :
960+ turn_tracker .fail_llm_turn (error )
900961 if agent_invocation .span :
901962 agent_invocation .span .set_attribute (
902963 "error.type" , type (e ).__name__
903964 )
904965 agent_invocation .span .set_attribute ("error.message" , error_msg )
905- handler .fail_invoke_agent (
906- agent_invocation ,
907- error = Error (message = error_msg , type = type (e )),
908- )
909- agent_closed = True
966+ fail_agent (error )
910967
911968 raise
912969 finally :
@@ -1018,13 +1075,15 @@ def start_llm_turn(
10181075 self .current_llm_invocation = llm_invocation
10191076 return llm_invocation
10201077
1021- def add_assistant_output (self , parts : List [Any ]) -> None :
1078+ def add_assistant_output (
1079+ self , parts : List [Any ], finish_reason : str = "stop"
1080+ ) -> None :
10221081 """Add output message parts to current LLM invocation."""
10231082 if not self .current_llm_invocation or not parts :
10241083 return
10251084
10261085 output_msg = OutputMessage (
1027- role = "assistant" , parts = list (parts ), finish_reason = "stop"
1086+ role = "assistant" , parts = list (parts ), finish_reason = finish_reason
10281087 )
10291088 self .current_llm_invocation .output_messages .append (output_msg )
10301089
@@ -1053,13 +1112,32 @@ def set_session_id(self, session_id: Optional[str]) -> None:
10531112 if target_invocation :
10541113 _set_llm_session_id (target_invocation , session_id )
10551114
1115+ def apply_model (self , model : str ) -> None :
1116+ """Update an open or recently closed LLM invocation model if unknown."""
1117+ target_invocation = (
1118+ self .current_llm_invocation or self .last_closed_llm_invocation
1119+ )
1120+ if (
1121+ target_invocation
1122+ and _is_unknown_model (target_invocation .request_model )
1123+ and not _is_unknown_model (model )
1124+ ):
1125+ target_invocation .request_model = model
1126+
10561127 def close_llm_turn (self ) -> None :
10571128 """Close the current LLM invocation span."""
10581129 if self .current_llm_invocation :
10591130 self .handler .stop_llm (self .current_llm_invocation )
10601131 self .last_closed_llm_invocation = self .current_llm_invocation
10611132 self .current_llm_invocation = None
10621133
1134+ def fail_llm_turn (self , error : Error ) -> None :
1135+ """Fail the current LLM invocation span."""
1136+ if self .current_llm_invocation :
1137+ self .handler .fail_llm (self .current_llm_invocation , error )
1138+ self .last_closed_llm_invocation = self .current_llm_invocation
1139+ self .current_llm_invocation = None
1140+
10631141 def close (self ) -> None :
10641142 """Close any open LLM invocation (cleanup fallback)."""
10651143 if self .current_llm_invocation :
0 commit comments