1use crate::config::{ProxyConfig, resolve_service_profile};
2use crate::dashboard_core::{
3 ApiV1OperatorSummary, ControlPlaneSurfaceCapabilities, ControlProfileOption,
4 HostLocalControlPlaneCapabilities, OperatorProfileSummary, OperatorRetrySummary,
5 OperatorRuntimeSummary, OperatorSummaryCounts, RemoteAdminAccessCapabilities,
6 SharedControlPlaneCapabilities, build_operator_health_summary, build_profile_options_from_mgr,
7 build_provider_options_from_view, build_station_options_from_mgr,
8 summarize_recent_retry_observations,
9};
10use crate::state::{SessionIdentityCardBuildInputs, build_session_identity_cards_from_parts};
11
12use super::ProxyService;
13use super::control_plane_manifest::api_v1_operator_summary_links;
14use super::control_plane_service::{load_persisted_proxy_settings_v2, service_view_v2};
15use super::profile_defaults::{
16 configured_active_station_name, effective_active_station_name, effective_default_profile_name,
17};
18
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20pub struct ProfilesResponse {
21 pub default_profile: Option<String>,
22 pub configured_default_profile: Option<String>,
23 pub profiles: Vec<ControlProfileOption>,
24}
25
26#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27pub struct RuntimeStatusResponse {
28 pub runtime_source_path: String,
29 pub config_path: String,
30 pub loaded_at_ms: u64,
31 pub source_mtime_ms: Option<u64>,
32 pub retry: crate::config::ResolvedRetryConfig,
33}
34
35#[derive(serde::Serialize)]
36pub(super) struct RetryConfigResponse {
37 configured: crate::config::RetryConfig,
38 resolved: crate::config::ResolvedRetryConfig,
39}
40
41#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
42pub struct ReloadResult {
43 pub reloaded: bool,
44 pub status: RuntimeStatusResponse,
45}
46
47pub(super) async fn build_operator_summary(
48 proxy: &ProxyService,
49 surface_capabilities: ControlPlaneSurfaceCapabilities,
50 shared_capabilities: SharedControlPlaneCapabilities,
51 host_local_capabilities: HostLocalControlPlaneCapabilities,
52 remote_admin_access: RemoteAdminAccessCapabilities,
53) -> ApiV1OperatorSummary {
54 let cfg = proxy.config.snapshot().await;
55 let mgr = proxy.service_manager(cfg.as_ref());
56 let configured_active_station = configured_active_station_name(mgr);
57 let effective_active_station = effective_active_station_name(mgr);
58 let configured_default_profile = mgr.default_profile.clone();
59 let configured_retry = cfg.retry.clone();
60 let resolved_retry = configured_retry.resolve();
61 let loaded_at_ms = proxy.config.last_loaded_at_ms();
62 let source_mtime_ms = proxy.config.last_mtime_ms().await;
63 let (
64 active,
65 recent,
66 global_station_override,
67 global_route_target_override,
68 session_model,
69 session_station,
70 session_effort,
71 session_service_tier,
72 session_bindings,
73 session_route_affinities,
74 session_stats,
75 default_profile,
76 station_meta_overrides,
77 station_state_overrides,
78 provider_upstream_overrides,
79 station_health,
80 health_checks,
81 lb_view,
82 ) = tokio::join!(
83 proxy.state.list_active_requests(),
84 proxy.state.list_recent_finished(200),
85 proxy.state.get_global_station_override(),
86 proxy.state.get_global_route_target_override(),
87 proxy.state.list_session_model_overrides(),
88 proxy.state.list_session_station_overrides(),
89 proxy.state.list_session_effort_overrides(),
90 proxy.state.list_session_service_tier_overrides(),
91 proxy.state.list_session_bindings(),
92 proxy.state.list_session_route_affinities(),
93 proxy.state.list_session_stats(),
94 effective_default_profile_name(proxy.state.as_ref(), proxy.service_name, mgr),
95 proxy.state.get_station_meta_overrides(proxy.service_name),
96 proxy
97 .state
98 .get_station_runtime_state_overrides(proxy.service_name),
99 proxy.state.get_upstream_meta_overrides(proxy.service_name),
100 proxy.state.get_station_health(proxy.service_name),
101 proxy.state.list_health_checks(proxy.service_name),
102 proxy.state.get_lb_view(),
103 );
104 let session_cards = build_session_identity_cards_from_parts(SessionIdentityCardBuildInputs {
105 active: &active,
106 recent: &recent,
107 overrides: &session_effort,
108 station_overrides: &session_station,
109 model_overrides: &session_model,
110 service_tier_overrides: &session_service_tier,
111 bindings: &session_bindings,
112 route_affinities: &session_route_affinities,
113 global_station_override: global_station_override.as_deref(),
114 stats: &session_stats,
115 });
116 let default_profile_summary = default_profile.as_deref().and_then(|profile_name| {
117 resolve_service_profile(mgr, profile_name)
118 .ok()
119 .map(|profile| OperatorProfileSummary {
120 name: profile_name.to_string(),
121 station: profile.station,
122 model: profile.model,
123 reasoning_effort: profile.reasoning_effort,
124 service_tier: profile.service_tier.clone(),
125 fast_mode: profile.service_tier.as_deref() == Some("priority"),
126 })
127 });
128 let stations =
129 build_station_options_from_mgr(mgr, &station_meta_overrides, &station_state_overrides);
130 let profiles = build_profile_options_from_mgr(mgr, default_profile.as_deref());
131 let health =
132 build_operator_health_summary(&stations, &station_health, &health_checks, &lb_view);
133 let providers = load_persisted_proxy_settings_v2()
134 .await
135 .ok()
136 .map(|persisted_cfg| {
137 build_provider_options_from_view(
138 proxy.service_name,
139 service_view_v2(&persisted_cfg, proxy.service_name),
140 &provider_upstream_overrides,
141 )
142 })
143 .unwrap_or_default();
144 let retry_observations = summarize_recent_retry_observations(&recent);
145
146 ApiV1OperatorSummary {
147 api_version: 1,
148 service_name: proxy.service_name.to_string(),
149 runtime: OperatorRuntimeSummary {
150 runtime_loaded_at_ms: Some(loaded_at_ms),
151 runtime_source_mtime_ms: source_mtime_ms,
152 configured_active_station,
153 effective_active_station,
154 global_station_override,
155 global_route_target_override,
156 configured_default_profile,
157 default_profile,
158 default_profile_summary,
159 },
160 counts: OperatorSummaryCounts {
161 active_requests: active.len(),
162 recent_requests: recent.len(),
163 sessions: session_cards.len(),
164 stations: mgr.stations().len(),
165 profiles: mgr.profiles.len(),
166 providers: providers.len(),
167 },
168 retry: OperatorRetrySummary {
169 configured_profile: configured_retry.profile,
170 supports_write: surface_capabilities.retry_config,
171 upstream_max_attempts: resolved_retry.upstream.max_attempts,
172 provider_max_attempts: resolved_retry.route.max_attempts,
173 allow_cross_station_before_first_output: resolved_retry
174 .allow_cross_station_before_first_output,
175 recent_retried_requests: retry_observations.recent_retried_requests,
176 recent_cross_station_failovers: retry_observations.recent_cross_station_failovers,
177 recent_same_station_retries: retry_observations.recent_same_station_retries,
178 recent_fast_mode_requests: retry_observations.recent_fast_mode_requests,
179 },
180 health: Some(health),
181 session_cards,
182 stations: stations.clone(),
183 profiles,
184 providers,
185 links: Some(api_v1_operator_summary_links()),
186 surface_capabilities,
187 shared_capabilities,
188 host_local_capabilities,
189 remote_admin_access,
190 }
191}
192
193pub(super) async fn make_profiles_response(proxy: &ProxyService) -> ProfilesResponse {
194 let cfg = proxy.config.snapshot().await;
195 let mgr = proxy.service_manager(cfg.as_ref());
196 let default_profile =
197 effective_default_profile_name(proxy.state.as_ref(), proxy.service_name, mgr).await;
198 ProfilesResponse {
199 default_profile: default_profile.clone(),
200 configured_default_profile: mgr.default_profile.clone(),
201 profiles: build_profile_options_from_mgr(mgr, default_profile.as_deref()),
202 }
203}
204
205pub(super) async fn build_runtime_status_response(proxy: &ProxyService) -> RuntimeStatusResponse {
206 let cfg = proxy.config.snapshot().await;
207 let runtime_source_path = crate::config::config_file_path().display().to_string();
208 RuntimeStatusResponse {
209 runtime_source_path: runtime_source_path.clone(),
210 config_path: runtime_source_path,
211 loaded_at_ms: proxy.config.last_loaded_at_ms(),
212 source_mtime_ms: proxy.config.last_mtime_ms().await,
213 retry: cfg.retry.resolve(),
214 }
215}
216
217pub(super) fn build_retry_config_response(cfg: &ProxyConfig) -> RetryConfigResponse {
218 RetryConfigResponse {
219 configured: cfg.retry.clone(),
220 resolved: cfg.retry.resolve(),
221 }
222}
223
224pub(super) fn build_reload_result(reloaded: bool, status: RuntimeStatusResponse) -> ReloadResult {
225 ReloadResult { reloaded, status }
226}