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