use std::collections::{HashMap, HashSet};
use crate::actions::ActionsConfig;
use crate::orchestrator::SwarmConfig;
use crate::state::SwarmState;
use crate::types::{SwarmTask, WorkerId};
use crate::context::{TaskContext, WorkerSummary};
pub trait Analyzer: Send + Sync {
fn analyze(&self, state: &SwarmState) -> TaskContext;
fn name(&self) -> &str {
"Analyzer"
}
}
#[derive(Debug, Clone, Default)]
pub struct DefaultAnalyzer {
name: String,
}
impl DefaultAnalyzer {
pub fn new() -> Self {
Self {
name: "DefaultAnalyzer".to_string(),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
fn calculate_success_rate(state: &SwarmState) -> f64 {
state.shared.stats.success_rate()
}
fn calculate_progress(state: &SwarmState, max_ticks: u64) -> f64 {
if max_ticks == 0 {
return Self::calculate_success_rate(state);
}
(state.shared.tick as f64 / max_ticks as f64).min(1.0)
}
}
impl Analyzer for DefaultAnalyzer {
fn analyze(&self, state: &SwarmState) -> TaskContext {
let tick = state.shared.tick;
let mut workers = HashMap::new();
let mut escalations = Vec::new();
for (idx, ctx) in state.workers.iter().enumerate() {
let worker_id = WorkerId(idx);
let has_escalation = ctx.escalation.is_some();
if let Some(esc) = &ctx.escalation {
escalations.push((worker_id, esc.clone()));
}
let last_entry = ctx.history.latest();
let (last_action, last_success) = last_entry
.map(|e| (Some(e.action_name.clone()), Some(e.success)))
.unwrap_or((None, None));
let summary = WorkerSummary {
id: worker_id,
consecutive_failures: ctx.consecutive_failures,
last_action,
last_success,
last_output: ctx.last_output.clone(),
history_len: ctx.history.len(),
has_escalation,
};
workers.insert(worker_id, summary);
}
let success_rate = Self::calculate_success_rate(state);
let max_ticks = state
.shared
.extensions
.get::<SwarmConfig>()
.map(|c| c.max_ticks)
.unwrap_or(0);
let progress = Self::calculate_progress(state, max_ticks);
let available_actions = state.shared.extensions.get::<ActionsConfig>().cloned();
let mut metadata = HashMap::new();
if let Some(task) = state.shared.extensions.get::<SwarmTask>() {
metadata.insert(
"task".to_string(),
serde_json::Value::String(task.goal.clone()),
);
if let Some(obj) = task.context.as_object() {
for (key, value) in obj {
metadata.insert(format!("task_{}", key), value.clone());
}
}
}
TaskContext {
tick,
workers,
success_rate,
progress,
escalations,
available_actions,
v2_guidances: None, excluded_actions: Vec::new(), previous_guidances: HashMap::new(), done_workers: HashSet::new(), metadata,
}
}
fn name(&self) -> &str {
&self.name
}
}
impl Analyzer for Box<dyn Analyzer> {
fn analyze(&self, state: &SwarmState) -> TaskContext {
(**self).analyze(state)
}
fn name(&self) -> &str {
(**self).name()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_analyzer_empty_state() {
let analyzer = DefaultAnalyzer::new();
let state = SwarmState::new(3);
let ctx = analyzer.analyze(&state);
assert_eq!(ctx.tick, 0);
assert_eq!(ctx.workers.len(), 3);
assert_eq!(ctx.success_rate, 1.0);
assert!(!ctx.has_escalations());
}
#[test]
fn test_default_analyzer_with_name() {
let analyzer = DefaultAnalyzer::new().with_name("TestAnalyzer");
assert_eq!(analyzer.name(), "TestAnalyzer");
}
}