@@ -901,25 +901,11 @@ def _prepare_messages_for_anthropic(self, messages: Sequence[Message]) -> list[d
901901 else :
902902 msgs = list (messages )
903903
904- # A hosted (server tool) call without a result is still running on Anthropic's side, for example
905- # when Claude called a local tool in parallel. It has to be sent back as ``server_tool_use``, or
906- # Anthropic expects a local ``tool_result`` for it.
907- result_call_ids = {
908- content .call_id for msg in msgs for content in msg .contents if content .type == "function_result"
909- }
910- pending_hosted_call_ids = {
911- content .call_id
912- for msg in msgs
913- for content in msg .contents
914- if content .type == "function_call"
915- and content .hosted
916- and content .call_id
917- and content .call_id not in result_call_ids
918- }
904+ pending_hosted_calls = self ._pending_hosted_function_calls (msgs )
919905
920906 result : list [dict [str , Any ]] = []
921907 for msg in msgs :
922- result .extend (self ._prepare_message_groups_for_anthropic (msg , pending_hosted_call_ids ))
908+ result .extend (self ._prepare_message_groups_for_anthropic (msg , pending_hosted_calls ))
923909
924910 # Anthropic requires the conversation to end with a user message.
925911 # Append a synthetic user turn so chained agent outputs work as
@@ -929,11 +915,43 @@ def _prepare_messages_for_anthropic(self, messages: Sequence[Message]) -> list[d
929915
930916 return result
931917
918+ @staticmethod
919+ def _pending_hosted_function_calls (messages : Sequence [Message ]) -> set [int ]:
920+ """Return the identities (``id()``) of hosted function calls that have no result yet.
921+
922+ A hosted (server tool) call without a result is still running on Anthropic's side, for example
923+ when Claude called a local tool in parallel. It has to be sent back as ``server_tool_use``, or
924+ Anthropic expects a local ``tool_result`` for it.
925+
926+ A ``call_id`` is not unique across a transcript: a later occurrence may reuse it. Calls and results
927+ are therefore correlated in transcript order. A result settles the open call occurrence with the
928+ same ``call_id`` whose ``Content.id`` it carries, and otherwise the earliest open occurrence with
929+ that ``call_id``. A result never settles a call that appears after it.
930+ """
931+ open_calls_by_call_id : dict [str , list [Content ]] = {}
932+ for message in messages :
933+ for content in message .contents :
934+ if not content .call_id :
935+ continue
936+ if content .type == "function_call" :
937+ open_calls_by_call_id .setdefault (content .call_id , []).append (content )
938+ elif content .type == "function_result" and (open_calls := open_calls_by_call_id .get (content .call_id )):
939+ settled_index = next (
940+ (
941+ index
942+ for index , call in enumerate (open_calls )
943+ if content .id is not None and call .id == content .id
944+ ),
945+ 0 ,
946+ )
947+ open_calls .pop (settled_index )
948+ return {id (call ) for open_calls in open_calls_by_call_id .values () for call in open_calls if call .hosted }
949+
932950 def _prepare_message_groups_for_anthropic (
933- self , message : Message , pending_hosted_call_ids : Set [str ] | None = None
951+ self , message : Message , pending_hosted_calls : Set [int ] | None = None
934952 ) -> list [dict [str , Any ]]:
935953 """Prepare a Message and split Anthropic content blocks into valid role groups."""
936- prepared_message = self ._prepare_message_for_anthropic (message , pending_hosted_call_ids )
954+ prepared_message = self ._prepare_message_for_anthropic (message , pending_hosted_calls )
937955 content = prepared_message .get ("content" )
938956 if not isinstance (content , list ):
939957 return [prepared_message ]
@@ -983,14 +1001,16 @@ def _message_has_tool_use(self, message: dict[str, Any]) -> bool:
9831001 return False
9841002
9851003 def _prepare_message_for_anthropic (
986- self , message : Message , pending_hosted_call_ids : Set [str ] | None = None
1004+ self , message : Message , pending_hosted_calls : Set [int ] | None = None
9871005 ) -> dict [str , Any ]:
9881006 """Prepare a Message for the Anthropic client.
9891007
9901008 Args:
9911009 message: The Message to convert.
992- pending_hosted_call_ids: Call IDs of hosted (server tool) calls that have no result yet.
993- These are sent as ``server_tool_use`` blocks.
1010+ pending_hosted_calls: Identities (``id()``) of the hosted (server tool) call contents that have
1011+ no result yet, from ``_pending_hosted_function_calls``. These are sent as ``server_tool_use``
1012+ blocks. An identity rather than a ``call_id`` is used because a later occurrence may reuse
1013+ the ``call_id`` of a call that already has its result.
9941014
9951015 Returns:
9961016 A dictionary representing the message in Anthropic format.
@@ -1023,7 +1043,7 @@ def _prepare_message_for_anthropic(
10231043 else :
10241044 logger .debug (f"Ignoring unsupported data content media type: { content .media_type } for now" )
10251045 case "function_call" :
1026- pending_hosted_call = content .hosted and content . call_id in (pending_hosted_call_ids or ())
1046+ pending_hosted_call = content .hosted and id ( content ) in (pending_hosted_calls or ())
10271047 a_content .append ({
10281048 "type" : "server_tool_use" if pending_hosted_call else "tool_use" ,
10291049 "id" : content .call_id ,
0 commit comments