1use crate::core::timeout_detector::{OperationType, TIMEOUT_DETECTOR};
2use crate::utils::current_timestamp;
3use anyhow::Result;
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ExecutionError {
11 pub id: String,
12 pub timestamp: u64,
13 pub error_type: ErrorType,
14 pub message: String,
15 pub context: ErrorContext,
16 pub recovery_attempts: Vec<RecoveryAttempt>,
17 pub resolved: bool,
18}
19
20#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, Hash, PartialEq)]
29pub enum ErrorType {
30 ToolExecution,
31 ApiCall,
32 FileSystem,
33 Network,
34 Validation,
35 CircuitBreaker,
36 Timeout,
37 PermissionDenied,
38 InvalidArguments,
39 ResourceNotFound,
40 Other,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ErrorContext {
46 pub conversation_turn: usize,
47 pub user_input: Option<String>,
48 pub tool_name: Option<String>,
49 pub tool_args: Option<Value>,
50 pub api_request_size: Option<usize>,
51 pub context_size: Option<usize>,
52 pub stack_trace: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct RecoveryAttempt {
58 pub timestamp: u64,
59 pub strategy: RecoveryStrategy,
60 pub success: bool,
61 pub result: String,
62 pub new_context_size: Option<usize>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum RecoveryStrategy {
68 RetryWithBackoff {
69 delay_ms: u64,
70 attempt_number: usize,
71 },
72
73 SimplifyRequest {
74 removed_parameters: Vec<String>,
75 },
76 AlternativeTool {
77 original_tool: String,
78 alternative_tool: String,
79 },
80 ContextReset {
81 preserved_data: IndexMap<String, Value>,
82 },
83 ManualIntervention,
84}
85
86pub struct ErrorRecoveryManager {
88 errors: Vec<ExecutionError>,
89 recovery_strategies: IndexMap<ErrorType, Vec<RecoveryStrategy>>,
90 operation_type_mapping: IndexMap<ErrorType, OperationType>,
91}
92
93impl Default for ErrorRecoveryManager {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99impl ErrorRecoveryManager {
100 pub fn new() -> Self {
101 let mut recovery_strategies = IndexMap::with_capacity(2);
103 let mut operation_type_mapping = IndexMap::with_capacity(11);
104
105 recovery_strategies.insert(
107 ErrorType::ToolExecution,
108 vec![
109 RecoveryStrategy::RetryWithBackoff { delay_ms: 1000, attempt_number: 1 },
110 RecoveryStrategy::AlternativeTool {
111 original_tool: String::new(),
112 alternative_tool: String::new(),
113 },
114 ],
115 );
116
117 recovery_strategies.insert(
118 ErrorType::ApiCall,
119 vec![
120 RecoveryStrategy::RetryWithBackoff { delay_ms: 2000, attempt_number: 1 },
121 RecoveryStrategy::ContextReset { preserved_data: IndexMap::new() },
122 ],
123 );
124
125 operation_type_mapping.insert(ErrorType::ToolExecution, OperationType::ToolExecution);
127 operation_type_mapping.insert(ErrorType::ApiCall, OperationType::ApiCall);
128 operation_type_mapping.insert(ErrorType::Network, OperationType::NetworkRequest);
129 operation_type_mapping.insert(ErrorType::FileSystem, OperationType::FileOperation);
130 operation_type_mapping.insert(ErrorType::Validation, OperationType::Processing);
131 operation_type_mapping.insert(ErrorType::CircuitBreaker, OperationType::ToolExecution);
132 operation_type_mapping.insert(ErrorType::Timeout, OperationType::Processing);
133 operation_type_mapping.insert(ErrorType::PermissionDenied, OperationType::Processing);
134 operation_type_mapping.insert(ErrorType::InvalidArguments, OperationType::Processing);
135 operation_type_mapping.insert(ErrorType::ResourceNotFound, OperationType::FileOperation);
136 operation_type_mapping.insert(ErrorType::Other, OperationType::Processing);
137
138 Self {
139 errors: Vec::with_capacity(16), recovery_strategies,
141 operation_type_mapping,
142 }
143 }
144
145 pub fn record_error(&mut self, error_type: ErrorType, message: String, context: ErrorContext) -> String {
147 let error_count = self.errors.len();
149 let timestamp_short = current_timestamp() % 10000;
150 let error_id = format!("e{error_count}_{timestamp_short}");
151
152 let error = ExecutionError {
153 id: error_id.clone(),
154 timestamp: current_timestamp(),
155 error_type, message,
157 context,
158 recovery_attempts: Vec::with_capacity(2), resolved: false,
160 };
161
162 self.errors.push(error);
163 error_id
164 }
165
166 #[cold]
168 pub fn record_recovery_attempt(
169 &mut self,
170 error_id: &str,
171 strategy: RecoveryStrategy,
172 success: bool,
173 result: String,
174 new_context_size: Option<usize>,
175 ) {
176 let attempt = RecoveryAttempt {
177 timestamp: current_timestamp(),
178 strategy,
179 success,
180 result,
181 new_context_size,
182 };
183
184 if let Some(error) = self.errors.iter_mut().find(|e| e.id == error_id) {
185 error.recovery_attempts.push(attempt);
186 if success {
187 error.resolved = true;
188 }
189 }
190 }
191
192 #[cold]
194 pub fn get_recovery_strategies(&self, error_type: &ErrorType) -> &[RecoveryStrategy] {
195 self.recovery_strategies
196 .get(error_type)
197 .map(|strategies| strategies.as_slice())
198 .unwrap_or(&[])
199 }
200
201 pub fn generate_context_preservation_plan(
203 &self,
204 context_size: usize,
205 error_count: usize,
206 ) -> ContextPreservationPlan {
207 let critical_errors = error_count > 5;
208
209 let strategies = if critical_errors {
210 vec![
211 PreservationStrategy::SelectiveRetention { preserve_decisions: true, preserve_errors: true },
212 PreservationStrategy::ContextReset { preserve_session_data: true },
213 ]
214 } else {
215 vec![PreservationStrategy::NoAction]
216 };
217
218 ContextPreservationPlan {
219 current_context_size: context_size,
220 error_count,
221 recommended_strategies: strategies,
222 urgency: if critical_errors {
223 Urgency::Critical
224 } else {
225 Urgency::Low
226 },
227 }
228 }
229
230 pub fn get_error_statistics(&self) -> ErrorStatistics {
232 let total_errors = self.errors.len();
233 if total_errors == 0 {
234 return ErrorStatistics {
235 total_errors: 0,
236 resolved_errors: 0,
237 unresolved_errors: 0,
238 errors_by_type: IndexMap::new(),
239 avg_recovery_attempts: 0.0,
240 recent_errors: Vec::new(),
241 };
242 }
243
244 let resolved_errors = self.errors.iter().filter(|e| e.resolved).count();
245 let unresolved_errors = total_errors - resolved_errors;
246
247 let mut errors_by_type = IndexMap::new();
249 let mut total_attempts = 0usize;
250
251 for error in &self.errors {
252 *errors_by_type.entry(error.error_type).or_insert(0) += 1;
253 total_attempts += error.recovery_attempts.len();
254 }
255
256 let avg_recovery_attempts = total_attempts as f64 / total_errors as f64;
257
258 let recent_count = total_errors.min(5);
260 let recent_errors: Vec<_> = self.errors.iter().rev().take(recent_count).cloned().collect();
261
262 ErrorStatistics {
263 total_errors,
264 resolved_errors,
265 unresolved_errors,
266 errors_by_type,
267 avg_recovery_attempts,
268 recent_errors,
269 }
270 }
271
272 pub fn detect_error_pattern(&self, error_type: &ErrorType, time_window_seconds: u64) -> bool {
274 let now = current_timestamp();
275
276 let recent_errors = self
277 .errors
278 .iter()
279 .filter(|e| e.error_type == *error_type && (now - e.timestamp) < time_window_seconds)
280 .count();
281
282 recent_errors >= 3 }
284
285 pub fn get_operation_type(&self, error_type: &ErrorType) -> OperationType {
287 self.operation_type_mapping
288 .get(error_type)
289 .cloned()
290 .unwrap_or(OperationType::Processing)
291 }
292
293 pub async fn execute_with_recovery<F, Fut, T>(
295 &mut self,
296 operation_id: String,
297 error_type: ErrorType,
298 _context: ErrorContext,
299 operation: F,
300 ) -> Result<T, anyhow::Error>
301 where
302 F: Fn() -> Fut,
303 Fut: Future<Output = Result<T, anyhow::Error>>,
304 {
305 let operation_type = self.get_operation_type(&error_type);
306
307 TIMEOUT_DETECTOR
308 .execute_with_timeout_retry(operation_id, operation_type, operation)
309 .await
310 }
311
312 #[cold]
315 pub async fn should_retry_operation(&self, error_type: &ErrorType, error: &anyhow::Error, attempt: u32) -> bool {
316 let operation_type = self.get_operation_type(error_type);
317 TIMEOUT_DETECTOR.should_retry(&operation_type, error, attempt).await
318 }
319
320 pub async fn get_timeout_stats(&self) -> crate::core::timeout_detector::TimeoutStats {
322 TIMEOUT_DETECTOR.get_stats().await
323 }
324
325 #[cold]
328 pub async fn configure_timeout_for_error_type(
329 &self,
330 error_type: ErrorType,
331 config: crate::core::timeout_detector::TimeoutConfig,
332 ) {
333 let operation_type = self.get_operation_type(&error_type);
334 TIMEOUT_DETECTOR.set_config(operation_type, config).await;
335 }
336
337 #[cold]
340 pub async fn generate_enhanced_recovery_plan(
341 &self,
342 context_size: usize,
343 error_count: usize,
344 ) -> EnhancedContextPreservationPlan {
345 let timeout_stats = self.get_timeout_stats().await;
346 let base_plan = self.generate_context_preservation_plan(context_size, error_count);
347
348 let timeout_rate = if timeout_stats.total_operations > 0 {
350 timeout_stats.timed_out_operations as f64 / timeout_stats.total_operations as f64
351 } else {
352 0.0
353 };
354
355 let retry_success_rate = if timeout_stats.total_retry_attempts > 0 {
356 timeout_stats.successful_retries as f64 / timeout_stats.total_retry_attempts as f64
357 } else {
358 1.0
359 };
360
361 let _adjusted_urgency = if timeout_rate > 0.3 {
363 Urgency::Critical
365 } else if retry_success_rate < 0.5 {
366 Urgency::High
368 } else {
369 base_plan.urgency
370 };
371
372 EnhancedContextPreservationPlan {
373 base_plan,
374 timeout_rate,
375 retry_success_rate,
376 timeout_stats,
377 }
378 }
379
380 pub fn error_count(&self) -> usize {
382 self.errors.len()
383 }
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ContextPreservationPlan {
389 pub current_context_size: usize,
390 pub error_count: usize,
391 pub recommended_strategies: Vec<PreservationStrategy>,
392 pub urgency: Urgency,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
397pub enum PreservationStrategy {
398 SelectiveRetention {
399 preserve_decisions: bool,
400 preserve_errors: bool,
401 },
402 ContextReset {
403 preserve_session_data: bool,
404 },
405 NoAction,
406}
407
408#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
410pub enum Urgency {
411 Low,
412 High,
413 Critical,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct ErrorStatistics {
419 pub total_errors: usize,
420 pub resolved_errors: usize,
421 pub unresolved_errors: usize,
422 pub errors_by_type: IndexMap<ErrorType, usize>,
423 pub avg_recovery_attempts: f64,
424 pub recent_errors: Vec<ExecutionError>,
425}
426
427#[derive(Debug, Clone)]
429pub struct EnhancedContextPreservationPlan {
430 pub base_plan: ContextPreservationPlan,
431 pub timeout_rate: f64,
432 pub retry_success_rate: f64,
433 pub timeout_stats: crate::core::timeout_detector::TimeoutStats,
434}