systemprompt_api/routes/gateway/
models.rs1use axum::Json;
2use axum::http::StatusCode;
3use serde::Serialize;
4use std::collections::BTreeMap;
5use systemprompt_config::ProfileBootstrap;
6use systemprompt_identifiers::ModelId;
7
8#[derive(Debug, Serialize)]
9pub struct RootResponse {
10 pub service: &'static str,
11 pub version: &'static str,
12 pub endpoints: Vec<&'static str>,
13}
14
15pub async fn root() -> Json<RootResponse> {
16 Json(RootResponse {
17 service: "systemprompt-gateway",
18 version: env!("CARGO_PKG_VERSION"),
19 endpoints: vec!["/v1/models", "/v1/messages"],
20 })
21}
22
23#[derive(Debug, Serialize)]
24pub struct ModelEntry {
25 #[serde(rename = "type")]
26 pub kind: &'static str,
27 pub id: String,
28 pub display_name: String,
29 pub created_at: String,
30}
31
32#[derive(Debug, Serialize)]
33pub struct ModelsResponse {
34 pub data: Vec<ModelEntry>,
35 pub has_more: bool,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub first_id: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub last_id: Option<String>,
40}
41
42pub async fn list() -> Result<Json<ModelsResponse>, (StatusCode, String)> {
43 let profile = ProfileBootstrap::get().map_err(|e| {
44 (
45 StatusCode::SERVICE_UNAVAILABLE,
46 format!("Profile not ready: {e}"),
47 )
48 })?;
49
50 profile
51 .gateway
52 .as_ref()
53 .and_then(systemprompt_models::profile::GatewayState::resolved)
54 .filter(|g| g.enabled)
55 .ok_or_else(|| (StatusCode::NOT_FOUND, "Gateway not enabled".to_owned()))?;
56
57 let mut by_id: BTreeMap<String, ModelEntry> = BTreeMap::new();
58
59 for entry in &profile.providers.providers {
60 for m in &entry.models {
61 for id in std::iter::once(m.id.as_str()).chain(m.aliases.iter().map(ModelId::as_str)) {
62 by_id.insert(
63 id.to_owned(),
64 ModelEntry {
65 kind: "model",
66 display_name: humanize_model_id(id),
67 id: id.to_owned(),
68 created_at: "1970-01-01T00:00:00Z".to_owned(),
69 },
70 );
71 }
72 }
73 }
74
75 let entries: Vec<ModelEntry> = by_id.into_values().collect();
76 let first_id = entries.first().map(|e| e.id.clone());
77 let last_id = entries.last().map(|e| e.id.clone());
78
79 Ok(Json(ModelsResponse {
80 data: entries,
81 has_more: false,
82 first_id,
83 last_id,
84 }))
85}
86
87fn humanize_model_id(id: &str) -> String {
88 id.split('-')
89 .map(|part| {
90 let mut chars = part.chars();
91 chars.next().map_or_else(String::new, |c| {
92 c.to_ascii_uppercase().to_string() + chars.as_str()
93 })
94 })
95 .collect::<Vec<_>>()
96 .join(" ")
97}