models_dev/
types.rs

1//! Data structures for the models.dev API schema.
2//!
3//! This module contains exact schema matches for the models.dev API response structures.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Top-level response structure from the models.dev API.
9/// 
10/// This is a map where keys are provider IDs and values are provider information.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ModelsDevResponse {
13    /// Provider information mapped by provider ID.
14    #[serde(flatten)]
15    pub providers: HashMap<String, Provider>,
16}
17
18impl ModelsDevResponse {
19    /// Get the providers as a vector.
20    pub fn providers_vec(&self) -> Vec<Provider> {
21        self.providers.values().cloned().collect()
22    }
23}
24
25/// Provider information from the models.dev API.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Provider {
28    /// The unique identifier for the provider.
29    pub id: String,
30
31    /// The display name of the provider.
32    pub name: String,
33
34    /// NPM package information for the provider.
35    pub npm: String,
36
37    /// Environment variables required for the provider.
38    pub env: Vec<String>,
39
40    /// Documentation information.
41    pub doc: String,
42
43    /// API configuration information.
44    #[serde(default)]
45    pub api: Option<String>,
46
47    /// Available models for this provider.
48    pub models: HashMap<String, Model>,
49}
50
51/// Model information from the models.dev API.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Model {
54    /// The unique identifier for the model.
55    pub id: String,
56
57    /// The display name of the model.
58    pub name: String,
59
60    /// Whether the model supports attachments.
61    pub attachment: bool,
62
63    /// Whether the model supports reasoning.
64    pub reasoning: bool,
65
66    /// Whether the model supports temperature.
67    pub temperature: bool,
68
69    /// Whether the model supports tool calls.
70    pub tool_call: bool,
71
72    /// Knowledge cutoff date.
73    #[serde(default)]
74    pub knowledge: Option<String>,
75
76    /// Release date.
77    #[serde(default)]
78    pub release_date: Option<String>,
79
80    /// Last updated date.
81    #[serde(default)]
82    pub last_updated: Option<String>,
83
84    /// Supported modalities for this model.
85    pub modalities: Modalities,
86
87    /// Whether the model uses open weights.
88    #[serde(default)]
89    pub open_weights: bool,
90
91    /// Cost information for using this model.
92    #[serde(default)]
93    pub cost: Option<ModelCost>,
94
95    /// Limits for this model.
96    pub limit: ModelLimit,
97}
98
99/// Cost information for a model.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ModelCost {
102    /// Cost per 1M input tokens.
103    pub input: f64,
104
105    /// Cost per 1M output tokens.
106    pub output: f64,
107
108    /// Cost per 1M cache read tokens (if supported).
109    #[serde(default)]
110    pub cache_read: Option<f64>,
111}
112
113/// Limits for a model.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ModelLimit {
116    /// Maximum context window size in tokens.
117    pub context: u32,
118
119    /// Maximum output tokens per request.
120    pub output: u32,
121}
122
123/// Supported modalities for a model.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct Modalities {
126    /// Supported input modalities.
127    pub input: Vec<String>,
128
129    /// Supported output modalities.
130    pub output: Vec<String>,
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_models_dev_response_deserialization() {
139        let json = r#"{
140            "deepseek": {
141                "id": "deepseek",
142                "name": "DeepSeek",
143                "npm": "@ai-sdk/openai-compatible",
144                "env": ["DEEPSEEK_API_KEY"],
145                "doc": "https://platform.deepseek.com/api-docs/pricing",
146                "api": "https://api.deepseek.com",
147                "models": {
148                    "deepseek-chat": {
149                        "id": "deepseek-chat",
150                        "name": "DeepSeek Chat",
151                        "attachment": true,
152                        "reasoning": false,
153                        "temperature": true,
154                        "tool_call": true,
155                        "modalities": {
156                            "input": ["text"],
157                            "output": ["text"]
158                        },
159                        "open_weights": false,
160                        "cost": {
161                            "input": 0.57,
162                            "output": 1.68,
163                            "cache_read": 0.07
164                        },
165                        "limit": {
166                            "context": 128000,
167                            "output": 8192
168                        }
169                    }
170                }
171            }
172        }"#;
173
174        let response: ModelsDevResponse = serde_json::from_str(json).unwrap();
175        assert_eq!(response.providers.len(), 1);
176        assert!(response.providers.contains_key("deepseek"));
177        
178        let provider = &response.providers["deepseek"];
179        assert_eq!(provider.id, "deepseek");
180        assert_eq!(provider.name, "DeepSeek");
181        assert_eq!(provider.npm, "@ai-sdk/openai-compatible");
182        assert_eq!(provider.env.len(), 1);
183        assert_eq!(provider.env[0], "DEEPSEEK_API_KEY");
184        
185        let providers_vec = response.providers_vec();
186        assert_eq!(providers_vec.len(), 1);
187        assert_eq!(providers_vec[0].id, "deepseek");
188    }
189
190    #[test]
191    fn test_model_cost_with_optional_fields() {
192        let json = r#"{
193            "input": 0.01,
194            "output": 0.02,
195            "cache_read": 0.005
196        }"#;
197
198        let cost: ModelCost = serde_json::from_str(json).unwrap();
199        assert_eq!(cost.input, 0.01);
200        assert_eq!(cost.output, 0.02);
201        assert_eq!(cost.cache_read, Some(0.005));
202    }
203
204    #[test]
205    fn test_model_cost_without_optional_fields() {
206        let json = r#"{
207            "input": 0.01,
208            "output": 0.02
209        }"#;
210
211        let cost: ModelCost = serde_json::from_str(json).unwrap();
212        assert_eq!(cost.input, 0.01);
213        assert_eq!(cost.output, 0.02);
214        assert_eq!(cost.cache_read, None);
215    }
216
217    #[test]
218    fn test_modalities() {
219        let json = r#"{
220            "input": ["text", "image"],
221            "output": ["text"]
222        }"#;
223
224        let modalities: Modalities = serde_json::from_str(json).unwrap();
225        assert_eq!(modalities.input, vec!["text", "image"]);
226        assert_eq!(modalities.output, vec!["text"]);
227    }
228}