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