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
26    for &provider in ALL_PROVIDERS {
27        let binary_name = provider.binary_name();
28        let env_key = provider.env_override_key();
29        let env_override = std::env::var(env_key).ok();
30
31        if resolve_binary(binary_name, env_override.as_deref()).is_err() {
32            debug!(provider = %provider, "Binary not found, skipping");
33            continue;
34        }
35
36        match state.get_runner(provider).await {
37            Ok(runner) => {
38                let provider_name = runner.name();
39                let models = runner.available_models();
40
41                if models.is_empty() {
42                    // Provider has no model list — expose just the provider name
43                    data.push(ModelObject {
44                        id: provider_name.to_owned(),
45                        object: "model",
46                        owned_by: provider_name.to_owned(),
47                    });
48                } else {
49                    for model in models {
50                        data.push(ModelObject {
51                            id: format!("{provider_name}:{model}"),
52                            object: "model",
53                            owned_by: provider_name.to_owned(),
54                        });
55                    }
56                }
57            }
58            Err(e) => {
59                debug!(provider = %provider, error = %e, "Failed to create runner");
60            }
61        }
62    }
63
64    let resp = ModelsResponse {
65        object: "list",
66        data,
67    };
68
69    (StatusCode::OK, Json(resp))
70}