use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelsDevResponse {
#[serde(flatten)]
pub providers: HashMap<String, Provider>,
}
impl ModelsDevResponse {
pub fn providers_vec(&self) -> Vec<Provider> {
self.providers.values().cloned().collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provider {
pub id: String,
pub name: String,
pub npm: String,
pub env: Vec<String>,
pub doc: String,
#[serde(default)]
pub api: Option<String>,
pub models: HashMap<String, Model>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Model {
pub id: String,
pub name: String,
pub attachment: bool,
pub reasoning: bool,
pub temperature: bool,
pub tool_call: bool,
#[serde(default)]
pub knowledge: Option<String>,
#[serde(default)]
pub release_date: Option<String>,
#[serde(default)]
pub last_updated: Option<String>,
pub modalities: Modalities,
#[serde(default)]
pub open_weights: bool,
#[serde(default)]
pub cost: Option<ModelCost>,
pub limit: ModelLimit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelCost {
pub input: f64,
pub output: f64,
#[serde(default)]
pub cache_read: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelLimit {
pub context: u32,
pub output: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Modalities {
pub input: Vec<String>,
pub output: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_models_dev_response_deserialization() {
let json = r#"{
"deepseek": {
"id": "deepseek",
"name": "DeepSeek",
"npm": "@ai-sdk/openai-compatible",
"env": ["DEEPSEEK_API_KEY"],
"doc": "https://platform.deepseek.com/api-docs/pricing",
"api": "https://api.deepseek.com",
"models": {
"deepseek-chat": {
"id": "deepseek-chat",
"name": "DeepSeek Chat",
"attachment": true,
"reasoning": false,
"temperature": true,
"tool_call": true,
"modalities": {
"input": ["text"],
"output": ["text"]
},
"open_weights": false,
"cost": {
"input": 0.57,
"output": 1.68,
"cache_read": 0.07
},
"limit": {
"context": 128000,
"output": 8192
}
}
}
}
}"#;
let response: ModelsDevResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.providers.len(), 1);
assert!(response.providers.contains_key("deepseek"));
let provider = &response.providers["deepseek"];
assert_eq!(provider.id, "deepseek");
assert_eq!(provider.name, "DeepSeek");
assert_eq!(provider.npm, "@ai-sdk/openai-compatible");
assert_eq!(provider.env.len(), 1);
assert_eq!(provider.env[0], "DEEPSEEK_API_KEY");
let providers_vec = response.providers_vec();
assert_eq!(providers_vec.len(), 1);
assert_eq!(providers_vec[0].id, "deepseek");
}
#[test]
fn test_model_cost_with_optional_fields() {
let json = r#"{
"input": 0.01,
"output": 0.02,
"cache_read": 0.005
}"#;
let cost: ModelCost = serde_json::from_str(json).unwrap();
assert_eq!(cost.input, 0.01);
assert_eq!(cost.output, 0.02);
assert_eq!(cost.cache_read, Some(0.005));
}
#[test]
fn test_model_cost_without_optional_fields() {
let json = r#"{
"input": 0.01,
"output": 0.02
}"#;
let cost: ModelCost = serde_json::from_str(json).unwrap();
assert_eq!(cost.input, 0.01);
assert_eq!(cost.output, 0.02);
assert_eq!(cost.cache_read, None);
}
#[test]
fn test_modalities() {
let json = r#"{
"input": ["text", "image"],
"output": ["text"]
}"#;
let modalities: Modalities = serde_json::from_str(json).unwrap();
assert_eq!(modalities.input, vec!["text", "image"]);
assert_eq!(modalities.output, vec!["text"]);
}
}