Skip to main content

codex_helper_core/
config_routing.rs

1use super::*;
2
3#[derive(Debug, Clone, Serialize)]
4pub struct RoutingCandidate {
5    pub name: String,
6    pub alias: Option<String>,
7    pub level: u8,
8    pub enabled: bool,
9    pub active: bool,
10    pub upstreams: usize,
11}
12
13#[derive(Debug, Clone, Serialize)]
14pub struct ServiceRoutingExplanation {
15    #[serde(rename = "active_station")]
16    pub active_station: Option<String>,
17    pub mode: &'static str,
18    #[serde(rename = "eligible_stations")]
19    pub eligible_stations: Vec<RoutingCandidate>,
20    #[serde(rename = "fallback_station")]
21    pub fallback_station: Option<RoutingCandidate>,
22}
23
24fn routing_candidate(
25    name: &str,
26    svc: &ServiceConfig,
27    active_name: Option<&str>,
28) -> RoutingCandidate {
29    RoutingCandidate {
30        name: name.to_string(),
31        alias: svc.alias.clone(),
32        level: svc.level.clamp(1, 10),
33        enabled: svc.enabled,
34        active: active_name.is_some_and(|active| active == name),
35        upstreams: svc.upstreams.len(),
36    }
37}
38
39fn active_or_first_station(mgr: &ServiceConfigManager) -> Option<(String, &ServiceConfig)> {
40    if let Some(active_name) = mgr.active.as_deref()
41        && let Some(svc) = mgr.station(active_name)
42    {
43        return Some((active_name.to_string(), svc));
44    }
45
46    mgr.stations()
47        .iter()
48        .min_by_key(|(name, _)| *name)
49        .map(|(name, svc)| (name.clone(), svc))
50}
51
52pub fn explain_service_routing(mgr: &ServiceConfigManager) -> ServiceRoutingExplanation {
53    let active_name = mgr.active.as_deref();
54    let mut eligible = mgr
55        .stations()
56        .iter()
57        .filter(|(name, svc)| {
58            !svc.upstreams.is_empty()
59                && (svc.enabled || active_name.is_some_and(|active| active == name.as_str()))
60        })
61        .map(|(name, svc)| routing_candidate(name, svc, active_name))
62        .collect::<Vec<_>>();
63
64    let has_multi_level = {
65        let mut levels = eligible
66            .iter()
67            .map(|candidate| candidate.level)
68            .collect::<Vec<_>>();
69        levels.sort_unstable();
70        levels.dedup();
71        levels.len() > 1
72    };
73
74    if !has_multi_level {
75        eligible.sort_by(|a, b| a.name.cmp(&b.name));
76        if let Some(active) = active_name
77            && let Some(pos) = eligible
78                .iter()
79                .position(|candidate| candidate.name == active)
80        {
81            let item = eligible.remove(pos);
82            eligible.insert(0, item);
83        }
84
85        if !eligible.is_empty() {
86            return ServiceRoutingExplanation {
87                active_station: mgr.active.clone(),
88                mode: "single_level_multi",
89                eligible_stations: eligible,
90                fallback_station: None,
91            };
92        }
93
94        return ServiceRoutingExplanation {
95            active_station: mgr.active.clone(),
96            mode: if active_or_first_station(mgr).is_some() {
97                "single_level_fallback_active_station"
98            } else {
99                "single_level_empty"
100            },
101            eligible_stations: Vec::new(),
102            fallback_station: active_or_first_station(mgr)
103                .map(|(name, svc)| routing_candidate(&name, svc, active_name)),
104        };
105    }
106
107    eligible.sort_by(|a, b| {
108        a.level
109            .cmp(&b.level)
110            .then_with(|| b.active.cmp(&a.active))
111            .then_with(|| a.name.cmp(&b.name))
112    });
113
114    if !eligible.is_empty() {
115        return ServiceRoutingExplanation {
116            active_station: mgr.active.clone(),
117            mode: "multi_level",
118            eligible_stations: eligible,
119            fallback_station: None,
120        };
121    }
122
123    ServiceRoutingExplanation {
124        active_station: mgr.active.clone(),
125        mode: if active_or_first_station(mgr).is_some() {
126            "multi_level_fallback_active_station"
127        } else {
128            "multi_level_empty"
129        },
130        eligible_stations: Vec::new(),
131        fallback_station: active_or_first_station(mgr)
132            .map(|(name, svc)| routing_candidate(&name, svc, active_name)),
133    }
134}