1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ModelsDevResponse {
13 #[serde(flatten)]
15 pub providers: HashMap<String, Provider>,
16}
17
18impl ModelsDevResponse {
19 pub fn providers_vec(&self) -> Vec<Provider> {
21 self.providers.values().cloned().collect()
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Provider {
28 pub id: String,
30
31 pub name: String,
33
34 pub npm: String,
36
37 pub env: Vec<String>,
39
40 pub doc: String,
42
43 #[serde(default)]
45 pub api: Option<String>,
46
47 pub models: HashMap<String, Model>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Model {
54 pub id: String,
56
57 pub name: String,
59
60 pub attachment: bool,
62
63 pub reasoning: bool,
65
66 pub temperature: bool,
68
69 pub tool_call: bool,
71
72 #[serde(default)]
74 pub knowledge: Option<String>,
75
76 #[serde(default)]
78 pub release_date: Option<String>,
79
80 #[serde(default)]
82 pub last_updated: Option<String>,
83
84 pub modalities: Modalities,
86
87 #[serde(default)]
89 pub open_weights: bool,
90
91 #[serde(default)]
93 pub cost: Option<ModelCost>,
94
95 pub limit: ModelLimit,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ModelCost {
102 pub input: f64,
104
105 pub output: f64,
107
108 #[serde(default)]
110 pub cache_read: Option<f64>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ModelLimit {
116 pub context: u32,
118
119 pub output: u32,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct Modalities {
126 pub input: Vec<String>,
128
129 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}