Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
class DesktopAgentOsError(BaseException):
class DesktopAgentOsError(Exception):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we wanted to have two types: DesktopAgentOsError for errors that aren’t fixable, and DesktopAgentOsException for errors that are recoverable by the agent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I guess then I'll just close this PR?

"""Base class for Desktop Agent OS errors.

This error is raised when an error occurs in the Desktop Agent OS.

Inherits from `Exception` (not `BaseException`) so that the standard
`except Exception` handlers in the tool-calling loop can catch it and
surface it to the agent as a tool error result instead of crashing.
"""
60 changes: 60 additions & 0 deletions tests/unit/models/shared/test_tool_error_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Tests that tool failures are surfaced to the agent instead of crashing.

When a tool raises, the tool-calling loop is expected to catch the error and
return a `ToolResultBlockParam` with `is_error=True` so the agent can react to
it. This only works if the raised exception derives from `Exception`; a
`BaseException` subclass would slip past the `except Exception` handler and
crash the run instead. `DesktopAgentOsError` (raised e.g. when reading a remote
file/directory that does not exist) must therefore behave like a regular
`Exception`.
"""

from askui.models.shared.agent_message_param import (
ToolResultBlockParam,
ToolUseBlockParam,
)
from askui.models.shared.tools import Tool, ToolCollection
from askui.tools.askui.askui_ui_controller_grpc.desktop_agent_os_error import (
DesktopAgentOsError,
)


class _RaisingTool(Tool):
"""A tool whose `__call__` always raises a `DesktopAgentOsError`."""

def __init__(self) -> None:
super().__init__(
name="raising_tool",
description="Always raises a DesktopAgentOsError.",
)

def __call__(self) -> str:
raise DesktopAgentOsError(self._error_message)

_error_message = (
"directory_iterator::directory_iterator: The system cannot find the "
'path specified.: "FrontEnd\\Traces"'
)


class TestDesktopAgentOsErrorHandling:
def test_desktop_agent_os_error_is_an_exception(self) -> None:
assert issubclass(DesktopAgentOsError, Exception)

def test_raising_tool_returns_error_result_instead_of_crashing(self) -> None:
tool = _RaisingTool()
collection = ToolCollection(tools=[tool])
tool_use = ToolUseBlockParam(
id="tool_use_1",
input={},
name=tool.name,
)

results = collection.run([tool_use])

assert len(results) == 1
result = results[0]
assert isinstance(result, ToolResultBlockParam)
assert result.is_error is True
assert result.tool_use_id == "tool_use_1"
assert "FrontEnd\\Traces" in str(result.content)
Loading