Skip to main content

embacle_server/
health.rs

1// ABOUTME: GET /health handler checking provider availability and readiness
2// ABOUTME: Returns per-provider status and HTTP 200 if any provider is ready, 503 if none
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright (c) 2026 dravr.ai
6
7use std::collections::HashMap;
8
9use axum::extract::State;
10use axum::http::StatusCode;
11use axum::response::IntoResponse;
12use axum::Json;
13use embacle::discovery::resolve_binary;
14use tracing::debug;
15
16use crate::openai_types::HealthResponse;
17use crate::runner::ALL_PROVIDERS;
18use crate::state::SharedState;
19
20/// Handle GET /health
21///
22/// Checks which providers have their CLI binary available on the system.
23/// For available providers, attempts a health check via the runner.
24/// Returns HTTP 200 if at least one provider is ready, 503 if none.
25pub async fn handle(State(state): State<SharedState>) -> impl IntoResponse {
26    let mut providers = HashMap::new();
27    let mut any_ready = false;
28    let state_guard = state.read().await;
29
30    for &provider in ALL_PROVIDERS {
31        let binary_name = provider.binary_name();
32        let env_key = provider.env_override_key();
33        let env_override = std::env::var(env_key).ok();
34
35        if resolve_binary(binary_name, env_override.as_deref()).is_err() {
36            providers.insert(provider.to_string(), "not_found".to_owned());
37            continue;
38        }
39
40        match state_guard.get_runner(provider).await {
41            Ok(runner) => match runner.health_check().await {
42                Ok(true) => {
43                    providers.insert(provider.to_string(), "ready".to_owned());
44                    any_ready = true;
45                }
46                Ok(false) => {
47                    providers.insert(provider.to_string(), "not_ready".to_owned());
48                }
49                Err(e) => {
50                    debug!(provider = %provider, error = %e, "Health check failed");
51                    providers.insert(provider.to_string(), format!("error: {e}"));
52                }
53            },
54            Err(e) => {
55                debug!(provider = %provider, error = %e, "Failed to create runner");
56                providers.insert(provider.to_string(), format!("error: {e}"));
57            }
58        }
59    }
60
61    let status_str = if any_ready { "ok" } else { "degraded" };
62    let http_status = if any_ready {
63        StatusCode::OK
64    } else {
65        StatusCode::SERVICE_UNAVAILABLE
66    };
67
68    let resp = HealthResponse {
69        status: status_str,
70        providers,
71    };
72
73    (http_status, Json(resp))
74}