@@ -681,22 +681,27 @@ def _serialize_current_step(self) -> dict[str, Any] | None:
681681 return None
682682
683683 # Interruptions are wrapped in a "data" field
684+ interruptions_data = []
685+ for item in self ._current_step .interruptions :
686+ if isinstance (item , ToolApprovalItem ):
687+ interruption_dict = {
688+ "type" : "tool_approval_item" ,
689+ "rawItem" : self ._camelize_field_names (
690+ item .raw_item .model_dump (exclude_unset = True )
691+ if hasattr (item .raw_item , "model_dump" )
692+ else item .raw_item
693+ ),
694+ "agent" : {"name" : item .agent .name },
695+ }
696+ # Include tool_name if present
697+ if item .tool_name is not None :
698+ interruption_dict ["toolName" ] = item .tool_name
699+ interruptions_data .append (interruption_dict )
700+
684701 return {
685702 "type" : "next_step_interruption" ,
686703 "data" : {
687- "interruptions" : [
688- {
689- "type" : "tool_approval_item" ,
690- "rawItem" : self ._camelize_field_names (
691- item .raw_item .model_dump (exclude_unset = True )
692- if hasattr (item .raw_item , "model_dump" )
693- else item .raw_item
694- ),
695- "agent" : {"name" : item .agent .name },
696- }
697- for item in self ._current_step .interruptions
698- if isinstance (item , ToolApprovalItem )
699- ],
704+ "interruptions" : interruptions_data ,
700705 },
701706 }
702707
@@ -994,8 +999,44 @@ async def from_string(
994999 # Normalize field names from JSON format (camelCase)
9951000 # to Python format (snake_case)
9961001 normalized_raw_item = _normalize_field_names (item_data ["rawItem" ])
997- raw_item = ResponseFunctionToolCall (** normalized_raw_item )
998- approval_item = ToolApprovalItem (agent = agent , raw_item = raw_item )
1002+
1003+ # Extract tool_name if present (for backwards compatibility)
1004+ tool_name = item_data .get ("toolName" )
1005+
1006+ # Tool call items can be function calls, shell calls, apply_patch calls,
1007+ # MCP calls, etc. Check the type field to determine which type to deserialize as
1008+ tool_type = normalized_raw_item .get ("type" )
1009+
1010+ # Try to deserialize based on the type field
1011+ try :
1012+ if tool_type == "function_call" :
1013+ raw_item = ResponseFunctionToolCall (** normalized_raw_item )
1014+ elif tool_type == "shell_call" :
1015+ # Shell calls use dict format, not a specific type
1016+ raw_item = normalized_raw_item # type: ignore[assignment]
1017+ elif tool_type == "apply_patch_call" :
1018+ # Apply patch calls use dict format
1019+ raw_item = normalized_raw_item # type: ignore[assignment]
1020+ elif tool_type == "hosted_tool_call" :
1021+ # MCP/hosted tool calls use dict format
1022+ raw_item = normalized_raw_item # type: ignore[assignment]
1023+ elif tool_type == "local_shell_call" :
1024+ # Local shell calls use dict format
1025+ raw_item = normalized_raw_item # type: ignore[assignment]
1026+ else :
1027+ # Default to trying ResponseFunctionToolCall for backwards compatibility
1028+ try :
1029+ raw_item = ResponseFunctionToolCall (** normalized_raw_item )
1030+ except Exception :
1031+ # If that fails, use dict as-is
1032+ raw_item = normalized_raw_item # type: ignore[assignment]
1033+ except Exception :
1034+ # If deserialization fails, use dict for flexibility
1035+ raw_item = normalized_raw_item # type: ignore[assignment]
1036+
1037+ approval_item = ToolApprovalItem (
1038+ agent = agent , raw_item = raw_item , tool_name = tool_name
1039+ )
9991040 interruptions .append (approval_item )
10001041
10011042 # Import at runtime to avoid circular import
@@ -1172,8 +1213,44 @@ async def from_json(
11721213 # Normalize field names from JSON format (camelCase)
11731214 # to Python format (snake_case)
11741215 normalized_raw_item = _normalize_field_names (item_data ["rawItem" ])
1175- raw_item = ResponseFunctionToolCall (** normalized_raw_item )
1176- approval_item = ToolApprovalItem (agent = agent , raw_item = raw_item )
1216+
1217+ # Extract tool_name if present (for backwards compatibility)
1218+ tool_name = item_data .get ("toolName" )
1219+
1220+ # Tool call items can be function calls, shell calls, apply_patch calls,
1221+ # MCP calls, etc. Check the type field to determine which type to deserialize as
1222+ tool_type = normalized_raw_item .get ("type" )
1223+
1224+ # Try to deserialize based on the type field
1225+ try :
1226+ if tool_type == "function_call" :
1227+ raw_item = ResponseFunctionToolCall (** normalized_raw_item )
1228+ elif tool_type == "shell_call" :
1229+ # Shell calls use dict format, not a specific type
1230+ raw_item = normalized_raw_item # type: ignore[assignment]
1231+ elif tool_type == "apply_patch_call" :
1232+ # Apply patch calls use dict format
1233+ raw_item = normalized_raw_item # type: ignore[assignment]
1234+ elif tool_type == "hosted_tool_call" :
1235+ # MCP/hosted tool calls use dict format
1236+ raw_item = normalized_raw_item # type: ignore[assignment]
1237+ elif tool_type == "local_shell_call" :
1238+ # Local shell calls use dict format
1239+ raw_item = normalized_raw_item # type: ignore[assignment]
1240+ else :
1241+ # Default to trying ResponseFunctionToolCall for backwards compatibility
1242+ try :
1243+ raw_item = ResponseFunctionToolCall (** normalized_raw_item )
1244+ except Exception :
1245+ # If that fails, use dict as-is
1246+ raw_item = normalized_raw_item # type: ignore[assignment]
1247+ except Exception :
1248+ # If deserialization fails, use dict for flexibility
1249+ raw_item = normalized_raw_item # type: ignore[assignment]
1250+
1251+ approval_item = ToolApprovalItem (
1252+ agent = agent , raw_item = raw_item , tool_name = tool_name
1253+ )
11771254 interruptions .append (approval_item )
11781255
11791256 # Import at runtime to avoid circular import
@@ -1575,8 +1652,40 @@ def _deserialize_items(
15751652 result .append (MessageOutputItem (agent = agent , raw_item = raw_item_msg ))
15761653
15771654 elif item_type == "tool_call_item" :
1578- raw_item_tool = ResponseFunctionToolCall (** normalized_raw_item )
1579- result .append (ToolCallItem (agent = agent , raw_item = raw_item_tool ))
1655+ # Tool call items can be function calls, shell calls, apply_patch calls,
1656+ # MCP calls, etc. Check the type field to determine which type to deserialize as
1657+ tool_type = normalized_raw_item .get ("type" )
1658+
1659+ # Try to deserialize based on the type field
1660+ # If deserialization fails, fall back to using the dict as-is
1661+ try :
1662+ if tool_type == "function_call" :
1663+ raw_item_tool = ResponseFunctionToolCall (** normalized_raw_item )
1664+ elif tool_type == "shell_call" :
1665+ # Shell calls use dict format, not a specific type
1666+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1667+ elif tool_type == "apply_patch_call" :
1668+ # Apply patch calls use dict format
1669+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1670+ elif tool_type == "hosted_tool_call" :
1671+ # MCP/hosted tool calls use dict format
1672+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1673+ elif tool_type == "local_shell_call" :
1674+ # Local shell calls use dict format
1675+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1676+ else :
1677+ # Default to trying ResponseFunctionToolCall for backwards compatibility
1678+ try :
1679+ raw_item_tool = ResponseFunctionToolCall (** normalized_raw_item )
1680+ except Exception :
1681+ # If that fails, use dict as-is
1682+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1683+
1684+ result .append (ToolCallItem (agent = agent , raw_item = raw_item_tool ))
1685+ except Exception :
1686+ # If deserialization fails, use dict for flexibility
1687+ raw_item_tool = normalized_raw_item # type: ignore[assignment]
1688+ result .append (ToolCallItem (agent = agent , raw_item = raw_item_tool ))
15801689
15811690 elif item_type == "tool_call_output_item" :
15821691 # For tool call outputs, validate and convert the raw dict
0 commit comments