@@ -2215,3 +2215,131 @@ 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+ {"messageStart" : {"role" : "assistant" }},
2224+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2225+ {"contentBlockDelta" : {"delta" : {"text" : "Fixed!" }}},
2226+ {"contentBlockStop" : {}},
2227+ {"messageStop" : {"stopReason" : "end_turn" }},
2228+ ])
2229+
2230+ # Start with orphaned toolUse message
2231+ messages = [
2232+ {
2233+ "role" : "assistant" ,
2234+ "content" : [{
2235+ "toolUse" : {
2236+ "toolUseId" : "orphaned-123" ,
2237+ "name" : "tool_decorated" ,
2238+ "input" : {"random_string" : "test" }
2239+ }
2240+ }]
2241+ }
2242+ ]
2243+
2244+ agent = Agent (messages = messages , tools = [tool_decorated ])
2245+
2246+ # Call with new prompt should fix orphaned toolUse
2247+ agent ("Continue conversation" )
2248+
2249+ # Should have added toolResult message
2250+ assert len (agent .messages ) >= 3
2251+ assert agent .messages [1 ]["role" ] == "user"
2252+ assert "toolResult" in agent .messages [1 ]["content" ][0 ]
2253+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["toolUseId" ] == "orphaned-123"
2254+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["status" ] == "error"
2255+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["content" ][0 ]["text" ] == "Tool was interrupted."
2256+
2257+
2258+ def test_agent_fixes_multiple_orphaned_tool_uses (mock_model , agenerator ):
2259+ """Test that agent handles multiple orphaned toolUse messages."""
2260+ mock_model .mock_stream .return_value = agenerator ([
2261+ {"messageStart" : {"role" : "assistant" }},
2262+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2263+ {"contentBlockDelta" : {"delta" : {"text" : "Fixed multiple!" }}},
2264+ {"contentBlockStop" : {}},
2265+ {"messageStop" : {"stopReason" : "end_turn" }},
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 (messages = messages , tools = [tool_decorated ])
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+ {"messageStart" : {"role" : "assistant" }},
2308+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2309+ {"contentBlockDelta" : {"delta" : {"text" : "No fix needed!" }}},
2310+ {"contentBlockStop" : {}},
2311+ {"messageStop" : {"stopReason" : "end_turn" }},
2312+ ])
2313+
2314+ # Valid conversation with toolUse followed by toolResult
2315+ messages = [
2316+ {
2317+ "role" : "assistant" ,
2318+ "content" : [{
2319+ "toolUse" : {
2320+ "toolUseId" : "valid-123" ,
2321+ "name" : "tool_decorated" ,
2322+ "input" : {"random_string" : "test" }
2323+ }
2324+ }]
2325+ },
2326+ {
2327+ "role" : "user" ,
2328+ "content" : [{
2329+ "toolResult" : {
2330+ "toolUseId" : "valid-123" ,
2331+ "status" : "success" ,
2332+ "content" : [{"text" : "result" }]
2333+ }
2334+ }]
2335+ }
2336+ ]
2337+
2338+ agent = Agent (messages = messages , tools = [tool_decorated ])
2339+ original_length = len (agent .messages )
2340+
2341+ agent ("Continue" )
2342+
2343+ # Should not have added any toolResult messages
2344+ # Only the new user message and assistant response should be added
2345+ assert len (agent .messages ) == original_length + 2
0 commit comments