1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct ListModelsResponse {
8 pub data: Vec<Model>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct Model {
14 pub id: String,
16 pub name: String,
17 pub created: i64,
19 pub input_modalities: Vec<InputModality>,
20 pub output_modalities: Vec<OutputModality>,
21 pub quantization: Quantization,
22 pub context_length: u64,
24 pub max_output_length: u64,
26 pub pricing: Pricing,
27 pub supported_sampling_parameters: Vec<SamplingParameter>,
28 pub supported_features: Vec<Feature>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub openrouter: Option<OpenRouterInfo>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub hugging_face_id: Option<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub datacenters: Option<Vec<Datacenter>>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41pub struct OpenRouterInfo {
42 pub slug: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48pub struct Pricing {
49 pub prompt: String,
50 pub completion: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub image: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub request: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub input_cache_read: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub input_cache_write: Option<String>,
59}
60
61impl Pricing {
62 pub fn new(prompt: impl Into<String>, completion: impl Into<String>) -> Self {
63 Self {
64 prompt: prompt.into(),
65 completion: completion.into(),
66 image: None,
67 request: None,
68 input_cache_read: None,
69 input_cache_write: None,
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "snake_case")]
76pub enum InputModality {
77 Text,
78 File,
79 Image,
80 Audio,
81 Video,
82}
83
84#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
85#[serde(rename_all = "snake_case")]
86pub enum OutputModality {
87 Text,
88 Image,
89}
90
91#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
92#[serde(rename_all = "snake_case")]
93pub enum Quantization {
94 Int4,
95 Int8,
96 Fp4,
97 Fp6,
98 Fp8,
99 Fp16,
100 Bf16,
101 Fp32,
102}
103
104#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
105#[serde(rename_all = "snake_case")]
106pub enum SamplingParameter {
107 Temperature,
108 TopP,
109 TopK,
110 RepetitionPenalty,
111 FrequencyPenalty,
112 PresencePenalty,
113 Stop,
114 Seed,
115}
116
117#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
118#[serde(rename_all = "snake_case")]
119pub enum Feature {
120 Tools,
121 JsonMode,
122 StructuredOutputs,
123 WebSearch,
124 Reasoning,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
128pub struct Datacenter {
129 pub country_code: String,
131}
132
133impl Datacenter {
134 pub fn new(country_code: impl Into<String>) -> Self {
135 Self {
136 country_code: country_code.into(),
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_round_trip() {
147 let original = ListModelsResponse {
148 data: vec![Model {
149 id: "org/model".to_string(),
150 name: "Model".to_string(),
151 created: 1700000000,
152 input_modalities: vec![InputModality::Text, InputModality::Image],
153 output_modalities: vec![OutputModality::Text],
154 quantization: Quantization::Fp16,
155 context_length: 128000,
156 max_output_length: 4096,
157 pricing: Pricing::new("0.001", "0.002"),
158 supported_sampling_parameters: vec![SamplingParameter::Temperature],
159 supported_features: vec![Feature::Tools],
160 openrouter: None,
161 hugging_face_id: None,
162 description: Some("Test".to_string()),
163 datacenters: Some(vec![Datacenter::new("US")]),
164 }],
165 };
166 let json = serde_json::to_string(&original).unwrap();
167 let parsed: ListModelsResponse = serde_json::from_str(&json).unwrap();
168 assert_eq!(original, parsed);
169 }
170
171 #[test]
172 fn test_quantization_serialization() {
173 assert_eq!(
174 serde_json::to_string(&Quantization::Int4).unwrap(),
175 "\"int4\""
176 );
177 assert_eq!(
178 serde_json::to_string(&Quantization::Bf16).unwrap(),
179 "\"bf16\""
180 );
181 assert_eq!(
182 serde_json::to_string(&Quantization::Fp32).unwrap(),
183 "\"fp32\""
184 );
185 }
186
187 #[test]
188 fn test_feature_serialization() {
189 assert_eq!(
190 serde_json::to_string(&Feature::JsonMode).unwrap(),
191 "\"json_mode\""
192 );
193 assert_eq!(
194 serde_json::to_string(&Feature::StructuredOutputs).unwrap(),
195 "\"structured_outputs\""
196 );
197 }
198
199 #[test]
200 fn test_sampling_parameter_serialization() {
201 assert_eq!(
202 serde_json::to_string(&SamplingParameter::TopP).unwrap(),
203 "\"top_p\""
204 );
205 assert_eq!(
206 serde_json::to_string(&SamplingParameter::RepetitionPenalty).unwrap(),
207 "\"repetition_penalty\""
208 );
209 }
210}