Skip to main content

computer_use/
error.rs

1//! Error type shared by the library and the CLI JSON envelope.
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// Failure from argument checking or desktop automation.
7#[derive(Debug, Error)]
8pub enum ComputerError {
9    /// The action or flag value cannot be used.
10    #[error("invalid arguments: {0}")]
11    InvalidArguments(String),
12
13    /// The operating system or a backend call failed.
14    #[error("execution failed: {0}")]
15    ExecutionFailed(String),
16
17    /// A filesystem or process call failed.
18    #[error("io error: {0}")]
19    Io(#[from] std::io::Error),
20
21    /// The platform does not implement this operation.
22    #[error("unsupported: {0}")]
23    Unsupported(String),
24
25    /// No running app matched the name, bundle id, or pid.
26    #[error("app not found: {0}")]
27    AppNotFound(String),
28
29    /// The app resolved, but the window did not.
30    #[error("window not found: {0}")]
31    WindowNotFound(String),
32
33    /// The element index is missing from the fresh tree.
34    #[error("element not found: {0}")]
35    ElementNotFound(String),
36
37    /// The element index exists, but its role, identifier, or frame changed.
38    #[error("stale element: {0}")]
39    StaleElement(String),
40
41    /// Accessibility or Screen Recording permission is off.
42    #[error("permission required: {0}")]
43    PermissionRequired(String),
44
45    /// A built-in or configured policy refused the action.
46    #[error("policy denied: {0}")]
47    PolicyDenied(String),
48
49    /// A sensitive action needs a confirm hook and none is configured.
50    #[error("confirm required: {0}")]
51    ConfirmRequired(String),
52
53    /// The confirm hook exited non-zero or timed out.
54    #[error("confirm denied: {0}")]
55    ConfirmDenied(String),
56
57    /// A browser URL matched the block list.
58    #[error("blocked url: {0}")]
59    BlockedUrl(String),
60
61    /// Local user input tripped the kill switch.
62    #[error("paused: {0}")]
63    Paused(String),
64
65    /// Chrome DevTools is not enabled or no debugging port is open.
66    #[error("cdp unavailable: {0}")]
67    CdpUnavailable(String),
68
69    /// The screen is locked, asleep, or no display is attached.
70    #[error("display unavailable: {0}")]
71    DisplayUnavailable(String),
72}
73
74impl ComputerError {
75    /// Stable machine-readable code used in JSON error objects.
76    pub fn code(&self) -> &'static str {
77        match self {
78            Self::InvalidArguments(_) => "invalid_arguments",
79            Self::ExecutionFailed(_) => "execution_failed",
80            Self::Io(_) => "io_error",
81            Self::Unsupported(_) => "unsupported",
82            Self::AppNotFound(_) => "app_not_found",
83            Self::WindowNotFound(_) => "window_not_found",
84            Self::ElementNotFound(_) => "element_not_found",
85            Self::StaleElement(_) => "stale_element",
86            Self::PermissionRequired(_) => "permission_required",
87            Self::PolicyDenied(_) => "policy_denied",
88            Self::ConfirmRequired(_) => "confirm_required",
89            Self::ConfirmDenied(_) => "confirm_denied",
90            Self::BlockedUrl(_) => "blocked_url",
91            Self::Paused(_) => "paused",
92            Self::CdpUnavailable(_) => "cdp_unavailable",
93            Self::DisplayUnavailable(_) => "display_unavailable",
94        }
95    }
96}
97
98/// `error` object inside a failed [`crate::Response`].
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct ErrorBody {
101    /// Stable code such as `invalid_arguments` or `execution_failed`.
102    pub code: String,
103    /// Human-readable explanation.
104    pub message: String,
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn codes_and_display_match_the_variant() {
113        let err = ComputerError::InvalidArguments("missing text".into());
114        assert_eq!(err.code(), "invalid_arguments");
115        assert_eq!(err.to_string(), "invalid arguments: missing text");
116
117        let err = ComputerError::ExecutionFailed("click failed".into());
118        assert_eq!(err.code(), "execution_failed");
119        assert_eq!(err.to_string(), "execution failed: click failed");
120
121        let err = ComputerError::Unsupported("no at-spi".into());
122        assert_eq!(err.code(), "unsupported");
123        assert_eq!(err.to_string(), "unsupported: no at-spi");
124
125        let err = ComputerError::Io(std::io::Error::other("disk"));
126        assert_eq!(err.code(), "io_error");
127        assert!(err.to_string().contains("disk"));
128
129        assert_eq!(
130            ComputerError::AppNotFound("Notes".into()).code(),
131            "app_not_found"
132        );
133        assert_eq!(
134            ComputerError::WindowNotFound("0".into()).code(),
135            "window_not_found"
136        );
137        assert_eq!(
138            ComputerError::ElementNotFound("4".into()).code(),
139            "element_not_found"
140        );
141        assert_eq!(
142            ComputerError::StaleElement("role changed".into()).code(),
143            "stale_element"
144        );
145        assert_eq!(
146            ComputerError::PermissionRequired("Accessibility".into()).code(),
147            "permission_required"
148        );
149        assert_eq!(
150            ComputerError::PolicyDenied("Terminal".into()).code(),
151            "policy_denied"
152        );
153        assert_eq!(
154            ComputerError::ConfirmRequired("quit".into()).code(),
155            "confirm_required"
156        );
157        assert_eq!(
158            ComputerError::ConfirmDenied("timeout".into()).code(),
159            "confirm_denied"
160        );
161        assert_eq!(
162            ComputerError::BlockedUrl("https://accounts.example.com".into()).code(),
163            "blocked_url"
164        );
165        assert_eq!(ComputerError::Paused("user input".into()).code(), "paused");
166        assert_eq!(
167            ComputerError::CdpUnavailable("feature off".into()).code(),
168            "cdp_unavailable"
169        );
170        let err = ComputerError::DisplayUnavailable("the screen is locked".into());
171        assert_eq!(err.code(), "display_unavailable");
172        assert_eq!(err.to_string(), "display unavailable: the screen is locked");
173    }
174}