1use thiserror::Error;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ErrorKind {
10 RateLimited,
11 Server,
12 Network,
13 Timeout,
14 Auth,
15 Client,
16 ContextOverflow,
17 Cancelled,
18 #[default]
19 Unknown,
20}
21
22impl ErrorKind {
23 pub fn as_str(self) -> &'static str {
24 match self {
25 Self::RateLimited => "rate_limited",
26 Self::Server => "server",
27 Self::Network => "network",
28 Self::Timeout => "timeout",
29 Self::Auth => "auth",
30 Self::Client => "client",
31 Self::ContextOverflow => "context_overflow",
32 Self::Cancelled => "cancelled",
33 Self::Unknown => "unknown",
34 }
35 }
36
37 pub fn retryable(self) -> bool {
39 matches!(
40 self,
41 Self::RateLimited | Self::Server | Self::Network | Self::Timeout
42 )
43 }
44}
45
46impl std::fmt::Display for ErrorKind {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.write_str(self.as_str())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TransportError {
55 pub kind: ErrorKind,
56 pub message: String,
57}
58
59impl TransportError {
60 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
61 Self {
62 kind,
63 message: message.into(),
64 }
65 }
66
67 pub fn unknown(message: impl Into<String>) -> Self {
68 Self::new(ErrorKind::Unknown, message)
69 }
70}
71
72impl std::fmt::Display for TransportError {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.write_str(&self.message)
75 }
76}
77
78impl From<String> for TransportError {
79 fn from(message: String) -> Self {
80 Self::unknown(message)
81 }
82}
83
84impl From<&str> for TransportError {
85 fn from(message: &str) -> Self {
86 Self::unknown(message)
87 }
88}
89
90#[derive(Error, Debug)]
91pub enum Error {
92 #[error("Configuration error: {0}")]
93 Config(String),
94
95 #[error("IO error: {0}")]
96 Io(#[from] std::io::Error),
97
98 #[error("Serialization error: {0}")]
99 Serde(String),
100
101 #[error("LLM error: {0}")]
102 Llm(TransportError),
103
104 #[error("Tool error: {0}")]
105 Tool(String),
106
107 #[error("Session error: {0}")]
108 Session(String),
109
110 #[error("Agent error: {0}")]
111 Agent(String),
112
113 #[error("Provider error: {0}")]
114 Provider(String),
115
116 #[error("HTTP error: {0}")]
117 Http(TransportError),
118
119 #[error("{0}")]
120 Other(String),
121}
122
123impl Error {
124 pub fn llm(message: impl Into<String>) -> Self {
125 Self::Llm(TransportError::unknown(message))
126 }
127
128 pub fn llm_kind(kind: ErrorKind, message: impl Into<String>) -> Self {
129 Self::Llm(TransportError::new(kind, message))
130 }
131
132 pub fn http(message: impl Into<String>) -> Self {
133 Self::Http(TransportError::unknown(message))
134 }
135
136 pub fn http_kind(kind: ErrorKind, message: impl Into<String>) -> Self {
137 Self::Http(TransportError::new(kind, message))
138 }
139
140 pub fn transport_kind(&self) -> Option<ErrorKind> {
142 match self {
143 Self::Llm(e) | Self::Http(e) => Some(e.kind),
144 _ => None,
145 }
146 }
147}
148
149impl From<serde_json::Error> for Error {
150 fn from(err: serde_json::Error) -> Self {
151 Self::Serde(err.to_string())
152 }
153}
154
155impl Clone for Error {
156 fn clone(&self) -> Self {
157 match self {
158 Self::Config(s) => Self::Config(s.clone()),
159 Self::Io(e) => Self::Io(std::io::Error::new(e.kind(), e.to_string())),
160 Self::Serde(s) => Self::Serde(s.clone()),
161 Self::Llm(e) => Self::Llm(e.clone()),
162 Self::Tool(s) => Self::Tool(s.clone()),
163 Self::Session(s) => Self::Session(s.clone()),
164 Self::Agent(s) => Self::Agent(s.clone()),
165 Self::Provider(s) => Self::Provider(s.clone()),
166 Self::Http(e) => Self::Http(e.clone()),
167 Self::Other(s) => Self::Other(s.clone()),
168 }
169 }
170}
171
172pub type Result<T> = std::result::Result<T, Error>;
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn error_kind_transport_constructors_and_clone() {
180 let kinds = [
181 ErrorKind::RateLimited,
182 ErrorKind::Server,
183 ErrorKind::Network,
184 ErrorKind::Timeout,
185 ErrorKind::Auth,
186 ErrorKind::Client,
187 ErrorKind::ContextOverflow,
188 ErrorKind::Cancelled,
189 ErrorKind::Unknown,
190 ];
191 for k in kinds {
192 assert!(!k.as_str().is_empty());
193 assert_eq!(k.to_string(), k.as_str());
194 let _ = k.retryable();
195 }
196 assert_eq!(ErrorKind::default(), ErrorKind::Unknown);
197 assert!(ErrorKind::Timeout.retryable());
198 assert!(!ErrorKind::Auth.retryable());
199
200 let te = TransportError::new(ErrorKind::Server, "s");
201 assert_eq!(te.to_string(), "s");
202 assert_eq!(TransportError::unknown("u").kind, ErrorKind::Unknown);
203 let from_string: TransportError = String::from("x").into();
204 assert_eq!(from_string.message, "x");
205 let from_str: TransportError = "y".into();
206 assert_eq!(from_str.message, "y");
207
208 let errs = vec![
209 Error::Config("c".into()),
210 Error::Io(std::io::Error::other("i")),
211 Error::Serde("s".into()),
212 Error::llm("l"),
213 Error::llm_kind(ErrorKind::RateLimited, "r"),
214 Error::Tool("t".into()),
215 Error::Session("se".into()),
216 Error::Agent("a".into()),
217 Error::Provider("p".into()),
218 Error::http("h"),
219 Error::http_kind(ErrorKind::Timeout, "ht"),
220 Error::Other("o".into()),
221 ];
222 for e in &errs {
223 let cloned = e.clone();
224 assert!(!cloned.to_string().is_empty());
225 let _ = format!("{e:?}");
226 }
227 assert_eq!(Error::llm("l").transport_kind(), Some(ErrorKind::Unknown));
228 assert_eq!(
229 Error::http_kind(ErrorKind::Timeout, "ht").transport_kind(),
230 Some(ErrorKind::Timeout)
231 );
232 assert_eq!(Error::Config("c".into()).transport_kind(), None);
233 let json_err = serde_json::from_str::<u8>("not-json").unwrap_err();
234 let e = Error::from(json_err);
235 assert!(matches!(e, Error::Serde(_)));
236 }
237}