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 std::env;
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::{ModelObject, ModelsResponse};
17use crate::runner::ALL_PROVIDERS;
18use crate::state::SharedState;
19
20/// Handle GET /v1/models
21///
22/// Probes each known provider to check if its CLI binary is installed.
23/// For installed providers, lists their available models in `OpenAI` format
24/// with provider prefix (e.g., "copilot:gpt-4o").
25pub async fn handle(State(state): State<SharedState>) -> impl IntoResponse {
26    let mut data = Vec::new();
27
28    for &provider in ALL_PROVIDERS {
29        let binary_name = provider.binary_name();
30        let env_key = provider.env_override_key();
31        let env_override = env::var(env_key).ok();
32
33        if resolve_binary(binary_name, env_override.as_deref()).is_err() {
34            debug!(provider = %provider, "Binary not found, skipping");
35            continue;
36        }
37
38        match state.get_runner(provider).await {
39            Ok(runner) => {
40                let provider_name = runner.name();
41                let models = runner.available_models();
42
43                if models.is_empty() {
44                    // Provider has no model list — expose just the provider name
45                    data.push(ModelObject {
46                        id: provider_name.to_owned(),
47                        object: "model",
48                        owned_by: provider_name.to_owned(),
49                    });
50                } else {
51                    for model in models {
52                        data.push(ModelObject {
53                            id: format!("{provider_name}:{model}"),
54                            object: "model",
55                            owned_by: provider_name.to_owned(),
56                        });
57                    }
58                }
59            }
60            Err(e) => {
61                debug!(provider = %provider, error = %e, "Failed to create runner");
62            }
63        }
64    }
65
66    let resp = ModelsResponse {
67        object: "list",
68        data,
69    };
70
71    (StatusCode::OK, Json(resp))
72}