@@ -2215,3 +2215,125 @@ def test_redact_user_content(content, expected):
22152215 agent = Agent ()
22162216 result = agent ._redact_user_content (content , "REDACTED" )
22172217 assert result == expected
2218+
2219+
2220+ def test_agent_fixes_orphaned_tool_use_on_new_prompt (mock_model , agenerator ):
2221+ """Test that agent adds toolResult for orphaned toolUse when called with new prompt."""
2222+ mock_model .mock_stream .return_value = agenerator (
2223+ [
2224+ {"messageStart" : {"role" : "assistant" }},
2225+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2226+ {"contentBlockDelta" : {"delta" : {"text" : "Fixed!" }}},
2227+ {"contentBlockStop" : {}},
2228+ {"messageStop" : {"stopReason" : "end_turn" }},
2229+ ]
2230+ )
2231+
2232+ # Start with orphaned toolUse message
2233+ messages = [
2234+ {
2235+ "role" : "assistant" ,
2236+ "content" : [
2237+ {"toolUse" : {"toolUseId" : "orphaned-123" , "name" : "tool_decorated" , "input" : {"random_string" : "test" }}}
2238+ ],
2239+ }
2240+ ]
2241+
2242+ agent = Agent (model = mock_model , messages = messages )
2243+
2244+ # Call with new prompt should fix orphaned toolUse
2245+ agent ("Continue conversation" )
2246+
2247+ # Should have added toolResult message
2248+ assert len (agent .messages ) >= 3
2249+ assert agent .messages [1 ]["role" ] == "user"
2250+ assert "toolResult" in agent .messages [1 ]["content" ][0 ]
2251+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["toolUseId" ] == "orphaned-123"
2252+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["status" ] == "error"
2253+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["content" ][0 ]["text" ] == "Tool was interrupted."
2254+
2255+
2256+ def test_agent_fixes_multiple_orphaned_tool_uses (mock_model , agenerator ):
2257+ """Test that agent handles multiple orphaned toolUse messages."""
2258+ mock_model .mock_stream .return_value = agenerator (
2259+ [
2260+ {"messageStart" : {"role" : "assistant" }},
2261+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2262+ {"contentBlockDelta" : {"delta" : {"text" : "Fixed multiple!" }}},
2263+ {"contentBlockStop" : {}},
2264+ {"messageStop" : {"stopReason" : "end_turn" }},
2265+ ]
2266+ )
2267+
2268+ messages = [
2269+ {
2270+ "role" : "assistant" ,
2271+ "content" : [
2272+ {
2273+ "toolUse" : {
2274+ "toolUseId" : "orphaned-123" ,
2275+ "name" : "tool_decorated" ,
2276+ "input" : {"random_string" : "test1" },
2277+ }
2278+ },
2279+ {
2280+ "toolUse" : {
2281+ "toolUseId" : "orphaned-456" ,
2282+ "name" : "tool_decorated" ,
2283+ "input" : {"random_string" : "test2" },
2284+ }
2285+ },
2286+ ],
2287+ }
2288+ ]
2289+
2290+ agent = Agent (model = mock_model , messages = messages )
2291+ agent ("Continue" )
2292+
2293+ # Should have toolResult for both toolUse IDs
2294+ tool_results = agent .messages [1 ]["content" ]
2295+ assert len (tool_results ) == 2
2296+ tool_use_ids = {tr ["toolResult" ]["toolUseId" ] for tr in tool_results }
2297+ assert tool_use_ids == {"orphaned-123" , "orphaned-456" }
2298+
2299+ for tool_result in tool_results :
2300+ assert tool_result ["toolResult" ]["status" ] == "error"
2301+ assert tool_result ["toolResult" ]["content" ][0 ]["text" ] == "Tool was interrupted."
2302+
2303+
2304+ def test_agent_skips_fix_for_valid_conversation (mock_model , agenerator ):
2305+ """Test that agent doesn't modify valid toolUse/toolResult pairs."""
2306+ mock_model .mock_stream .return_value = agenerator (
2307+ [
2308+ {"messageStart" : {"role" : "assistant" }},
2309+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2310+ {"contentBlockDelta" : {"delta" : {"text" : "No fix needed!" }}},
2311+ {"contentBlockStop" : {}},
2312+ {"messageStop" : {"stopReason" : "end_turn" }},
2313+ ]
2314+ )
2315+
2316+ # Valid conversation with toolUse followed by toolResult
2317+ messages = [
2318+ {
2319+ "role" : "assistant" ,
2320+ "content" : [
2321+ {"toolUse" : {"toolUseId" : "valid-123" , "name" : "tool_decorated" , "input" : {"random_string" : "test" }}}
2322+ ],
2323+ },
2324+ {
2325+ "role" : "user" ,
2326+ "content" : [
2327+ {"toolResult" : {"toolUseId" : "valid-123" , "status" : "success" , "content" : [{"text" : "result" }]}}
2328+ ],
2329+ },
2330+ ]
2331+
2332+ agent = Agent (model = mock_model , messages = messages )
2333+ original_length = len (agent .messages )
2334+
2335+ agent ("Continue" )
2336+
2337+ # Should not have added any toolResult messages
2338+ # Only the new user message and assistant response should be added
2339+ assert len (agent .messages ) == original_length + 2
0 commit comments