codex-helper-core 0.15.0

Core library for codex-helper.
Documentation
use super::*;

#[derive(Debug, Clone, Serialize)]
pub struct RoutingCandidate {
    pub name: String,
    pub alias: Option<String>,
    pub level: u8,
    pub enabled: bool,
    pub active: bool,
    pub upstreams: usize,
}

#[derive(Debug, Clone, Serialize)]
pub struct ServiceRoutingExplanation {
    #[serde(rename = "active_station")]
    pub active_station: Option<String>,
    pub mode: &'static str,
    #[serde(rename = "eligible_stations")]
    pub eligible_stations: Vec<RoutingCandidate>,
    #[serde(rename = "fallback_station")]
    pub fallback_station: Option<RoutingCandidate>,
}

fn routing_candidate(
    name: &str,
    svc: &ServiceConfig,
    active_name: Option<&str>,
) -> RoutingCandidate {
    RoutingCandidate {
        name: name.to_string(),
        alias: svc.alias.clone(),
        level: svc.level.clamp(1, 10),
        enabled: svc.enabled,
        active: active_name.is_some_and(|active| active == name),
        upstreams: svc.upstreams.len(),
    }
}

fn active_or_first_station(mgr: &ServiceConfigManager) -> Option<(String, &ServiceConfig)> {
    if let Some(active_name) = mgr.active.as_deref()
        && let Some(svc) = mgr.station(active_name)
    {
        return Some((active_name.to_string(), svc));
    }

    mgr.stations()
        .iter()
        .min_by_key(|(name, _)| *name)
        .map(|(name, svc)| (name.clone(), svc))
}

pub fn explain_service_routing(mgr: &ServiceConfigManager) -> ServiceRoutingExplanation {
    let active_name = mgr.active.as_deref();
    let mut eligible = mgr
        .stations()
        .iter()
        .filter(|(name, svc)| {
            !svc.upstreams.is_empty()
                && (svc.enabled || active_name.is_some_and(|active| active == name.as_str()))
        })
        .map(|(name, svc)| routing_candidate(name, svc, active_name))
        .collect::<Vec<_>>();

    let has_multi_level = {
        let mut levels = eligible
            .iter()
            .map(|candidate| candidate.level)
            .collect::<Vec<_>>();
        levels.sort_unstable();
        levels.dedup();
        levels.len() > 1
    };

    if !has_multi_level {
        eligible.sort_by(|a, b| a.name.cmp(&b.name));
        if let Some(active) = active_name
            && let Some(pos) = eligible
                .iter()
                .position(|candidate| candidate.name == active)
        {
            let item = eligible.remove(pos);
            eligible.insert(0, item);
        }

        if !eligible.is_empty() {
            return ServiceRoutingExplanation {
                active_station: mgr.active.clone(),
                mode: "single_level_multi",
                eligible_stations: eligible,
                fallback_station: None,
            };
        }

        return ServiceRoutingExplanation {
            active_station: mgr.active.clone(),
            mode: if active_or_first_station(mgr).is_some() {
                "single_level_fallback_active_station"
            } else {
                "single_level_empty"
            },
            eligible_stations: Vec::new(),
            fallback_station: active_or_first_station(mgr)
                .map(|(name, svc)| routing_candidate(&name, svc, active_name)),
        };
    }

    eligible.sort_by(|a, b| {
        a.level
            .cmp(&b.level)
            .then_with(|| b.active.cmp(&a.active))
            .then_with(|| a.name.cmp(&b.name))
    });

    if !eligible.is_empty() {
        return ServiceRoutingExplanation {
            active_station: mgr.active.clone(),
            mode: "multi_level",
            eligible_stations: eligible,
            fallback_station: None,
        };
    }

    ServiceRoutingExplanation {
        active_station: mgr.active.clone(),
        mode: if active_or_first_station(mgr).is_some() {
            "multi_level_fallback_active_station"
        } else {
            "multi_level_empty"
        },
        eligible_stations: Vec::new(),
        fallback_station: active_or_first_station(mgr)
            .map(|(name, svc)| routing_candidate(&name, svc, active_name)),
    }
}