Skip to main content

embacle_server/
models.rs

1// ABOUTME: GET /v1/models handler listing available providers and their models
2// ABOUTME: Probes installed CLI binaries and returns OpenAI-compatible model list
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright (c) 2026 dravr.ai
6
7use axum::extract::State;
8use axum::http::StatusCode;
9use axum::response::IntoResponse;
10use axum::Json;
11use embacle::discovery::resolve_binary;
12use tracing::debug;
13
14use crate::openai_types::{ModelObject, ModelsResponse};
15use crate::runner::ALL_PROVIDERS;
16use crate::state::SharedState;
17
18/// Handle GET /v1/models
19///
20/// Probes each known provider to check if its CLI binary is installed.
21/// For installed providers, lists their available models in `OpenAI` format
22/// with provider prefix (e.g., "copilot:gpt-4o").
23pub async fn handle(State(state): State<SharedState>) -> impl IntoResponse {
24    let mut data = Vec::new();
25    let state_guard = state.read().await;
26
27    for &provider in ALL_PROVIDERS {
28        let binary_name = provider.binary_name();
29        let env_key = provider.env_override_key();
30        let env_override = std::env::var(env_key).ok();
31
32        if resolve_binary(binary_name, env_override.as_deref()).is_err() {
33            debug!(provider = %provider, "Binary not found, skipping");
34            continue;
35        }
36
37        match state_guard.get_runner(provider).await {
38            Ok(runner) => {
39                let provider_name = runner.name();
40                let models = runner.available_models();
41
42                if models.is_empty() {
43                    // Provider has no model list — expose just the provider name
44                    data.push(ModelObject {
45                        id: provider_name.to_owned(),
46                        object: "model",
47                        owned_by: provider_name.to_owned(),
48                    });
49                } else {
50                    for model in models {
51                        data.push(ModelObject {
52                            id: format!("{provider_name}:{model}"),
53                            object: "model",
54                            owned_by: provider_name.to_owned(),
55                        });
56                    }
57                }
58            }
59            Err(e) => {
60                debug!(provider = %provider, error = %e, "Failed to create runner");
61            }
62        }
63    }
64
65    let resp = ModelsResponse {
66        object: "list",
67        data,
68    };
69
70    (StatusCode::OK, Json(resp))
71}