99import json
1010from collections .abc import Mapping
1111from dataclasses import dataclass
12- from typing import TYPE_CHECKING , Any , cast
12+ from typing import TYPE_CHECKING , cast
1313
1414from anthropic .types import (
1515 InputJSONDelta ,
2020 ThinkingBlock ,
2121 ThinkingDelta ,
2222 ToolUseBlock ,
23- WebSearchToolResultBlock ,
2423)
2524
2625from opentelemetry .util .genai .types import (
2726 BlobPart ,
2827 MessagePart ,
2928 ReasoningPart ,
29+ ServerToolCallPart ,
30+ ServerToolCallResponsePart ,
3031 TextPart ,
3132 ToolCallRequestPart ,
3233 ToolCallResponsePart ,
3334)
3435
36+ _SERVER_TOOL_RESULT_TYPES = {
37+ "web_search_tool_result" : "web_search" ,
38+ "web_fetch_tool_result" : "web_fetch" ,
39+ "code_execution_tool_result" : "code_execution" ,
40+ "bash_code_execution_tool_result" : "bash_code_execution" ,
41+ "text_editor_code_execution_tool_result" : "text_editor_code_execution" ,
42+ "tool_search_tool_result" : "tool_search" ,
43+ }
44+
3545if TYPE_CHECKING :
3646 from collections .abc import Iterable
3747
@@ -120,7 +130,7 @@ def _extract_base64_blob(source: object, modality: str) -> BlobPart | None:
120130
121131
122132def _convert_dict_block_to_part (
123- block : Mapping [str , Any ],
133+ block : Mapping [str , object ],
124134) -> MessagePart | None :
125135 """Convert a request-param content block (TypedDict/dict) to a MessagePart."""
126136 block_type = block .get ("type" )
@@ -137,12 +147,42 @@ def _convert_dict_block_to_part(
137147 id = str (block .get ("id" , "" )),
138148 )
139149
150+ if block_type == "server_tool_use" :
151+ name = str (block .get ("name" , "" ))
152+ server_tool_call = {
153+ key : value
154+ for key , value in block .items ()
155+ if key not in ("id" , "name" , "type" )
156+ }
157+ server_tool_call ["type" ] = name
158+ block_id = block .get ("id" )
159+ return ServerToolCallPart (
160+ name = name ,
161+ server_tool_call = server_tool_call ,
162+ id = str (block_id ) if block_id is not None else None ,
163+ )
164+
140165 if block_type == "tool_result" :
141166 return ToolCallResponsePart (
142167 response = block .get ("content" ),
143168 id = str (block .get ("tool_use_id" , "" )),
144169 )
145170
171+ if isinstance (block_type , str ) and (
172+ server_tool_name := _SERVER_TOOL_RESULT_TYPES .get (block_type )
173+ ):
174+ server_tool_call_response = {
175+ key : value
176+ for key , value in block .items ()
177+ if key not in ("tool_use_id" , "type" )
178+ }
179+ server_tool_call_response ["type" ] = server_tool_name
180+ tool_use_id = block .get ("tool_use_id" )
181+ return ServerToolCallResponsePart (
182+ server_tool_call_response = server_tool_call_response ,
183+ id = str (tool_use_id ) if tool_use_id is not None else None ,
184+ )
185+
146186 if block_type in ("thinking" , "redacted_thinking" ):
147187 thinking = block .get ("thinking" ) or block .get ("data" )
148188 return ReasoningPart (
@@ -159,29 +199,32 @@ def _convert_content_block_to_part(
159199 block : ContentBlock | ContentBlockParam ,
160200) -> MessagePart | None :
161201 """Convert an Anthropic content block to a MessagePart."""
202+ if isinstance (block , Mapping ):
203+ return _convert_dict_block_to_part (cast (Mapping [str , object ], block ))
204+
162205 if isinstance (block , TextBlock ):
163206 return TextPart (content = block .text )
164207
165- if isinstance (block , ( ToolUseBlock , ServerToolUseBlock ) ):
208+ if isinstance (block , ToolUseBlock ):
166209 return ToolCallRequestPart (
167210 arguments = block .input , name = block .name , id = block .id
168211 )
169212
213+ if isinstance (block , ServerToolUseBlock ):
214+ return _convert_dict_block_to_part (block .model_dump (exclude_none = True ))
215+
170216 if isinstance (block , (ThinkingBlock , RedactedThinkingBlock )):
171217 content = (
172218 block .thinking if isinstance (block , ThinkingBlock ) else block .data
173219 )
174220 return ReasoningPart (content = content )
175221
176- if isinstance (block , WebSearchToolResultBlock ):
177- return ToolCallResponsePart (
178- response = block .model_dump ().get ("content" ),
179- id = block .tool_use_id ,
222+ if block .type in _SERVER_TOOL_RESULT_TYPES :
223+ return _convert_dict_block_to_part (
224+ cast (Mapping [str , object ], block .model_dump (exclude_none = True ))
180225 )
181226
182- if not hasattr (block , "get" ):
183- return None
184- return _convert_dict_block_to_part (cast (Mapping [str , Any ], block ))
227+ return None
185228
186229
187230def convert_content_to_parts (
0 commit comments