Skip to main content

sandbox_agent_error/
lib.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4use thiserror::Error;
5use utoipa::ToSchema;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, ToSchema)]
8#[serde(rename_all = "snake_case")]
9pub enum ErrorType {
10    InvalidRequest,
11    UnsupportedAgent,
12    AgentNotInstalled,
13    InstallFailed,
14    AgentProcessExited,
15    TokenInvalid,
16    PermissionDenied,
17    SessionNotFound,
18    SessionAlreadyExists,
19    ModeNotSupported,
20    StreamError,
21    Timeout,
22}
23
24impl ErrorType {
25    pub fn as_urn(&self) -> &'static str {
26        match self {
27            Self::InvalidRequest => "urn:sandbox-agent:error:invalid_request",
28            Self::UnsupportedAgent => "urn:sandbox-agent:error:unsupported_agent",
29            Self::AgentNotInstalled => "urn:sandbox-agent:error:agent_not_installed",
30            Self::InstallFailed => "urn:sandbox-agent:error:install_failed",
31            Self::AgentProcessExited => "urn:sandbox-agent:error:agent_process_exited",
32            Self::TokenInvalid => "urn:sandbox-agent:error:token_invalid",
33            Self::PermissionDenied => "urn:sandbox-agent:error:permission_denied",
34            Self::SessionNotFound => "urn:sandbox-agent:error:session_not_found",
35            Self::SessionAlreadyExists => "urn:sandbox-agent:error:session_already_exists",
36            Self::ModeNotSupported => "urn:sandbox-agent:error:mode_not_supported",
37            Self::StreamError => "urn:sandbox-agent:error:stream_error",
38            Self::Timeout => "urn:sandbox-agent:error:timeout",
39        }
40    }
41
42    pub fn title(&self) -> &'static str {
43        match self {
44            Self::InvalidRequest => "Invalid Request",
45            Self::UnsupportedAgent => "Unsupported Agent",
46            Self::AgentNotInstalled => "Agent Not Installed",
47            Self::InstallFailed => "Install Failed",
48            Self::AgentProcessExited => "Agent Process Exited",
49            Self::TokenInvalid => "Token Invalid",
50            Self::PermissionDenied => "Permission Denied",
51            Self::SessionNotFound => "Session Not Found",
52            Self::SessionAlreadyExists => "Session Already Exists",
53            Self::ModeNotSupported => "Mode Not Supported",
54            Self::StreamError => "Stream Error",
55            Self::Timeout => "Timeout",
56        }
57    }
58
59    pub fn status_code(&self) -> u16 {
60        match self {
61            Self::InvalidRequest => 400,
62            Self::UnsupportedAgent => 400,
63            Self::AgentNotInstalled => 404,
64            Self::InstallFailed => 500,
65            Self::AgentProcessExited => 500,
66            Self::TokenInvalid => 401,
67            Self::PermissionDenied => 403,
68            Self::SessionNotFound => 404,
69            Self::SessionAlreadyExists => 409,
70            Self::ModeNotSupported => 400,
71            Self::StreamError => 502,
72            Self::Timeout => 504,
73        }
74    }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
78pub struct ProblemDetails {
79    #[serde(rename = "type")]
80    pub type_: String,
81    pub title: String,
82    pub status: u16,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub detail: Option<String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub instance: Option<String>,
87    #[serde(flatten, default, skip_serializing_if = "Map::is_empty")]
88    pub extensions: Map<String, Value>,
89}
90
91impl ProblemDetails {
92    pub fn new(error_type: ErrorType, detail: Option<String>) -> Self {
93        Self {
94            type_: error_type.as_urn().to_string(),
95            title: error_type.title().to_string(),
96            status: error_type.status_code(),
97            detail,
98            instance: None,
99            extensions: Map::new(),
100        }
101    }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
105pub struct AgentError {
106    #[serde(rename = "type")]
107    pub type_: ErrorType,
108    pub message: String,
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub agent: Option<String>,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub session_id: Option<String>,
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub details: Option<Value>,
115}
116
117#[derive(Debug, Error)]
118pub enum SandboxError {
119    #[error("invalid request: {message}")]
120    InvalidRequest { message: String },
121    #[error("unsupported agent: {agent}")]
122    UnsupportedAgent { agent: String },
123    #[error("agent not installed: {agent}")]
124    AgentNotInstalled { agent: String },
125    #[error("install failed: {agent}")]
126    InstallFailed {
127        agent: String,
128        stderr: Option<String>,
129    },
130    #[error("agent process exited: {agent}")]
131    AgentProcessExited {
132        agent: String,
133        exit_code: Option<i32>,
134        stderr: Option<String>,
135    },
136    #[error("token invalid")]
137    TokenInvalid { message: Option<String> },
138    #[error("permission denied")]
139    PermissionDenied { message: Option<String> },
140    #[error("session not found: {session_id}")]
141    SessionNotFound { session_id: String },
142    #[error("session already exists: {session_id}")]
143    SessionAlreadyExists { session_id: String },
144    #[error("mode not supported: {agent} {mode}")]
145    ModeNotSupported { agent: String, mode: String },
146    #[error("stream error: {message}")]
147    StreamError { message: String },
148    #[error("timeout")]
149    Timeout { message: Option<String> },
150}
151
152impl SandboxError {
153    pub fn error_type(&self) -> ErrorType {
154        match self {
155            Self::InvalidRequest { .. } => ErrorType::InvalidRequest,
156            Self::UnsupportedAgent { .. } => ErrorType::UnsupportedAgent,
157            Self::AgentNotInstalled { .. } => ErrorType::AgentNotInstalled,
158            Self::InstallFailed { .. } => ErrorType::InstallFailed,
159            Self::AgentProcessExited { .. } => ErrorType::AgentProcessExited,
160            Self::TokenInvalid { .. } => ErrorType::TokenInvalid,
161            Self::PermissionDenied { .. } => ErrorType::PermissionDenied,
162            Self::SessionNotFound { .. } => ErrorType::SessionNotFound,
163            Self::SessionAlreadyExists { .. } => ErrorType::SessionAlreadyExists,
164            Self::ModeNotSupported { .. } => ErrorType::ModeNotSupported,
165            Self::StreamError { .. } => ErrorType::StreamError,
166            Self::Timeout { .. } => ErrorType::Timeout,
167        }
168    }
169
170    pub fn to_agent_error(&self) -> AgentError {
171        let (agent, session_id, details) = match self {
172            Self::InvalidRequest { .. } => (None, None, None),
173            Self::UnsupportedAgent { agent } => (Some(agent.clone()), None, None),
174            Self::AgentNotInstalled { agent } => (Some(agent.clone()), None, None),
175            Self::InstallFailed { agent, stderr } => {
176                let mut map = Map::new();
177                if let Some(stderr) = stderr {
178                    map.insert("stderr".to_string(), Value::String(stderr.clone()));
179                }
180                (
181                    Some(agent.clone()),
182                    None,
183                    if map.is_empty() {
184                        None
185                    } else {
186                        Some(Value::Object(map))
187                    },
188                )
189            }
190            Self::AgentProcessExited {
191                agent,
192                exit_code,
193                stderr,
194            } => {
195                let mut map = Map::new();
196                if let Some(code) = exit_code {
197                    map.insert(
198                        "exitCode".to_string(),
199                        Value::Number(serde_json::Number::from(*code as i64)),
200                    );
201                }
202                if let Some(stderr) = stderr {
203                    map.insert("stderr".to_string(), Value::String(stderr.clone()));
204                }
205                (
206                    Some(agent.clone()),
207                    None,
208                    if map.is_empty() {
209                        None
210                    } else {
211                        Some(Value::Object(map))
212                    },
213                )
214            }
215            Self::TokenInvalid { message } => {
216                let details = message.as_ref().map(|msg| {
217                    let mut map = Map::new();
218                    map.insert("message".to_string(), Value::String(msg.clone()));
219                    Value::Object(map)
220                });
221                (None, None, details)
222            }
223            Self::PermissionDenied { message } => {
224                let details = message.as_ref().map(|msg| {
225                    let mut map = Map::new();
226                    map.insert("message".to_string(), Value::String(msg.clone()));
227                    Value::Object(map)
228                });
229                (None, None, details)
230            }
231            Self::SessionNotFound { session_id } => (None, Some(session_id.clone()), None),
232            Self::SessionAlreadyExists { session_id } => (None, Some(session_id.clone()), None),
233            Self::ModeNotSupported { agent, mode } => {
234                let mut map = Map::new();
235                map.insert("mode".to_string(), Value::String(mode.clone()));
236                (Some(agent.clone()), None, Some(Value::Object(map)))
237            }
238            Self::StreamError { message } => {
239                let mut map = Map::new();
240                map.insert("message".to_string(), Value::String(message.clone()));
241                (None, None, Some(Value::Object(map)))
242            }
243            Self::Timeout { message } => {
244                let details = message.as_ref().map(|msg| {
245                    let mut map = Map::new();
246                    map.insert("message".to_string(), Value::String(msg.clone()));
247                    Value::Object(map)
248                });
249                (None, None, details)
250            }
251        };
252
253        AgentError {
254            type_: self.error_type(),
255            message: self.to_string(),
256            agent,
257            session_id,
258            details,
259        }
260    }
261
262    pub fn to_problem_details(&self) -> ProblemDetails {
263        let mut problem = ProblemDetails::new(self.error_type(), Some(self.to_string()));
264        let agent_error = self.to_agent_error();
265
266        let mut extensions = Map::new();
267        if let Some(agent) = agent_error.agent {
268            extensions.insert("agent".to_string(), Value::String(agent));
269        }
270        if let Some(session_id) = agent_error.session_id {
271            extensions.insert("sessionId".to_string(), Value::String(session_id));
272        }
273        if let Some(details) = agent_error.details {
274            extensions.insert("details".to_string(), details);
275        }
276        problem.extensions = extensions;
277        problem
278    }
279}
280
281impl From<SandboxError> for ProblemDetails {
282    fn from(value: SandboxError) -> Self {
283        value.to_problem_details()
284    }
285}
286
287impl From<&SandboxError> for ProblemDetails {
288    fn from(value: &SandboxError) -> Self {
289        value.to_problem_details()
290    }
291}
292
293impl From<SandboxError> for AgentError {
294    fn from(value: SandboxError) -> Self {
295        value.to_agent_error()
296    }
297}
298
299impl From<&SandboxError> for AgentError {
300    fn from(value: &SandboxError) -> Self {
301        value.to_agent_error()
302    }
303}