@@ -233,3 +233,153 @@ def test_initialize_multi_agent_existing(session_manager, mock_multi_agent):
233233
234234 # Verify deserialize_state was called with existing state
235235 mock_multi_agent .deserialize_state .assert_called_once_with (existing_state )
236+
237+
238+ def test_fix_broken_tool_use_adds_missing_tool_results (session_manager ):
239+ """Test that _fix_broken_tool_use adds missing toolResult messages."""
240+ # Messages with orphaned toolUse
241+ broken_messages = [
242+ {
243+ "role" : "assistant" ,
244+ "content" : [{"toolUse" : {"toolUseId" : "orphaned-123" , "name" : "test_tool" , "input" : {"input" : "test" }}}],
245+ },
246+ {"role" : "user" , "content" : [{"text" : "Some other message" }]},
247+ ]
248+
249+ fixed_messages = session_manager ._fix_broken_tool_use (broken_messages )
250+
251+ # Should insert toolResult message between toolUse and other message
252+ assert len (fixed_messages ) == 3
253+ assert fixed_messages [1 ]["role" ] == "user"
254+ assert "toolResult" in fixed_messages [1 ]["content" ][0 ]
255+ assert fixed_messages [1 ]["content" ][0 ]["toolResult" ]["toolUseId" ] == "orphaned-123"
256+ assert fixed_messages [1 ]["content" ][0 ]["toolResult" ]["status" ] == "error"
257+ assert fixed_messages [1 ]["content" ][0 ]["toolResult" ]["content" ][0 ]["text" ] == "Tool execution interrupted."
258+
259+
260+ def test_fix_broken_tool_use_extends_partial_tool_results (session_manager ):
261+ """Test fixing messages where some toolResults are missing."""
262+ messages = [
263+ {
264+ "role" : "assistant" ,
265+ "content" : [
266+ {"toolUse" : {"toolUseId" : "complete-123" , "name" : "test_tool" , "input" : {"input" : "test1" }}},
267+ {"toolUse" : {"toolUseId" : "missing-456" , "name" : "test_tool" , "input" : {"input" : "test2" }}},
268+ ],
269+ },
270+ {
271+ "role" : "user" ,
272+ "content" : [
273+ {"toolResult" : {"toolUseId" : "complete-123" , "status" : "success" , "content" : [{"text" : "result" }]}}
274+ ],
275+ },
276+ ]
277+
278+ fixed_messages = session_manager ._fix_broken_tool_use (messages )
279+
280+ # Should add missing toolResult to existing message
281+ assert len (fixed_messages ) == 2
282+ assert len (fixed_messages [1 ]["content" ]) == 2
283+
284+ tool_use_ids = {tr ["toolResult" ]["toolUseId" ] for tr in fixed_messages [1 ]["content" ]}
285+ assert tool_use_ids == {"complete-123" , "missing-456" }
286+
287+ # Check the added toolResult has correct properties
288+ missing_result = next (tr for tr in fixed_messages [1 ]["content" ] if tr ["toolResult" ]["toolUseId" ] == "missing-456" )
289+ assert missing_result ["toolResult" ]["status" ] == "error"
290+ assert missing_result ["toolResult" ]["content" ][0 ]["text" ] == "Tool execution interrupted."
291+
292+
293+ def test_fix_broken_tool_use_handles_multiple_orphaned_tools (session_manager ):
294+ """Test fixing multiple orphaned toolUse messages."""
295+ messages = [
296+ {
297+ "role" : "assistant" ,
298+ "content" : [
299+ {"toolUse" : {"toolUseId" : "orphaned-123" , "name" : "test_tool" , "input" : {"input" : "test1" }}},
300+ {"toolUse" : {"toolUseId" : "orphaned-456" , "name" : "test_tool" , "input" : {"input" : "test2" }}},
301+ ],
302+ },
303+ {"role" : "user" , "content" : [{"text" : "Next message" }]},
304+ ]
305+
306+ fixed_messages = session_manager ._fix_broken_tool_use (messages )
307+
308+ # Should insert message with both toolResults
309+ assert len (fixed_messages ) == 3
310+ assert len (fixed_messages [1 ]["content" ]) == 2
311+
312+ tool_use_ids = {tr ["toolResult" ]["toolUseId" ] for tr in fixed_messages [1 ]["content" ]}
313+ assert tool_use_ids == {"orphaned-123" , "orphaned-456" }
314+
315+
316+ def test_fix_broken_tool_use_preserves_valid_messages (session_manager ):
317+ """Test that valid message sequences are not modified."""
318+ valid_messages = [
319+ {
320+ "role" : "assistant" ,
321+ "content" : [{"toolUse" : {"toolUseId" : "valid-123" , "name" : "test_tool" , "input" : {"input" : "test" }}}],
322+ },
323+ {
324+ "role" : "user" ,
325+ "content" : [
326+ {"toolResult" : {"toolUseId" : "valid-123" , "status" : "success" , "content" : [{"text" : "result" }]}}
327+ ],
328+ },
329+ ]
330+
331+ fixed_messages = session_manager ._fix_broken_tool_use (valid_messages )
332+
333+ # Should remain unchanged
334+ assert fixed_messages == valid_messages
335+
336+
337+ def test_fix_broken_tool_use_ignores_last_message (session_manager ):
338+ """Test that orphaned toolUse in the last message is not fixed."""
339+ messages = [
340+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
341+ {
342+ "role" : "assistant" ,
343+ "content" : [
344+ {"toolUse" : {"toolUseId" : "last-message-123" , "name" : "test_tool" , "input" : {"input" : "test" }}}
345+ ],
346+ },
347+ ]
348+
349+ fixed_messages = session_manager ._fix_broken_tool_use (messages )
350+
351+ # Should remain unchanged since toolUse is in last message
352+ assert fixed_messages == messages
353+
354+
355+ def test_initialize_agent_applies_fix_broken_tool_use (session_manager ):
356+ """Test that initialize applies the fix_broken_tool_use method."""
357+ agent = Agent (agent_id = "test-agent" )
358+
359+ # Create session agent first
360+ session_agent = SessionAgent (
361+ agent_id = "test-agent" , state = {}, conversation_manager_state = SlidingWindowConversationManager ().get_state ()
362+ )
363+ session_manager .session_repository .create_agent ("test-session" , session_agent )
364+
365+ # Add broken messages to session
366+ broken_message = SessionMessage (
367+ message = {
368+ "role" : "assistant" ,
369+ "content" : [{"toolUse" : {"toolUseId" : "broken-123" , "name" : "test_tool" , "input" : {"input" : "test" }}}],
370+ },
371+ message_id = 0 ,
372+ )
373+ session_manager .session_repository .create_message ("test-session" , "test-agent" , broken_message )
374+
375+ follow_up_message = SessionMessage (message = {"role" : "user" , "content" : [{"text" : "Some message" }]}, message_id = 1 )
376+ session_manager .session_repository .create_message ("test-session" , "test-agent" , follow_up_message )
377+
378+ # Initialize agent - should apply fix
379+ session_manager .initialize (agent )
380+
381+ # Check that fix was applied
382+ assert len (agent .messages ) == 3
383+ assert agent .messages [1 ]["role" ] == "user"
384+ assert "toolResult" in agent .messages [1 ]["content" ][0 ]
385+ assert agent .messages [1 ]["content" ][0 ]["toolResult" ]["toolUseId" ] == "broken-123"
0 commit comments