codex_helper_core/
config_routing.rs1use 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}