gemini_adk_rs/
error.rs

1//! Error types for the agent runtime.
2
3use gemini_genai_rs::session::SessionError;
4
5/// Convenience alias for fallible agent-runtime operations.
6///
7/// Lets call sites write `AgentResult<T>` instead of the more verbose
8/// `Result<T, AgentError>`.
9pub type AgentResult<T> = std::result::Result<T, AgentError>;
10
11/// Errors that can occur during agent execution.
12#[derive(Debug, thiserror::Error)]
13pub enum AgentError {
14    /// A wire-level session error (WebSocket, auth, setup).
15    #[error("Session error: {0}")]
16    Session(#[from] SessionError),
17
18    /// A tool execution error.
19    #[error("Tool error: {0}")]
20    Tool(#[from] ToolError),
21
22    /// The requested agent was not found in the registry.
23    #[error("Unknown agent: {0}")]
24    UnknownAgent(String),
25
26    /// The agent requested a transfer to another agent.
27    #[error("Transfer requested to agent: {0}")]
28    TransferRequested(String),
29
30    /// An agent transfer was attempted but failed.
31    #[error("Agent transfer failed: {0}")]
32    TransferFailed(String),
33
34    /// The underlying session has been closed.
35    #[error("Agent session closed")]
36    SessionClosed,
37
38    /// The operation timed out.
39    #[error("Timeout")]
40    Timeout,
41
42    /// A configuration error.
43    #[error("Configuration error: {0}")]
44    Config(String),
45
46    /// A catch-all for other errors.
47    #[error("{0}")]
48    Other(String),
49}
50
51/// A build-time configuration error: one or more problems found while
52/// validating user-supplied configuration (a [`Flow`](crate::flow::Flow), a
53/// [`PhaseMachine`](crate::live::PhaseMachine), a
54/// [`ComputedRegistry`](crate::live::ComputedRegistry), …).
55///
56/// Every issue is reported, not just the first; `Display` joins them with
57/// `"; "`. Converts into [`AgentError::Config`] via `?`.
58#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
59#[error("{}", issues.join("; "))]
60pub struct ConfigError {
61    /// Every problem found, in discovery order. Never empty.
62    pub issues: Vec<String>,
63}
64
65impl ConfigError {
66    /// A single-issue error.
67    pub fn new(issue: impl Into<String>) -> Self {
68        Self {
69            issues: vec![issue.into()],
70        }
71    }
72
73    /// Collect a list of issues into an error, or `Ok(())` when there are none.
74    pub fn from_issues(issues: Vec<String>) -> Result<(), Self> {
75        if issues.is_empty() {
76            Ok(())
77        } else {
78            Err(Self { issues })
79        }
80    }
81}
82
83impl From<ConfigError> for AgentError {
84    fn from(err: ConfigError) -> Self {
85        AgentError::Config(err.to_string())
86    }
87}
88
89/// Errors that can occur during tool execution.
90#[derive(Debug, Clone, thiserror::Error)]
91pub enum ToolError {
92    /// The tool's execution logic failed.
93    #[error("Tool execution failed: {0}")]
94    ExecutionFailed(String),
95
96    /// No tool with this name is registered.
97    #[error("Tool not found: {0}")]
98    NotFound(String),
99
100    /// The arguments provided to the tool were invalid.
101    #[error("Invalid arguments: {0}")]
102    InvalidArgs(String),
103
104    /// The tool call was cancelled before completion.
105    #[error("Tool cancelled")]
106    Cancelled,
107
108    /// The tool call exceeded its timeout.
109    #[error("Tool execution timed out after {0:?}")]
110    Timeout(std::time::Duration),
111
112    /// A catch-all for other tool errors.
113    #[error("{0}")]
114    Other(String),
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use std::time::Duration;
121
122    #[test]
123    fn agent_error_display_messages() {
124        let err = AgentError::UnknownAgent("foo".into());
125        assert_eq!(err.to_string(), "Unknown agent: foo");
126
127        let err = AgentError::TransferRequested("bar".into());
128        assert_eq!(err.to_string(), "Transfer requested to agent: bar");
129
130        let err = AgentError::TransferFailed("baz".into());
131        assert_eq!(err.to_string(), "Agent transfer failed: baz");
132
133        let err = AgentError::SessionClosed;
134        assert_eq!(err.to_string(), "Agent session closed");
135
136        let err = AgentError::Timeout;
137        assert_eq!(err.to_string(), "Timeout");
138
139        let err = AgentError::Config("bad value".into());
140        assert_eq!(err.to_string(), "Configuration error: bad value");
141
142        let err = AgentError::Other("something".into());
143        assert_eq!(err.to_string(), "something");
144    }
145
146    #[test]
147    fn agent_error_from_session_error() {
148        use gemini_genai_rs::session::SessionError;
149        use gemini_genai_rs::session::WebSocketError;
150
151        let ws_err = SessionError::WebSocket(WebSocketError::ConnectionRefused("refused".into()));
152        let agent_err: AgentError = ws_err.into();
153        let msg = agent_err.to_string();
154        assert!(msg.contains("Session error"), "got: {msg}");
155    }
156
157    #[test]
158    fn agent_error_from_tool_error() {
159        let tool_err = ToolError::NotFound("my_tool".into());
160        let agent_err: AgentError = tool_err.into();
161        let msg = agent_err.to_string();
162        assert!(msg.contains("Tool error"), "got: {msg}");
163        assert!(msg.contains("my_tool"), "got: {msg}");
164    }
165
166    #[test]
167    fn config_error_joins_issues_and_converts() {
168        let err = ConfigError {
169            issues: vec!["a".into(), "b".into()],
170        };
171        assert_eq!(err.to_string(), "a; b");
172        assert!(ConfigError::from_issues(vec![]).is_ok());
173        let agent_err: AgentError = ConfigError::new("bad").into();
174        assert_eq!(agent_err.to_string(), "Configuration error: bad");
175    }
176
177    #[test]
178    fn tool_error_display_messages() {
179        assert_eq!(
180            ToolError::ExecutionFailed("boom".into()).to_string(),
181            "Tool execution failed: boom"
182        );
183        assert_eq!(
184            ToolError::NotFound("x".into()).to_string(),
185            "Tool not found: x"
186        );
187        assert_eq!(
188            ToolError::InvalidArgs("bad".into()).to_string(),
189            "Invalid arguments: bad"
190        );
191        assert_eq!(ToolError::Cancelled.to_string(), "Tool cancelled");
192        assert_eq!(ToolError::Other("misc".into()).to_string(), "misc");
193    }
194
195    #[test]
196    fn tool_error_timeout_shows_duration() {
197        let err = ToolError::Timeout(Duration::from_secs(5));
198        let msg = err.to_string();
199        assert!(msg.contains("5s"), "got: {msg}");
200        assert!(msg.contains("timed out"), "got: {msg}");
201    }
202
203    #[test]
204    fn tool_error_is_clone() {
205        let err = ToolError::ExecutionFailed("test".into());
206        let cloned = err.clone();
207        assert_eq!(err.to_string(), cloned.to_string());
208
209        let err2 = ToolError::Timeout(Duration::from_millis(100));
210        let cloned2 = err2.clone();
211        assert_eq!(err2.to_string(), cloned2.to_string());
212    }
213}