use crate::backends::ExternalTask;
use super::config::RouterConfig;
use super::weights::AgentWeights;
use super::RouteResult;
pub(super) fn extract_agent_from_labels(
agents_config: &[String],
labels: &[String],
) -> Option<String> {
for label in labels {
if let Some(agent) = label.strip_prefix("agent:") {
let agent = agent.to_lowercase();
if agents_config.iter().any(|a| a == &agent) {
return Some(agent);
}
}
}
None
}
pub(super) fn extract_complexity_from_labels(labels: &[String]) -> String {
for label in labels {
if let Some(comp) = label.strip_prefix("complexity:") {
let comp = comp.to_lowercase();
if ["simple", "medium", "complex"].contains(&comp.as_str()) {
return comp;
}
}
}
"medium".to_string()
}
pub(super) fn route_via_round_robin(
agents: &[String],
config: &RouterConfig,
task: &ExternalTask,
) -> anyhow::Result<RouteResult> {
if agents.is_empty() {
anyhow::bail!("no agent CLIs found in PATH");
}
let task_num: usize = task.id.0.parse().unwrap_or(0);
let agent_idx = task_num % agents.len();
let agent = agents[agent_idx].clone();
Ok(RouteResult::from_config(
agent,
"medium".to_string(),
format!("round_robin (task {} % {} agents)", task.id.0, agents.len()),
config,
&task.id.0,
))
}
pub(super) fn route_via_round_robin_stateful(
agents: &[String],
config: &RouterConfig,
task: &ExternalTask,
rr_index: &mut usize,
last_agent: &mut Option<String>,
) -> anyhow::Result<RouteResult> {
if agents.is_empty() {
anyhow::bail!("no agent CLIs found in PATH");
}
let mut agent_idx = *rr_index % agents.len();
let mut found = false;
let mut skipped_last_idx: Option<usize> = None;
for offset in 0..agents.len() {
let idx = (agent_idx + offset) % agents.len();
let candidate = &agents[idx];
if crate::engine::cooldown::is_agent_in_cooldown(candidate) {
continue;
}
if agents.len() > 1 {
if let Some(ref last) = last_agent {
if candidate == last && offset + 1 < agents.len() {
skipped_last_idx = Some(idx);
continue;
}
}
}
agent_idx = idx;
found = true;
break;
}
if !found {
agent_idx = skipped_last_idx.unwrap_or(*rr_index % agents.len());
}
let agent = agents[agent_idx].clone();
*rr_index = (agent_idx + 1) % agents.len();
*last_agent = Some(agent.clone());
let complexity = extract_complexity_from_labels(&task.labels);
Ok(RouteResult::from_config(
agent,
complexity,
format!(
"round_robin (index {} of {} agents)",
agent_idx,
agents.len()
),
config,
&task.id.0,
))
}
pub(super) fn route_via_weighted_round_robin(
agents: &[String],
weights: &AgentWeights,
config: &RouterConfig,
task: &ExternalTask,
last_agent: &mut Option<String>,
) -> anyhow::Result<RouteResult> {
if agents.is_empty() {
anyhow::bail!("no agent CLIs found in PATH");
}
let uncooled: Vec<String> = agents
.iter()
.filter(|a| !crate::engine::cooldown::is_agent_in_cooldown(a))
.cloned()
.collect();
let candidates = if uncooled.is_empty() {
agents
} else {
&uncooled
};
let agent = weights
.weighted_select(candidates, &task.id.0)
.unwrap_or_else(|| candidates[0].clone());
let weight = weights.get_weight(&agent);
let complexity = extract_complexity_from_labels(&task.labels);
let weight_summary: Vec<String> = weights
.snapshot()
.iter()
.filter(|(a, _, _)| agents.contains(a))
.map(|(a, w, _)| format!("{a}={w:.2}"))
.collect();
*last_agent = Some(agent.clone());
let reason = format!(
"weighted_round_robin (weight={weight:.2}, weights=[{}])",
weight_summary.join(", ")
);
tracing::info!(
task_id = %task.id.0,
agent = %agent,
weight,
"weighted round-robin selected agent"
);
Ok(RouteResult::from_config(
agent, complexity, reason, config, &task.id.0,
))
}
pub(super) fn route_via_fallback(
available_agents: &[String],
config: &RouterConfig,
task: &ExternalTask,
weights: Option<&AgentWeights>,
) -> anyhow::Result<RouteResult> {
if config.fallback_executor == "round_robin" {
if let Some(w) = weights {
if !config.weights.is_empty() {
let mut last = None;
return route_via_weighted_round_robin(
available_agents,
w,
config,
task,
&mut last,
)
.map(|mut r| {
r.reason = format!("router failed; fallback weighted → {}", r.agent);
r
});
}
}
return route_via_round_robin(available_agents, config, task).map(|mut r| {
r.reason = format!("router failed; fallback round_robin → {}", r.agent);
r
});
}
let agent = if available_agents.contains(&config.fallback_executor) {
config.fallback_executor.clone()
} else {
available_agents
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("no agents available"))?
};
let complexity = extract_complexity_from_labels(&task.labels);
Ok(RouteResult::from_config(
agent.clone(),
complexity,
format!("router failed; fallback to {agent}"),
config,
&task.id.0,
))
}