1use super::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8#[serde(rename_all = "kebab-case")]
9pub enum Model {
10 MiMoV2Pro,
12 MiMoV2Omni,
14 MiMoV2Tts,
16 MiMoV2Flash,
18}
19
20impl Model {
21 pub fn as_str(&self) -> &'static str {
23 match self {
24 Model::MiMoV2Pro => "mimo-v2-pro",
25 Model::MiMoV2Omni => "mimo-v2-omni",
26 Model::MiMoV2Tts => "mimo-v2-tts",
27 Model::MiMoV2Flash => "mimo-v2-flash",
28 }
29 }
30}
31
32impl std::fmt::Display for Model {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(f, "{}", self.as_str())
35 }
36}
37
38impl From<&str> for Model {
39 fn from(s: &str) -> Self {
40 match s {
41 "mimo-v2-pro" => Model::MiMoV2Pro,
42 "mimo-v2-omni" => Model::MiMoV2Omni,
43 "mimo-v2-tts" => Model::MiMoV2Tts,
44 "mimo-v2-flash" => Model::MiMoV2Flash,
45 _ => Model::MiMoV2Flash,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
52#[serde(rename_all = "lowercase")]
53pub enum ThinkingType {
54 Enabled,
56 Disabled,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Thinking {
63 #[serde(rename = "type")]
65 pub thinking_type: ThinkingType,
66}
67
68impl Thinking {
69 pub fn new(thinking_type: ThinkingType) -> Self {
71 Self { thinking_type }
72 }
73
74 pub fn enabled() -> Self {
76 Self::new(ThinkingType::Enabled)
77 }
78
79 pub fn disabled() -> Self {
81 Self::new(ThinkingType::Disabled)
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "lowercase")]
88pub enum ToolChoice {
89 Auto,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
95#[serde(rename_all = "lowercase")]
96pub enum ResponseFormatType {
97 Text,
99 JsonObject,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ResponseFormat {
106 #[serde(rename = "type")]
108 pub format_type: ResponseFormatType,
109}
110
111impl ResponseFormat {
112 pub fn new(format_type: ResponseFormatType) -> Self {
114 Self { format_type }
115 }
116
117 pub fn text() -> Self {
119 Self::new(ResponseFormatType::Text)
120 }
121
122 pub fn json_object() -> Self {
124 Self::new(ResponseFormatType::JsonObject)
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(untagged)]
131pub enum Stop {
132 Single(String),
134 Multiple(Vec<String>),
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ChatRequest {
141 pub model: String,
143 pub messages: Vec<Message>,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub audio: Option<Audio>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub frequency_penalty: Option<f32>,
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub max_completion_tokens: Option<u32>,
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub presence_penalty: Option<f32>,
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub response_format: Option<ResponseFormat>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub stop: Option<Stop>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub stream: Option<bool>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub thinking: Option<Thinking>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub temperature: Option<f32>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub tool_choice: Option<ToolChoice>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub tools: Option<Vec<Tool>>,
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub top_p: Option<f32>,
181 #[serde(skip_serializing_if = "Option::is_none", rename = "webSearchEnabled")]
187 pub web_search_enabled: Option<bool>,
188}
189
190impl Default for ChatRequest {
191 fn default() -> Self {
192 Self {
193 model: "mimo-v2-flash".to_string(),
194 messages: Vec::new(),
195 audio: None,
196 frequency_penalty: None,
197 max_completion_tokens: None,
198 presence_penalty: None,
199 response_format: None,
200 stop: None,
201 stream: None,
202 thinking: None,
203 temperature: None,
204 tool_choice: None,
205 tools: None,
206 top_p: None,
207 web_search_enabled: None,
208 }
209 }
210}
211
212impl ChatRequest {
213 pub fn new(model: impl Into<String>) -> Self {
215 Self {
216 model: model.into(),
217 messages: Vec::new(),
218 audio: None,
219 frequency_penalty: None,
220 max_completion_tokens: None,
221 presence_penalty: None,
222 response_format: None,
223 stop: None,
224 stream: None,
225 thinking: None,
226 temperature: None,
227 tool_choice: None,
228 tools: None,
229 top_p: None,
230 web_search_enabled: None,
231 }
232 }
233
234 pub fn flash() -> Self {
236 Self::new(Model::MiMoV2Flash.as_str())
237 }
238
239 pub fn pro() -> Self {
241 Self::new(Model::MiMoV2Pro.as_str())
242 }
243
244 pub fn omni() -> Self {
246 Self::new(Model::MiMoV2Omni.as_str())
247 }
248
249 pub fn tts() -> Self {
251 Self::new(Model::MiMoV2Tts.as_str())
252 }
253
254 pub fn model(mut self, model: impl Into<String>) -> Self {
256 self.model = model.into();
257 self
258 }
259
260 pub fn message(mut self, message: Message) -> Self {
262 self.messages.push(message);
263 self
264 }
265
266 pub fn messages(mut self, messages: Vec<Message>) -> Self {
268 self.messages = messages;
269 self
270 }
271
272 pub fn system(mut self, content: impl Into<String>) -> Self {
274 self.messages.push(Message::system(MessageContent::Text(content.into())));
275 self
276 }
277
278 pub fn user(mut self, content: impl Into<String>) -> Self {
280 self.messages.push(Message::user(MessageContent::Text(content.into())));
281 self
282 }
283
284 pub fn assistant(mut self, content: impl Into<String>) -> Self {
286 self.messages.push(Message::assistant(MessageContent::Text(content.into())));
287 self
288 }
289
290 pub fn audio(mut self, audio: Audio) -> Self {
292 self.audio = Some(audio);
293 self
294 }
295
296 pub fn frequency_penalty(mut self, penalty: f32) -> Self {
298 self.frequency_penalty = Some(penalty);
299 self
300 }
301
302 pub fn max_completion_tokens(mut self, tokens: u32) -> Self {
304 self.max_completion_tokens = Some(tokens);
305 self
306 }
307
308 pub fn presence_penalty(mut self, penalty: f32) -> Self {
310 self.presence_penalty = Some(penalty);
311 self
312 }
313
314 pub fn response_format(mut self, format: ResponseFormat) -> Self {
316 self.response_format = Some(format);
317 self
318 }
319
320 pub fn stop(mut self, stop: Stop) -> Self {
322 self.stop = Some(stop);
323 self
324 }
325
326 pub fn stream(mut self, stream: bool) -> Self {
328 self.stream = Some(stream);
329 self
330 }
331
332 pub fn thinking(mut self, thinking: Thinking) -> Self {
334 self.thinking = Some(thinking);
335 self
336 }
337
338 pub fn enable_thinking(mut self) -> Self {
340 self.thinking = Some(Thinking::enabled());
341 self
342 }
343
344 pub fn disable_thinking(mut self) -> Self {
346 self.thinking = Some(Thinking::disabled());
347 self
348 }
349
350 pub fn temperature(mut self, temperature: f32) -> Self {
352 self.temperature = Some(temperature);
353 self
354 }
355
356 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
358 self.tool_choice = Some(choice);
359 self
360 }
361
362 pub fn tool(mut self, tool: Tool) -> Self {
364 if self.tools.is_none() {
365 self.tools = Some(Vec::new());
366 }
367 self.tools.as_mut().unwrap().push(tool);
368 self
369 }
370
371 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
373 self.tools = Some(tools);
374 self
375 }
376
377 pub fn top_p(mut self, top_p: f32) -> Self {
379 self.top_p = Some(top_p);
380 self
381 }
382
383 pub fn web_search_enabled(mut self, enabled: bool) -> Self {
389 self.web_search_enabled = Some(enabled);
390 self
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_model_as_str() {
400 assert_eq!(Model::MiMoV2Pro.as_str(), "mimo-v2-pro");
401 assert_eq!(Model::MiMoV2Omni.as_str(), "mimo-v2-omni");
402 assert_eq!(Model::MiMoV2Tts.as_str(), "mimo-v2-tts");
403 assert_eq!(Model::MiMoV2Flash.as_str(), "mimo-v2-flash");
404 }
405
406 #[test]
407 fn test_model_from_str() {
408 assert_eq!(Model::from("mimo-v2-pro"), Model::MiMoV2Pro);
409 assert_eq!(Model::from("mimo-v2-flash"), Model::MiMoV2Flash);
410 assert_eq!(Model::from("unknown"), Model::MiMoV2Flash);
411 }
412
413 #[test]
414 fn test_model_display() {
415 assert_eq!(format!("{}", Model::MiMoV2Pro), "mimo-v2-pro");
416 }
417
418 #[test]
419 fn test_thinking() {
420 let enabled = Thinking::enabled();
421 assert_eq!(enabled.thinking_type, ThinkingType::Enabled);
422
423 let disabled = Thinking::disabled();
424 assert_eq!(disabled.thinking_type, ThinkingType::Disabled);
425 }
426
427 #[test]
428 fn test_response_format() {
429 let text = ResponseFormat::text();
430 assert_eq!(text.format_type, ResponseFormatType::Text);
431
432 let json = ResponseFormat::json_object();
433 assert_eq!(json.format_type, ResponseFormatType::JsonObject);
434 }
435
436 #[test]
437 fn test_chat_request_builder() {
438 let request = ChatRequest::flash()
439 .system("You are a helpful assistant.")
440 .user("Hello!")
441 .temperature(0.7)
442 .max_completion_tokens(1024);
443
444 assert_eq!(request.model, "mimo-v2-flash");
445 assert_eq!(request.messages.len(), 2);
446 assert_eq!(request.temperature, Some(0.7));
447 assert_eq!(request.max_completion_tokens, Some(1024));
448 }
449
450 #[test]
451 fn test_chat_request_serialization() {
452 let request = ChatRequest::new("mimo-v2-flash")
453 .user("Hello!")
454 .temperature(0.5);
455
456 let json = serde_json::to_string(&request).unwrap();
457 assert!(json.contains("\"model\":\"mimo-v2-flash\""));
458 assert!(json.contains("\"temperature\":0.5"));
459 }
460}