1use serde_json::{json, Value};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum ErrorCode {
7 OpencodeNotFound,
8 ServiceDown,
9 AuthFailed,
10 SessionNotFound,
11 NoSession,
12 PairNotFound,
13 InvalidArgument,
14 InvalidRequest,
15 RefusedOnHost,
16 ConfirmRequired,
17 PermissionsLocked,
18 MutationBlocked,
19 BlockedOperation,
20 NonJsonResponse,
21 UnknownOperation,
22 BodyTooLarge,
23 Conflict,
24 Deadline,
25 IoError,
26 LauncherConflict,
27 ControllerNotFound,
28 CliError,
29}
30
31impl ErrorCode {
32 pub const ALL: [ErrorCode; 22] = [
33 ErrorCode::OpencodeNotFound,
34 ErrorCode::ServiceDown,
35 ErrorCode::AuthFailed,
36 ErrorCode::SessionNotFound,
37 ErrorCode::NoSession,
38 ErrorCode::PairNotFound,
39 ErrorCode::InvalidArgument,
40 ErrorCode::InvalidRequest,
41 ErrorCode::RefusedOnHost,
42 ErrorCode::ConfirmRequired,
43 ErrorCode::PermissionsLocked,
44 ErrorCode::MutationBlocked,
45 ErrorCode::BlockedOperation,
46 ErrorCode::NonJsonResponse,
47 ErrorCode::UnknownOperation,
48 ErrorCode::BodyTooLarge,
49 ErrorCode::Conflict,
50 ErrorCode::Deadline,
51 ErrorCode::IoError,
52 ErrorCode::LauncherConflict,
53 ErrorCode::ControllerNotFound,
54 ErrorCode::CliError,
55 ];
56
57 pub fn as_str(self) -> &'static str {
58 match self {
59 ErrorCode::OpencodeNotFound => "OPENCODE_NOT_FOUND",
60 ErrorCode::ServiceDown => "SERVICE_DOWN",
61 ErrorCode::AuthFailed => "AUTH_FAILED",
62 ErrorCode::SessionNotFound => "SESSION_NOT_FOUND",
63 ErrorCode::NoSession => "NO_SESSION",
64 ErrorCode::PairNotFound => "PAIR_NOT_FOUND",
65 ErrorCode::InvalidArgument => "INVALID_ARGUMENT",
66 ErrorCode::InvalidRequest => "INVALID_REQUEST",
67 ErrorCode::RefusedOnHost => "REFUSED_ON_HOST",
68 ErrorCode::ConfirmRequired => "CONFIRM_REQUIRED",
69 ErrorCode::PermissionsLocked => "PERMISSIONS_LOCKED",
70 ErrorCode::MutationBlocked => "MUTATION_BLOCKED",
71 ErrorCode::BlockedOperation => "BLOCKED_OPERATION",
72 ErrorCode::NonJsonResponse => "NON_JSON_RESPONSE",
73 ErrorCode::UnknownOperation => "UNKNOWN_OPERATION",
74 ErrorCode::BodyTooLarge => "BODY_TOO_LARGE",
75 ErrorCode::Conflict => "CONFLICT",
76 ErrorCode::Deadline => "DEADLINE",
77 ErrorCode::IoError => "IO_ERROR",
78 ErrorCode::LauncherConflict => "LAUNCHER_CONFLICT",
79 ErrorCode::ControllerNotFound => "CONTROLLER_NOT_FOUND",
80 ErrorCode::CliError => "CLI_ERROR",
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq)]
86pub struct ToolError {
87 pub code: ErrorCode,
88 pub message: String,
89 pub hint: Option<String>,
90 pub details: Option<Value>,
91}
92
93impl ToolError {
94 pub fn new(code: ErrorCode, msg: impl Into<String>) -> Self {
95 Self {
96 code,
97 message: msg.into(),
98 hint: None,
99 details: None,
100 }
101 }
102
103 pub fn hint(mut self, hint: &str) -> Self {
104 self.hint = Some(hint.to_string());
105 self
106 }
107
108 pub fn details(mut self, details: Value) -> Self {
109 self.details = Some(details);
110 self
111 }
112
113 pub fn to_json(&self) -> Value {
114 let mut error = json!({ "code": self.code.as_str(), "message": self.message });
115 if let Some(h) = &self.hint {
116 error["hint"] = json!(h);
117 }
118 if let Some(d) = &self.details {
119 error["details"] = d.clone();
120 }
121 json!({ "error": error })
122 }
123}
124
125impl std::fmt::Display for ToolError {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "{}: {}", self.code.as_str(), self.message)
128 }
129}
130
131impl std::error::Error for ToolError {}
132
133impl From<std::io::Error> for ToolError {
134 fn from(e: std::io::Error) -> Self {
135 ToolError::new(ErrorCode::IoError, e.to_string())
136 }
137}
138
139pub type ToolResult = Result<Value, ToolError>;