diff --git a/packages/toolbox-adk/src/toolbox_adk/tool.py b/packages/toolbox-adk/src/toolbox_adk/tool.py index 720407bfe..8283496a9 100644 --- a/packages/toolbox-adk/src/toolbox_adk/tool.py +++ b/packages/toolbox-adk/src/toolbox_adk/tool.py @@ -266,17 +266,14 @@ async def run_async( "error": f"OAuth2 Credentials required for {self.name}. A consent link has been generated for the user. Do NOT attempt to run this tool again until the user confirms they have logged in." } - result: Optional[Any] = None - error: Optional[Exception] = None - try: # Execute the core tool - result = await self._core_tool(**args) - return result - + return await self._core_tool(**args) except Exception as e: - error = e - raise e + logging.warning( + "Toolbox tool '%s' execution failed: %s", self.name, e, exc_info=True + ) + return {"error": f"{type(e).__name__}: {e}", "is_error": True} finally: if reset_token: USER_TOKEN_CONTEXT_VAR.reset(reset_token) diff --git a/packages/toolbox-adk/tests/unit/test_tool.py b/packages/toolbox-adk/tests/unit/test_tool.py index 0f3c5120d..1bd262ceb 100644 --- a/packages/toolbox-adk/tests/unit/test_tool.py +++ b/packages/toolbox-adk/tests/unit/test_tool.py @@ -56,6 +56,21 @@ async def test_auth_check_no_token(self): # Should proceed to execute (auth not forced) mock_core.assert_awaited() + @pytest.mark.asyncio + async def test_run_async_returns_error_on_exception(self): + mock_core = AsyncMock(side_effect=RuntimeError("boom")) + mock_core.__name__ = "my_tool" + mock_core.__doc__ = "my description" + + tool = ToolboxTool(mock_core) + ctx = MagicMock() + + result = await tool.run_async({"arg": 1}, ctx) + + assert isinstance(result, dict) and "error" in result + assert result.get("is_error") is True + assert "RuntimeError" in result["error"] + @pytest.mark.asyncio async def test_bind_params(self): mock_core = MagicMock()