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}