Skip to main content

openai_protocol/
models.rs

1//! OpenAI-compatible `/v1/models` response types.
2//!
3//! Provides [`ListModelsResponse`] built from in-memory [`ModelCard`] data,
4//! ensuring every router returns the same format.
5
6use std::collections::HashSet;
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use super::{model_card::ModelCard, worker::ProviderType};
12
13/// A single model entry in the `/v1/models` response.
14#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
15pub struct ModelObject {
16    /// Model identifier (e.g., "meta-llama/Llama-3.1-8B-Instruct").
17    pub id: String,
18    /// Always `"model"`.
19    pub object: String,
20    /// Unix timestamp of model creation (0 = unknown).
21    pub created: i64,
22    /// Who owns/hosts the model.
23    pub owned_by: String,
24}
25
26/// Response body for `GET /v1/models`.
27#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
28pub struct ListModelsResponse {
29    /// Always `"list"`.
30    pub object: String,
31    /// Available models.
32    pub data: Vec<ModelObject>,
33}
34
35impl ListModelsResponse {
36    /// Build a response from an iterator of [`ModelCard`]s.
37    ///
38    /// Deduplicates by model ID (first occurrence wins).
39    pub fn from_model_cards(cards: impl IntoIterator<Item = ModelCard>) -> Self {
40        let mut seen = HashSet::new();
41        let data = cards
42            .into_iter()
43            .filter(|card| seen.insert(card.id.clone()))
44            .map(ModelCard::into_model_object)
45            .collect();
46        Self {
47            object: "list".to_owned(),
48            data,
49        }
50    }
51
52    /// Parse an upstream `/v1/models` JSON response into [`ModelCard`]s.
53    ///
54    /// Handles both OpenAI and Anthropic response schemas — both use
55    /// `data[].id` for the model identifier. Returns an empty vec if
56    /// the JSON does not contain a valid `data` array.
57    pub fn parse_upstream(json: &Value, provider: Option<ProviderType>) -> Vec<ModelCard> {
58        let Some(data) = json.get("data").and_then(|d| d.as_array()) else {
59            return Vec::new();
60        };
61        data.iter()
62            .filter_map(|m| m.get("id").and_then(|id| id.as_str()))
63            .map(|id| {
64                let mut card = ModelCard::new(id);
65                card.provider.clone_from(&provider);
66                card
67            })
68            .collect()
69    }
70}