systemprompt_api/routes/gateway/
models.rs1use axum::Json;
2use axum::http::StatusCode;
3use serde::Serialize;
4use std::collections::BTreeMap;
5use systemprompt_config::ProfileBootstrap;
6
7#[derive(Debug, Serialize)]
8pub struct RootResponse {
9 pub service: &'static str,
10 pub version: &'static str,
11 pub endpoints: Vec<&'static str>,
12}
13
14pub async fn root() -> Json<RootResponse> {
15 Json(RootResponse {
16 service: "systemprompt-gateway",
17 version: env!("CARGO_PKG_VERSION"),
18 endpoints: vec!["/v1/models", "/v1/messages"],
19 })
20}
21
22#[derive(Debug, Serialize)]
23pub struct ModelEntry {
24 #[serde(rename = "type")]
25 pub kind: &'static str,
26 pub id: String,
27 pub display_name: String,
28 pub created_at: String,
29}
30
31#[derive(Debug, Serialize)]
32pub struct ModelsResponse {
33 pub data: Vec<ModelEntry>,
34 pub has_more: bool,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub first_id: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub last_id: Option<String>,
39}
40
41pub async fn list() -> Result<Json<ModelsResponse>, (StatusCode, String)> {
42 let profile = ProfileBootstrap::get().map_err(|e| {
43 (
44 StatusCode::SERVICE_UNAVAILABLE,
45 format!("Profile not ready: {e}"),
46 )
47 })?;
48
49 let gateway = profile
50 .gateway
51 .as_ref()
52 .and_then(systemprompt_models::profile::GatewayState::resolved)
53 .filter(|g| g.enabled)
54 .ok_or_else(|| (StatusCode::NOT_FOUND, "Gateway not enabled".to_owned()))?;
55
56 let mut by_id: BTreeMap<String, ModelEntry> = BTreeMap::new();
57
58 if let Some(catalog) = gateway.catalog.as_ref() {
59 for m in &catalog.models {
60 by_id.insert(
61 m.id.as_str().to_owned(),
62 ModelEntry {
63 kind: "model",
64 display_name: m
65 .display_name
66 .clone()
67 .unwrap_or_else(|| humanize_model_id(m.id.as_str())),
68 id: m.id.as_str().to_owned(),
69 created_at: "1970-01-01T00:00:00Z".to_owned(),
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}