use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::ai::task::AITaskType;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryConfig {
#[serde(default = "default_scan_interval")]
pub scan_interval_secs: u64,
#[serde(default = "default_task_timeouts")]
pub task_timeouts: HashMap<String, u64>,
#[serde(default = "default_heartbeat_timeout")]
pub agent_heartbeat_timeout_secs: u64,
#[serde(default = "default_circuit_failure_threshold")]
pub circuit_failure_threshold: u32,
#[serde(default = "default_circuit_failure_rate")]
pub circuit_failure_rate_threshold: f64,
#[serde(default = "default_circuit_cooldown")]
pub circuit_cooldown_secs: u64,
#[serde(default = "default_contribution_stuck_timeout")]
pub contribution_stuck_timeout_secs: u64,
#[serde(default = "default_max_recovery_retries")]
pub max_recovery_retries: u32,
#[serde(default = "default_true")]
pub enable_task_reassignment: bool,
#[serde(default = "default_true")]
pub enable_circuit_breaker: bool,
}
fn default_scan_interval() -> u64 {
30
}
fn default_task_timeouts() -> HashMap<String, u64> {
let mut timeouts = HashMap::new();
timeouts.insert(AITaskType::AnalyzeContribution.to_string(), 300); timeouts.insert(AITaskType::GenerateCode.to_string(), 600); timeouts.insert(AITaskType::ValidateCode.to_string(), 300); timeouts.insert(AITaskType::RunTests.to_string(), 900); timeouts.insert(AITaskType::PrepareReview.to_string(), 180); timeouts.insert(AITaskType::MergeChanges.to_string(), 120); timeouts
}
fn default_heartbeat_timeout() -> u64 {
60
}
fn default_circuit_failure_threshold() -> u32 {
5
}
fn default_circuit_failure_rate() -> f64 {
0.5
}
fn default_circuit_cooldown() -> u64 {
300 }
fn default_contribution_stuck_timeout() -> u64 {
1800 }
fn default_max_recovery_retries() -> u32 {
3
}
fn default_true() -> bool {
true
}
impl Default for RecoveryConfig {
fn default() -> Self {
Self {
scan_interval_secs: default_scan_interval(),
task_timeouts: default_task_timeouts(),
agent_heartbeat_timeout_secs: default_heartbeat_timeout(),
circuit_failure_threshold: default_circuit_failure_threshold(),
circuit_failure_rate_threshold: default_circuit_failure_rate(),
circuit_cooldown_secs: default_circuit_cooldown(),
contribution_stuck_timeout_secs: default_contribution_stuck_timeout(),
max_recovery_retries: default_max_recovery_retries(),
enable_task_reassignment: true,
enable_circuit_breaker: true,
}
}
}
impl RecoveryConfig {
pub fn get_task_timeout(&self, task_type: &AITaskType) -> u64 {
self.task_timeouts
.get(&task_type.to_string())
.copied()
.unwrap_or(300) }
#[cfg(test)]
pub fn minimal() -> Self {
Self {
scan_interval_secs: 5,
agent_heartbeat_timeout_secs: 10,
circuit_cooldown_secs: 30,
contribution_stuck_timeout_secs: 60,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = RecoveryConfig::default();
assert_eq!(config.scan_interval_secs, 30);
assert_eq!(config.agent_heartbeat_timeout_secs, 60);
assert_eq!(config.circuit_failure_threshold, 5);
assert!(config.enable_circuit_breaker);
}
#[test]
fn test_task_timeout() {
let config = RecoveryConfig::default();
assert_eq!(
config.get_task_timeout(&AITaskType::AnalyzeContribution),
300
);
assert_eq!(config.get_task_timeout(&AITaskType::GenerateCode), 600);
assert_eq!(config.get_task_timeout(&AITaskType::RunTests), 900);
}
#[test]
fn test_config_serialization() {
let config = RecoveryConfig::default();
let json = serde_json::to_string(&config).unwrap();
let parsed: RecoveryConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.scan_interval_secs, config.scan_interval_secs);
}
}