Skip to main content

modular_agent_core/
error.rs

1use thiserror::Error;
2
3/// Errors that occur during agent operations.
4///
5/// Errors are categorized into:
6///
7/// - **Configuration errors**: `InvalidConfig`, `UnknownConfig`, `NoConfig`
8/// - **Value errors**: `InvalidValue`, `InvalidArrayValue`
9/// - **Agent management errors**: `AgentNotFound`, `AgentAlreadyExists`
10/// - **Connection errors**: `ConnectionNotFound`, `ConnectionAlreadyExists`
11/// - **I/O errors**: `IoError`, `SerializationError`, `JsonParseError`
12/// - **Retryable / provider errors**: `RateLimited`, `Overloaded`, `Timeout`, `ContextOverflow`, `Cancelled`
13#[derive(Clone, Debug, Error)]
14pub enum AgentError {
15    /// Invalid value in an array element.
16    #[error("Invalid {0} value in array")]
17    InvalidArrayValue(String),
18
19    /// Agent definition is invalid.
20    #[error("{0}: Agent definition \"{1}\" is invalid")]
21    InvalidDefinition(String, String),
22
23    /// Invalid port name.
24    #[error("Invalid port: {0}")]
25    InvalidPin(String),
26
27    /// Invalid preset name.
28    #[error("Invalid preset name: {0}")]
29    InvalidPresetName(String),
30
31    /// Invalid value for the expected type.
32    #[error("Invalid {0} value")]
33    InvalidValue(String),
34
35    /// Agent definition is missing a required field.
36    #[error("{0}: Agent definition \"{1}\" is missing")]
37    MissingDefinition(String, String),
38
39    /// Failed to rename a preset.
40    #[error("Failed to rename preset: {0}")]
41    RenamePresetFailed(String),
42
43    /// Unknown agent definition kind.
44    #[error("Unknown agent def kind: {0}")]
45    UnknownDefKind(String),
46
47    /// Unknown agent definition name.
48    #[error("Unknown agent def name: {0}")]
49    UnknownDefName(String),
50
51    /// Agent definition is not implemented.
52    #[error("Agent definition \"{0}\" is not implemented")]
53    NotImplemented(String),
54
55    /// An agent with this ID already exists.
56    #[error("Agent {0} already exists")]
57    AgentAlreadyExists(String),
58
59    /// Failed to create an agent.
60    #[error("Failed to create agent {0}")]
61    AgentCreationFailed(String),
62
63    /// Agent with the specified ID was not found.
64    #[error("Agent {0} not found")]
65    AgentNotFound(String),
66
67    /// Source agent in a connection was not found.
68    #[error("Source agent {0} not found")]
69    SourceAgentNotFound(String),
70
71    /// Duplicate ID detected.
72    #[error("Duplicate id: {0}")]
73    DuplicateId(String),
74
75    /// Connection source handle is empty.
76    #[error("Source handle is empty")]
77    EmptySourceHandle,
78
79    /// Connection target handle is empty.
80    #[error("Target handle is empty")]
81    EmptyTargetHandle,
82
83    /// A connection between these ports already exists.
84    #[error("Connection already exists")]
85    ConnectionAlreadyExists,
86
87    /// Connection with the specified ID was not found.
88    #[error("Connection {0} not found")]
89    ConnectionNotFound(String),
90
91    /// Preset with the specified name was not found.
92    #[error("Preset {0} not found")]
93    PresetNotFound(String),
94
95    /// A preset with this name already exists.
96    #[error("Preset name \"{0}\" already exists")]
97    PresetNameExists(String),
98
99    /// Agent definition was not found.
100    #[error("Agent {0} definition not found")]
101    AgentDefinitionNotFound(String),
102
103    /// Agent message sender was not found.
104    #[error("Agent tx for {0} not found")]
105    AgentTxNotFound(String),
106
107    /// Failed to send a message to an agent.
108    #[error("Failed to send message: {0}")]
109    SendMessageFailed(String),
110
111    /// Serialization or deserialization error.
112    #[error("Failed to serialize/deserialize: {0}")]
113    SerializationError(String),
114
115    /// Message sender is not initialized.
116    #[error("Message sender not initialized")]
117    TxNotInitialized,
118
119    /// I/O error.
120    #[error("IO error: {0}")]
121    IoError(String),
122
123    /// JSON parsing error.
124    #[error("JSON parsing error: {0}")]
125    JsonParseError(String),
126
127    /// Invalid file extension (expected JSON).
128    #[error("Invalid file extension: expected JSON")]
129    InvalidFileExtension,
130
131    /// File name is empty.
132    #[error("Empty file name")]
133    EmptyFileName,
134
135    /// Failed to get file stem from path.
136    #[error("Failed to get file stem from path")]
137    FileSystemError,
138
139    /// Invalid configuration value.
140    #[error("Configuration error: {0}")]
141    InvalidConfig(String),
142
143    /// No configuration is available for this agent.
144    #[error("No configuration available")]
145    NoConfig,
146
147    /// Configuration key does not exist.
148    #[error("Unknown configuration: {0}")]
149    UnknownConfig(String),
150
151    /// No global configuration is available.
152    #[error("No global configuration available")]
153    NoGlobalConfig,
154
155    /// Port (pin) was not found.
156    #[error("Pin not found: {0}")]
157    PinNotFound(String),
158
159    /// Request was rejected because the provider rate limit was exceeded.
160    ///
161    /// `retry_after` carries the provider's suggested wait duration when a
162    /// `Retry-After` header is present.
163    #[error("Rate limited: {message}")]
164    RateLimited {
165        message: String,
166        retry_after: Option<std::time::Duration>,
167    },
168
169    /// Provider is temporarily overloaded (e.g. HTTP 529).
170    #[error("Provider overloaded: {0}")]
171    Overloaded(String),
172
173    /// Request did not complete within the allotted time.
174    #[error("Request timed out: {0}")]
175    Timeout(String),
176
177    /// Request exceeded the model's context window.
178    #[error("Context overflow: {0}")]
179    ContextOverflow(String),
180
181    /// Operation was cancelled before completion.
182    #[error("Cancelled")]
183    Cancelled,
184
185    /// Generic agent error.
186    #[error("Agent error: {0}")]
187    Other(String),
188}
189
190impl AgentError {
191    /// Returns `true` for errors that are transient and may succeed on retry.
192    pub fn is_retryable(&self) -> bool {
193        matches!(
194            self,
195            Self::RateLimited { .. } | Self::Overloaded(_) | Self::Timeout(_)
196        )
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn retryable_variants_are_retryable() {
206        assert!(
207            AgentError::RateLimited {
208                message: "slow down".into(),
209                retry_after: None,
210            }
211            .is_retryable()
212        );
213        assert!(AgentError::Overloaded("busy".into()).is_retryable());
214        assert!(AgentError::Timeout("deadline exceeded".into()).is_retryable());
215    }
216
217    #[test]
218    fn non_retryable_variants_are_not_retryable() {
219        assert!(!AgentError::ContextOverflow("too long".into()).is_retryable());
220        assert!(!AgentError::Cancelled.is_retryable());
221        assert!(!AgentError::InvalidValue("bad".into()).is_retryable());
222        assert!(!AgentError::IoError("disk full".into()).is_retryable());
223    }
224}