1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
//! Error types shared across the public API.
use std::time::Duration;
use thiserror::Error;
use crate::provider::ProviderId;
/// Crate-wide result type.
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Top-level crate error.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
/// Error returned by a model or embedding provider.
#[error(transparent)]
Provider(#[from] ProviderError),
/// Error from tool execution.
#[error(transparent)]
Tool(#[from] ToolError),
/// Error from context construction.
#[error(transparent)]
Context(#[from] ContextError),
/// Error from a storage backend.
#[error(transparent)]
Storage(#[from] StorageError),
/// Configuration loading or validation error.
#[error("configuration error: {0}")]
Config(String),
}
/// Errors produced by provider implementations.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ProviderError {
/// Provider credentials were rejected.
#[error("authentication failed for provider `{provider}`")]
Authentication {
/// Provider that rejected authentication.
provider: ProviderId,
},
/// Request payload was invalid for the target provider.
#[error("provider `{provider}` rejected request: {message}")]
BadRequest {
/// Provider that rejected the request.
provider: ProviderId,
/// Provider-facing validation message.
message: String,
},
/// Provider asked the caller to slow down.
#[error("provider `{provider}` rate limited request")]
RateLimited {
/// Provider that rate limited the request.
provider: ProviderId,
/// Suggested delay before retrying.
retry_after: Option<Duration>,
},
/// Provider call exceeded the configured timeout.
#[error("provider `{provider}` timed out")]
Timeout {
/// Provider that timed out.
provider: ProviderId,
},
/// Provider is temporarily overloaded.
#[error("provider `{provider}` is overloaded")]
Overloaded {
/// Provider that reported overload.
provider: ProviderId,
},
/// Requested provider feature is unavailable.
#[error("provider `{provider}` does not support feature `{feature}`")]
Unsupported {
/// Provider that cannot serve the feature.
provider: ProviderId,
/// Feature name, such as `chat_stream` or `embedding`.
feature: String,
},
/// Transport layer failed before a provider response was decoded.
#[error("transport error for provider `{provider}`: {source}")]
Transport {
/// Provider reached by the transport.
provider: ProviderId,
/// Lower-level HTTP client error.
#[source]
source: reqwest::Error,
},
/// Provider response could not be decoded into the neutral schema.
#[error("failed to decode response from provider `{provider}`: {message}")]
Decode {
/// Provider that returned an invalid response.
provider: ProviderId,
/// Decode failure summary.
message: String,
},
/// Provider returned a structured error response.
#[error("provider `{provider}` returned error: {message}")]
Provider {
/// Provider that returned the error.
provider: ProviderId,
/// Optional HTTP status or provider-specific status code.
status: Option<u16>,
/// Human-readable provider error message.
message: String,
},
}
impl ProviderError {
/// Returns `true` when retrying the same request may succeed.
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(
self,
Self::RateLimited { .. }
| Self::Timeout { .. }
| Self::Overloaded { .. }
| Self::Transport { .. }
)
}
/// Returns `true` when the error indicates the request exceeded the
/// model's context window and should be retried after compaction.
///
/// Detects provider-specific error messages for:
/// - OpenAI: `context_length_exceeded`
/// - Anthropic: `prompt is too long`
/// - Generic: `maximum context length`, `too many tokens`, `token limit`
#[must_use]
pub fn is_context_overflow(&self) -> bool {
let message = match self {
Self::BadRequest { message, .. } | Self::Provider { message, .. } => message.as_str(),
_ => return false,
};
message.contains("context_length_exceeded")
|| message.contains("maximum context length")
|| message.contains("too many tokens")
|| message.contains("reduce the length")
|| message.contains("token limit")
|| message.contains("prompt is too long")
|| message.contains("input length exceeds")
|| message.contains("context window")
}
}
/// Errors produced during tool execution.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ToolError {
/// Requested tool is not registered.
#[error("tool `{name}` is not registered")]
NotFound {
/// Name of the missing tool.
name: String,
},
/// Tool execution failed with an application error.
#[error("tool `{name}` failed: {message}")]
Execution {
/// Name of the tool that failed.
name: String,
/// Human-readable error message.
message: String,
},
/// Tool arguments could not be parsed or validated.
#[error("tool `{name}` received invalid arguments: {message}")]
InvalidArguments {
/// Name of the tool that rejected arguments.
name: String,
/// Validation or parsing failure description.
message: String,
},
/// Tool is defined but not yet implemented.
#[error("tool `{name}` is not implemented")]
NotImplemented {
/// Name of the unimplemented tool.
name: String,
},
}
/// Errors produced during context construction.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ContextError {
/// Context adapter failed to produce its fragment.
#[error("context adapter `{adapter}` failed: {message}")]
AdapterFailed {
/// Name of the adapter that failed.
adapter: String,
/// Human-readable error message.
message: String,
},
/// Context input was invalid or incomplete.
#[error("invalid context input: {message}")]
InvalidInput {
/// Description of the validation failure.
message: String,
},
/// Required context adapter is not registered.
#[error("context adapter `{adapter}` is not registered")]
AdapterNotFound {
/// Name of the missing adapter.
adapter: String,
},
}
/// Errors produced by storage backends.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StorageError {
/// Requested entity was not found.
#[error("storage entity not found: `{id}`")]
NotFound {
/// Identifier of the missing entity.
id: String,
},
/// Failed to connect to the storage backend.
#[error("storage connection failed ({backend}): {message}")]
ConnectionFailed {
/// Backend identifier (e.g., "postgres", "redis").
backend: String,
/// Human-readable failure description.
message: String,
/// Lower-level error, if available.
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
/// Serialization or deserialization failed.
#[error("storage serialization failed: {message}")]
SerializationFailed {
/// Description of the serialization failure.
message: String,
/// Lower-level error, if available.
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
/// Backend-specific operational error.
#[error("storage backend error ({backend}): {message}")]
BackendError {
/// Backend identifier.
backend: String,
/// Human-readable error description.
message: String,
/// Lower-level error, if available.
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
/// Schema migration failed.
#[error("storage migration failed ({backend}): {message}")]
MigrationFailed {
/// Backend identifier.
backend: String,
/// Human-readable failure description.
message: String,
/// Lower-level error, if available.
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
/// Stored data is malformed or corrupted and cannot be interpreted.
///
/// This is distinct from [`SerializationFailed`](StorageError::SerializationFailed),
/// which indicates an operational error during encode/decode.
/// `DataCorruption` means bytes were successfully read from storage
/// but do not form valid data — an integrity error.
#[error("data corruption in `{field}`: {message}")]
DataCorruption {
/// Name of the corrupted field (e.g., "session.id", "message.metadata").
field: String,
/// Human-readable description of the corruption.
message: String,
/// Underlying parse or deserialization error, if available.
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}