@@ -94,3 +94,41 @@ def test_convert_assistant_message_repairs_history_tool_arguments():
9494
9595 assert blocks [0 ]["type" ] == "tool_use"
9696 assert blocks [0 ]["input" ] == {"path" : "foo.txt" }
97+
98+
99+ def test_anthropic_sanitizes_invalid_tool_ids_consistently ():
100+ """Invalid restored IDs must be valid for Anthropic and keep pairs matched."""
101+ blocks = AnthropicProvider ._assistant_blocks ({
102+ "role" : "assistant" ,
103+ "content" : None ,
104+ "tool_calls" : [{
105+ "id" : "call_abc|rs.same" ,
106+ "function" : {"name" : "read_file" , "arguments" : "{}" },
107+ }],
108+ })
109+ result = AnthropicProvider ._tool_result_block ({
110+ "role" : "tool" ,
111+ "tool_call_id" : "call_abc|rs.same" ,
112+ "content" : "ok" ,
113+ })
114+
115+ tool_id = blocks [0 ]["id" ]
116+ assert tool_id == result ["tool_use_id" ]
117+ assert tool_id != "call_abc|rs.same"
118+ assert all (ch .isalnum () or ch in "_-" for ch in tool_id )
119+
120+
121+ def test_anthropic_sanitized_tool_ids_avoid_simple_collisions ():
122+ """Replacement-only sanitizing would collapse these two ids to call_a."""
123+ blocks = AnthropicProvider ._assistant_blocks ({
124+ "role" : "assistant" ,
125+ "content" : None ,
126+ "tool_calls" : [
127+ {"id" : "call.a" , "function" : {"name" : "a" , "arguments" : "{}" }},
128+ {"id" : "call|a" , "function" : {"name" : "b" , "arguments" : "{}" }},
129+ ],
130+ })
131+
132+ ids = [block ["id" ] for block in blocks if block ["type" ] == "tool_use" ]
133+ assert len (ids ) == len (set (ids )) == 2
134+ assert all (all (ch .isalnum () or ch in "_-" for ch in tool_id ) for tool_id in ids )
0 commit comments