@@ -189,6 +189,7 @@ async def test_executor_stream_yields_unknown_tool(executor, agent, tool_results
189189 tool_use = tool_use ,
190190 invocation_state = invocation_state ,
191191 result = exp_results [0 ],
192+ exception = unittest .mock .ANY ,
192193 )
193194 assert tru_hook_after_event == exp_hook_after_event
194195
@@ -216,6 +217,7 @@ async def test_executor_stream_with_trace(
216217 tracer .end_tool_call_span .assert_called_once_with (
217218 tracer .start_tool_call_span .return_value ,
218219 {"content" : [{"text" : "sunny" }], "status" : "success" , "toolUseId" : "1" },
220+ error = None ,
219221 )
220222
221223 cycle_trace .add_child .assert_called_once ()
@@ -901,3 +903,71 @@ def retry_once_on_unknown(event):
901903 assert len (tru_events ) == 1
902904 assert tru_events [0 ].tool_result ["status" ] == "error"
903905 assert "Unknown tool" in tru_events [0 ].tool_result ["content" ][0 ]["text" ]
906+
907+
908+ @pytest .mark .asyncio
909+ async def test_executor_stream_with_trace_error (
910+ executor , tracer , agent , tool_results , cycle_trace , cycle_span , invocation_state , alist
911+ ):
912+ """Test that _stream_with_trace passes the exception to end_tool_call_span when a tool fails."""
913+ tool_use : ToolUse = {"name" : "exception_tool" , "toolUseId" : "1" , "input" : {}}
914+ stream = executor ._stream_with_trace (agent , tool_use , tool_results , cycle_trace , cycle_span , invocation_state )
915+
916+ await alist (stream )
917+
918+ tracer .end_tool_call_span .assert_called_once ()
919+ call_args = tracer .end_tool_call_span .call_args
920+ assert call_args [0 ][1 ]["status" ] == "error"
921+ error_arg = call_args [1 ].get ("error" )
922+ assert error_arg is not None
923+ assert isinstance (error_arg , RuntimeError )
924+ assert "Tool error" in str (error_arg )
925+
926+
927+ @pytest .mark .asyncio
928+ async def test_executor_stream_error_preserves_exception (executor , agent , tool_results , invocation_state , alist ):
929+ """Test that _stream yields a ToolResultEvent with the exception preserved."""
930+ tool_use : ToolUse = {"name" : "exception_tool" , "toolUseId" : "1" , "input" : {}}
931+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
932+
933+ events = await alist (stream )
934+ result_event = events [- 1 ]
935+ assert isinstance (result_event , ToolResultEvent )
936+ assert result_event .tool_result ["status" ] == "error"
937+ assert result_event .exception is not None
938+ assert isinstance (result_event .exception , RuntimeError )
939+ assert "Tool error" in str (result_event .exception )
940+
941+
942+ @pytest .mark .asyncio
943+ async def test_executor_stream_unknown_tool_has_exception (executor , agent , tool_results , invocation_state , alist ):
944+ """Test that _stream yields a ToolResultEvent with exception for unknown tools."""
945+ tool_use : ToolUse = {"name" : "nonexistent_tool" , "toolUseId" : "1" , "input" : {}}
946+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
947+
948+ events = await alist (stream )
949+ result_event = events [- 1 ]
950+ assert isinstance (result_event , ToolResultEvent )
951+ assert result_event .tool_result ["status" ] == "error"
952+ assert result_event .exception is not None
953+ assert "Unknown tool" in str (result_event .exception )
954+
955+
956+ @pytest .mark .asyncio
957+ async def test_executor_stream_cancel_has_exception (executor , agent , tool_results , invocation_state , alist ):
958+ """Test that _stream yields a ToolResultEvent with exception for cancelled tools."""
959+
960+ def cancel_callback (event ):
961+ event .cancel_tool = True
962+ return event
963+
964+ agent .hooks .add_callback (BeforeToolCallEvent , cancel_callback )
965+ tool_use : ToolUse = {"name" : "weather_tool" , "toolUseId" : "1" , "input" : {}}
966+ stream = executor ._stream (agent , tool_use , tool_results , invocation_state )
967+
968+ events = await alist (stream )
969+ result_event = events [- 1 ]
970+ assert isinstance (result_event , ToolResultEvent )
971+ assert result_event .tool_result ["status" ] == "error"
972+ assert result_event .exception is not None
973+ assert "cancelled" in str (result_event .exception )
0 commit comments