1use gemini_genai_rs::session::SessionError;
4
5pub type AgentResult<T> = std::result::Result<T, AgentError>;
10
11#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum AgentError {
26 #[error("Session error: {0}")]
28 Session(#[from] SessionError),
29
30 #[error("Tool error: {0}")]
32 Tool(#[from] ToolError),
33
34 #[error("model call failed: {0}")]
36 Llm(#[from] crate::llm::LlmError),
37
38 #[error(transparent)]
40 State(#[from] crate::state::StateError),
41
42 #[error("the model's reply is not a valid {expected}: {reason}")]
45 InvalidOutput {
46 expected: &'static str,
48 reason: String,
50 text: String,
52 },
53
54 #[error("Unknown agent: {0}")]
56 UnknownAgent(String),
57
58 #[error("Transfer requested to agent: {0}")]
60 TransferRequested(String),
61
62 #[error("Agent transfer failed: {0}")]
64 TransferFailed(String),
65
66 #[error("Agent session closed")]
68 SessionClosed,
69
70 #[error("Timeout")]
72 Timeout,
73
74 #[error("Configuration error: {0}")]
76 Config(String),
77
78 #[error("{0}")]
80 Other(String),
81}
82
83impl AgentError {
84 pub fn as_llm(&self) -> Option<&crate::llm::LlmError> {
86 match self {
87 Self::Llm(e) => Some(e),
88 _ => None,
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
101#[error("{}", issues.join("; "))]
102pub struct ConfigError {
103 pub issues: Vec<String>,
105}
106
107impl ConfigError {
108 pub fn new(issue: impl Into<String>) -> Self {
110 Self {
111 issues: vec![issue.into()],
112 }
113 }
114
115 pub fn from_issues(issues: Vec<String>) -> Result<(), Self> {
117 if issues.is_empty() {
118 Ok(())
119 } else {
120 Err(Self { issues })
121 }
122 }
123}
124
125impl From<ConfigError> for AgentError {
126 fn from(err: ConfigError) -> Self {
127 AgentError::Config(err.to_string())
128 }
129}
130
131#[derive(Debug, Clone, thiserror::Error)]
133#[non_exhaustive]
134pub enum ToolError {
135 #[error("Tool execution failed: {0}")]
137 ExecutionFailed(String),
138
139 #[error("Tool not found: {0}")]
141 NotFound(String),
142
143 #[error("Invalid arguments: {0}")]
145 InvalidArgs(String),
146
147 #[error("Tool cancelled")]
149 Cancelled,
150
151 #[error("Tool call declined: {0}")]
154 Declined(String),
155
156 #[error("Tool execution timed out after {0:?}")]
158 Timeout(std::time::Duration),
159
160 #[error("{0}")]
162 Other(String),
163}
164
165impl ToolError {
166 pub fn from_error(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
183 match error.into().downcast::<ToolError>() {
184 Ok(tool_error) => *tool_error,
185 Err(other) => ToolError::ExecutionFailed(other.to_string()),
186 }
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use std::time::Duration;
194
195 #[test]
196 fn agent_error_display_messages() {
197 let err = AgentError::UnknownAgent("foo".into());
198 assert_eq!(err.to_string(), "Unknown agent: foo");
199
200 let err = AgentError::TransferRequested("bar".into());
201 assert_eq!(err.to_string(), "Transfer requested to agent: bar");
202
203 let err = AgentError::TransferFailed("baz".into());
204 assert_eq!(err.to_string(), "Agent transfer failed: baz");
205
206 let err = AgentError::SessionClosed;
207 assert_eq!(err.to_string(), "Agent session closed");
208
209 let err = AgentError::Timeout;
210 assert_eq!(err.to_string(), "Timeout");
211
212 let err = AgentError::Config("bad value".into());
213 assert_eq!(err.to_string(), "Configuration error: bad value");
214
215 let err = AgentError::Other("something".into());
216 assert_eq!(err.to_string(), "something");
217 }
218
219 #[test]
220 fn agent_error_from_session_error() {
221 use gemini_genai_rs::session::SessionError;
222 use gemini_genai_rs::session::WebSocketError;
223
224 let ws_err = SessionError::WebSocket(WebSocketError::ConnectionRefused("refused".into()));
225 let agent_err: AgentError = ws_err.into();
226 let msg = agent_err.to_string();
227 assert!(msg.contains("Session error"), "got: {msg}");
228 }
229
230 #[test]
231 fn agent_error_from_tool_error() {
232 let tool_err = ToolError::NotFound("my_tool".into());
233 let agent_err: AgentError = tool_err.into();
234 let msg = agent_err.to_string();
235 assert!(msg.contains("Tool error"), "got: {msg}");
236 assert!(msg.contains("my_tool"), "got: {msg}");
237 }
238
239 #[test]
240 fn config_error_joins_issues_and_converts() {
241 let err = ConfigError {
242 issues: vec!["a".into(), "b".into()],
243 };
244 assert_eq!(err.to_string(), "a; b");
245 assert!(ConfigError::from_issues(vec![]).is_ok());
246 let agent_err: AgentError = ConfigError::new("bad").into();
247 assert_eq!(agent_err.to_string(), "Configuration error: bad");
248 }
249
250 #[test]
251 fn tool_error_display_messages() {
252 assert_eq!(
253 ToolError::ExecutionFailed("boom".into()).to_string(),
254 "Tool execution failed: boom"
255 );
256 assert_eq!(
257 ToolError::NotFound("x".into()).to_string(),
258 "Tool not found: x"
259 );
260 assert_eq!(
261 ToolError::InvalidArgs("bad".into()).to_string(),
262 "Invalid arguments: bad"
263 );
264 assert_eq!(ToolError::Cancelled.to_string(), "Tool cancelled");
265 assert_eq!(ToolError::Other("misc".into()).to_string(), "misc");
266 }
267
268 #[test]
269 fn tool_error_timeout_shows_duration() {
270 let err = ToolError::Timeout(Duration::from_secs(5));
271 let msg = err.to_string();
272 assert!(msg.contains("5s"), "got: {msg}");
273 assert!(msg.contains("timed out"), "got: {msg}");
274 }
275
276 #[test]
277 fn tool_error_is_clone() {
278 let err = ToolError::ExecutionFailed("test".into());
279 let cloned = err.clone();
280 assert_eq!(err.to_string(), cloned.to_string());
281
282 let err2 = ToolError::Timeout(Duration::from_millis(100));
283 let cloned2 = err2.clone();
284 assert_eq!(err2.to_string(), cloned2.to_string());
285 }
286}