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}