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}