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