//! Inbound OpenAI Models adapter: `GET /v1/models`. //! Lists everything a client can address: the virtual `cortiq-auto`, the //! configured `[[models]]` pool, and the managed local `[[cmf.servers]]`. //! //! The managed CMF servers belong here because they are routable model ids like //! any other — omitting them left OpenAI clients (Open WebUI among them) unable //! to pin a local model even though the gateway would happily serve it. use crate::error::{GatewayError, Result}; use crate::state::SharedState; use axum::{extract::State, response::IntoResponse, routing::get, Json, Router}; pub fn routes() -> Router { Router::new().route("/v1/models", get(handler)) } fn now_secs() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() } async fn handler(State(state): State) -> Result { let live = state.live(); if !live.cfg.protocols.openai_models { return Err(GatewayError::InvalidRequest( "openai_models protocol is disabled".into(), )); } let created = now_secs(); let mut data = vec![serde_json::json!({ "id": "cortiq-auto", "object": "model", "created": created, "owned_by": "cortiq", })]; let mut seen: std::collections::HashSet = std::collections::HashSet::from(["cortiq-auto".to_string()]); for m in &live.cfg.models { if !seen.insert(m.id.clone()) { continue; } data.push(serde_json::json!({ "id": m.id, "object": "model", "created": created, "owned_by": m.provider, })); } // Managed local CMF servers. Health is reported alongside (a non-standard // field clients ignore) rather than used as a filter: a large `.cmf` takes // minutes to load, and a model that vanishes from the list while it warms up // is worse than one marked "not ready yet". let cmf = state.cmf.status(); for s in live.cfg.cmf.effective_servers() { if !seen.insert(s.id.clone()) { continue; // a static [[models]] entry with the same id wins } let status = cmf.servers.iter().find(|st| st.id == s.id); data.push(serde_json::json!({ "id": s.id, "object": "model", "created": created, "owned_by": "cortiq-cmf", "cortiq": { "kind": "cmf", "local": true, "running": status.map(|st| st.running).unwrap_or(false), "healthy": status.map(|st| st.healthy).unwrap_or(false), }, })); } Ok(Json(serde_json::json!({ "object": "list", "data": data }))) }