1use thiserror::Error;
7
8use crate::agent::AgentError;
9use crate::provider::ProviderError;
10use crate::tool::ToolError;
11
12#[cfg(feature = "session")]
13use crate::session::SessionError;
14
15#[derive(Debug, Error)]
28pub enum Error {
29 #[error("authentication failed: {0}")]
31 Auth(String),
32
33 #[error("rate limited: {0}")]
35 RateLimited(String),
36
37 #[error("network error: {0}")]
39 Network(String),
40
41 #[error("service unavailable: {0}")]
43 Unavailable(String),
44
45 #[error("model error: {0}")]
47 Model(String),
48
49 #[error("tool error: {0}")]
51 Tool(String),
52
53 #[error("configuration error: {0}")]
55 Config(String),
56
57 #[cfg(feature = "session")]
59 #[error("session error: {0}")]
60 Session(String),
61
62 #[cfg(feature = "mcp")]
64 #[error("MCP error: {0}")]
65 Mcp(String),
66
67 #[error("{0}")]
69 Other(String),
70}
71
72impl Error {
73 pub fn is_auth(&self) -> bool {
75 matches!(self, Self::Auth(_))
76 }
77
78 pub fn is_rate_limited(&self) -> bool {
80 matches!(self, Self::RateLimited(_))
81 }
82
83 pub fn is_network(&self) -> bool {
85 matches!(self, Self::Network(_))
86 }
87
88 pub fn is_unavailable(&self) -> bool {
90 matches!(self, Self::Unavailable(_))
91 }
92
93 pub fn is_model(&self) -> bool {
95 matches!(self, Self::Model(_))
96 }
97
98 pub fn is_tool(&self) -> bool {
100 matches!(self, Self::Tool(_))
101 }
102
103 pub fn is_config(&self) -> bool {
105 matches!(self, Self::Config(_))
106 }
107
108 pub fn is_retryable(&self) -> bool {
114 matches!(
115 self,
116 Self::RateLimited(_) | Self::Network(_) | Self::Unavailable(_)
117 )
118 }
119}
120
121impl From<ProviderError> for Error {
122 fn from(err: ProviderError) -> Self {
123 match err {
124 ProviderError::Authentication(msg) => Self::Auth(msg),
125 ProviderError::RateLimited(msg) => Self::RateLimited(msg),
126 ProviderError::Network(msg) => Self::Network(msg),
127 ProviderError::ServiceUnavailable(msg) => Self::Unavailable(msg),
128 ProviderError::Model(msg) => Self::Model(msg),
129 ProviderError::Configuration(msg) => Self::Config(msg),
130 ProviderError::Communication(err) => Self::Network(err.to_string()),
131 ProviderError::Other(msg) => Self::Other(msg),
132 }
133 }
134}
135
136impl From<ToolError> for Error {
137 fn from(err: ToolError) -> Self {
138 Self::Tool(err.to_string())
139 }
140}
141
142#[cfg(feature = "session")]
143impl From<SessionError> for Error {
144 fn from(err: SessionError) -> Self {
145 Self::Session(err.to_string())
146 }
147}
148
149impl From<AgentError> for Error {
150 fn from(err: AgentError) -> Self {
151 match err {
152 AgentError::Provider(e) => e.into(),
153 AgentError::Tool(e) => e.into(),
154 #[cfg(feature = "session")]
155 AgentError::Session(e) => e.into(),
156 AgentError::NoResponse => Self::Model("model returned no response".to_string()),
157 AgentError::EmptyResponse => Self::Model("model returned empty response".to_string()),
158 AgentError::MaxTokensExceeded => Self::Model(
159 "response exceeded maximum token limit - try asking the model to be more concise"
160 .to_string(),
161 ),
162 AgentError::ContentFiltered => {
163 Self::Model("response was filtered by content moderation".to_string())
164 }
165 AgentError::ToolDenied(msg) => Self::Tool(format!("denied: {}", msg)),
166 AgentError::ToolNotFound(name) => Self::Tool(format!("not found: {}", name)),
167 AgentError::InvalidToolInput(msg) => Self::Tool(format!("invalid input: {}", msg)),
168 AgentError::PermissionFailed(msg) => Self::Tool(format!("permission failed: {}", msg)),
169 AgentError::UnexpectedStopReason(reason) => {
170 Self::Model(format!("unexpected stop reason: {}", reason))
171 }
172 AgentError::Context(e) => Self::Model(format!("context error: {}", e)),
173 }
174 }
175}
176
177pub type Result<T> = std::result::Result<T, Error>;
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_is_retryable() {
186 assert!(Error::RateLimited("slow down".into()).is_retryable());
187 assert!(Error::Network("connection refused".into()).is_retryable());
188 assert!(Error::Unavailable("503".into()).is_retryable());
189
190 assert!(!Error::Auth("invalid token".into()).is_retryable());
191 assert!(!Error::Config("bad model id".into()).is_retryable());
192 assert!(!Error::Model("content filtered".into()).is_retryable());
193 }
194
195 #[test]
196 fn test_from_provider_error() {
197 let err: Error = ProviderError::Authentication("expired".into()).into();
198 assert!(err.is_auth());
199
200 let err: Error = ProviderError::RateLimited("throttled".into()).into();
201 assert!(err.is_rate_limited());
202
203 let err: Error = ProviderError::Network("timeout".into()).into();
204 assert!(err.is_network());
205 }
206
207 #[test]
208 fn test_from_agent_error() {
209 let err: Error = AgentError::MaxTokensExceeded.into();
210 assert!(err.is_model());
211
212 let err: Error = AgentError::ToolNotFound("calculator".into()).into();
213 assert!(err.is_tool());
214
215 let err: Error = AgentError::Provider(ProviderError::RateLimited("slow".into())).into();
216 assert!(err.is_rate_limited());
217 }
218
219 #[test]
220 fn test_convenience_methods() {
221 assert!(Error::Auth("x".into()).is_auth());
222 assert!(Error::RateLimited("x".into()).is_rate_limited());
223 assert!(Error::Network("x".into()).is_network());
224 assert!(Error::Unavailable("x".into()).is_unavailable());
225 assert!(Error::Model("x".into()).is_model());
226 assert!(Error::Tool("x".into()).is_tool());
227 assert!(Error::Config("x".into()).is_config());
228 }
229}