@@ -2215,3 +2215,143 @@ 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 ] == {
2250+ "role" : "user" ,
2251+ "content" : [
2252+ {
2253+ "toolResult" : {
2254+ "toolUseId" : "orphaned-123" ,
2255+ "status" : "error" ,
2256+ "content" : [{"text" : "Tool was interrupted." }],
2257+ }
2258+ }
2259+ ],
2260+ }
2261+
2262+
2263+ def test_agent_fixes_multiple_orphaned_tool_uses (mock_model , agenerator ):
2264+ """Test that agent handles multiple orphaned toolUse messages."""
2265+ mock_model .mock_stream .return_value = agenerator (
2266+ [
2267+ {"messageStart" : {"role" : "assistant" }},
2268+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2269+ {"contentBlockDelta" : {"delta" : {"text" : "Fixed multiple!" }}},
2270+ {"contentBlockStop" : {}},
2271+ {"messageStop" : {"stopReason" : "end_turn" }},
2272+ ]
2273+ )
2274+
2275+ messages = [
2276+ {
2277+ "role" : "assistant" ,
2278+ "content" : [
2279+ {
2280+ "toolUse" : {
2281+ "toolUseId" : "orphaned-123" ,
2282+ "name" : "tool_decorated" ,
2283+ "input" : {"random_string" : "test1" },
2284+ }
2285+ },
2286+ {
2287+ "toolUse" : {
2288+ "toolUseId" : "orphaned-456" ,
2289+ "name" : "tool_decorated" ,
2290+ "input" : {"random_string" : "test2" },
2291+ }
2292+ },
2293+ ],
2294+ }
2295+ ]
2296+
2297+ agent = Agent (model = mock_model , messages = messages )
2298+ agent ("Continue" )
2299+
2300+ # Should have toolResult for both toolUse IDs
2301+ assert agent .messages [1 ] == {
2302+ "role" : "user" ,
2303+ "content" : [
2304+ {
2305+ "toolResult" : {
2306+ "toolUseId" : "orphaned-123" ,
2307+ "status" : "error" ,
2308+ "content" : [{"text" : "Tool was interrupted." }],
2309+ }
2310+ },
2311+ {
2312+ "toolResult" : {
2313+ "toolUseId" : "orphaned-456" ,
2314+ "status" : "error" ,
2315+ "content" : [{"text" : "Tool was interrupted." }],
2316+ }
2317+ },
2318+ ],
2319+ }
2320+
2321+
2322+ def test_agent_skips_fix_for_valid_conversation (mock_model , agenerator ):
2323+ """Test that agent doesn't modify valid toolUse/toolResult pairs."""
2324+ mock_model .mock_stream .return_value = agenerator (
2325+ [
2326+ {"messageStart" : {"role" : "assistant" }},
2327+ {"contentBlockStart" : {"start" : {"text" : "" }}},
2328+ {"contentBlockDelta" : {"delta" : {"text" : "No fix needed!" }}},
2329+ {"contentBlockStop" : {}},
2330+ {"messageStop" : {"stopReason" : "end_turn" }},
2331+ ]
2332+ )
2333+
2334+ # Valid conversation with toolUse followed by toolResult
2335+ messages = [
2336+ {
2337+ "role" : "assistant" ,
2338+ "content" : [
2339+ {"toolUse" : {"toolUseId" : "valid-123" , "name" : "tool_decorated" , "input" : {"random_string" : "test" }}}
2340+ ],
2341+ },
2342+ {
2343+ "role" : "user" ,
2344+ "content" : [
2345+ {"toolResult" : {"toolUseId" : "valid-123" , "status" : "success" , "content" : [{"text" : "result" }]}}
2346+ ],
2347+ },
2348+ ]
2349+
2350+ agent = Agent (model = mock_model , messages = messages )
2351+ original_length = len (agent .messages )
2352+
2353+ agent ("Continue" )
2354+
2355+ # Should not have added any toolResult messages
2356+ # Only the new user message and assistant response should be added
2357+ assert len (agent .messages ) == original_length + 2
0 commit comments