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 { agent: String, stderr: Option<String> },
127    #[error("agent process exited: {agent}")]
128    AgentProcessExited {
129        agent: String,
130        exit_code: Option<i32>,
131        stderr: Option<String>,
132    },
133    #[error("token invalid")]
134    TokenInvalid { message: Option<String> },
135    #[error("permission denied")]
136    PermissionDenied { message: Option<String> },
137    #[error("session not found: {session_id}")]
138    SessionNotFound { session_id: String },
139    #[error("session already exists: {session_id}")]
140    SessionAlreadyExists { session_id: String },
141    #[error("mode not supported: {agent} {mode}")]
142    ModeNotSupported { agent: String, mode: String },
143    #[error("stream error: {message}")]
144    StreamError { message: String },
145    #[error("timeout")]
146    Timeout { message: Option<String> },
147}
148
149impl SandboxError {
150    pub fn error_type(&self) -> ErrorType {
151        match self {
152            Self::InvalidRequest { .. } => ErrorType::InvalidRequest,
153            Self::UnsupportedAgent { .. } => ErrorType::UnsupportedAgent,
154            Self::AgentNotInstalled { .. } => ErrorType::AgentNotInstalled,
155            Self::InstallFailed { .. } => ErrorType::InstallFailed,
156            Self::AgentProcessExited { .. } => ErrorType::AgentProcessExited,
157            Self::TokenInvalid { .. } => ErrorType::TokenInvalid,
158            Self::PermissionDenied { .. } => ErrorType::PermissionDenied,
159            Self::SessionNotFound { .. } => ErrorType::SessionNotFound,
160            Self::SessionAlreadyExists { .. } => ErrorType::SessionAlreadyExists,
161            Self::ModeNotSupported { .. } => ErrorType::ModeNotSupported,
162            Self::StreamError { .. } => ErrorType::StreamError,
163            Self::Timeout { .. } => ErrorType::Timeout,
164        }
165    }
166
167    pub fn to_agent_error(&self) -> AgentError {
168        let (agent, session_id, details) = match self {
169            Self::InvalidRequest { .. } => (None, None, None),
170            Self::UnsupportedAgent { agent } => {
171                (Some(agent.clone()), None, None)
172            }
173            Self::AgentNotInstalled { agent } => (Some(agent.clone()), None, None),
174            Self::InstallFailed { agent, stderr } => {
175                let mut map = Map::new();
176                if let Some(stderr) = stderr {
177                    map.insert("stderr".to_string(), Value::String(stderr.clone()));
178                }
179                (
180                    Some(agent.clone()),
181                    None,
182                    if map.is_empty() { None } else { Some(Value::Object(map)) },
183                )
184            }
185            Self::AgentProcessExited {
186                agent,
187                exit_code,
188                stderr,
189            } => {
190                let mut map = Map::new();
191                if let Some(code) = exit_code {
192                    map.insert(
193                        "exitCode".to_string(),
194                        Value::Number(serde_json::Number::from(*code as i64)),
195                    );
196                }
197                if let Some(stderr) = stderr {
198                    map.insert("stderr".to_string(), Value::String(stderr.clone()));
199                }
200                (
201                    Some(agent.clone()),
202                    None,
203                    if map.is_empty() { None } else { Some(Value::Object(map)) },
204                )
205            }
206            Self::TokenInvalid { message } => {
207                let details = message.as_ref().map(|msg| {
208                    let mut map = Map::new();
209                    map.insert("message".to_string(), Value::String(msg.clone()));
210                    Value::Object(map)
211                });
212                (None, None, details)
213            }
214            Self::PermissionDenied { message } => {
215                let details = message.as_ref().map(|msg| {
216                    let mut map = Map::new();
217                    map.insert("message".to_string(), Value::String(msg.clone()));
218                    Value::Object(map)
219                });
220                (None, None, details)
221            }
222            Self::SessionNotFound { session_id } => {
223                (None, Some(session_id.clone()), None)
224            }
225            Self::SessionAlreadyExists { session_id } => {
226                (None, Some(session_id.clone()), None)
227            }
228            Self::ModeNotSupported { agent, mode } => {
229                let mut map = Map::new();
230                map.insert("mode".to_string(), Value::String(mode.clone()));
231                (
232                    Some(agent.clone()),
233                    None,
234                    Some(Value::Object(map)),
235                )
236            }
237            Self::StreamError { message } => {
238                let mut map = Map::new();
239                map.insert("message".to_string(), Value::String(message.clone()));
240                (None, None, Some(Value::Object(map)))
241            }
242            Self::Timeout { message } => {
243                let details = message.as_ref().map(|msg| {
244                    let mut map = Map::new();
245                    map.insert("message".to_string(), Value::String(msg.clone()));
246                    Value::Object(map)
247                });
248                (None, None, details)
249            }
250        };
251
252        AgentError {
253            type_: self.error_type(),
254            message: self.to_string(),
255            agent,
256            session_id,
257            details,
258        }
259    }
260
261    pub fn to_problem_details(&self) -> ProblemDetails {
262        let mut problem = ProblemDetails::new(self.error_type(), Some(self.to_string()));
263        let agent_error = self.to_agent_error();
264
265        let mut extensions = Map::new();
266        if let Some(agent) = agent_error.agent {
267            extensions.insert("agent".to_string(), Value::String(agent));
268        }
269        if let Some(session_id) = agent_error.session_id {
270            extensions.insert("sessionId".to_string(), Value::String(session_id));
271        }
272        if let Some(details) = agent_error.details {
273            extensions.insert("details".to_string(), details);
274        }
275        problem.extensions = extensions;
276        problem
277    }
278}
279
280impl From<SandboxError> for ProblemDetails {
281    fn from(value: SandboxError) -> Self {
282        value.to_problem_details()
283    }
284}
285
286impl From<&SandboxError> for ProblemDetails {
287    fn from(value: &SandboxError) -> Self {
288        value.to_problem_details()
289    }
290}
291
292impl From<SandboxError> for AgentError {
293    fn from(value: SandboxError) -> Self {
294        value.to_agent_error()
295    }
296}
297
298impl From<&SandboxError> for AgentError {
299    fn from(value: &SandboxError) -> Self {
300        value.to_agent_error()
301    }
302}