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