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