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 MiMoV25Pro,
12 MiMoV25,
14 MiMoV25Tts,
16 MiMoV25TtsVoiceDesign,
18 MiMoV25TtsVoiceClone,
20 MiMoV2Pro,
22 MiMoV2Omni,
24 MiMoV2Tts,
26 MiMoV2Flash,
28}
29
30impl Model {
31 pub fn as_str(&self) -> &'static str {
33 match self {
34 Model::MiMoV25Pro => "mimo-v2.5-pro",
35 Model::MiMoV25 => "mimo-v2.5",
36 Model::MiMoV25Tts => "mimo-v2.5-tts",
37 Model::MiMoV25TtsVoiceDesign => "mimo-v2.5-tts-voicedesign",
38 Model::MiMoV25TtsVoiceClone => "mimo-v2.5-tts-voiceclone",
39 Model::MiMoV2Pro => "mimo-v2-pro",
40 Model::MiMoV2Omni => "mimo-v2-omni",
41 Model::MiMoV2Tts => "mimo-v2-tts",
42 Model::MiMoV2Flash => "mimo-v2-flash",
43 }
44 }
45}
46
47impl std::fmt::Display for Model {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.as_str())
50 }
51}
52
53impl From<&str> for Model {
54 fn from(s: &str) -> Self {
55 match s {
56 "mimo-v2.5-pro" => Model::MiMoV25Pro,
57 "mimo-v2.5" => Model::MiMoV25,
58 "mimo-v2.5-tts" => Model::MiMoV25Tts,
59 "mimo-v2.5-tts-voicedesign" => Model::MiMoV25TtsVoiceDesign,
60 "mimo-v2.5-tts-voiceclone" => Model::MiMoV25TtsVoiceClone,
61 "mimo-v2-pro" => Model::MiMoV2Pro,
62 "mimo-v2-omni" => Model::MiMoV2Omni,
63 "mimo-v2-tts" => Model::MiMoV2Tts,
64 "mimo-v2-flash" => Model::MiMoV2Flash,
65 _ => Model::MiMoV2Flash,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
72#[serde(rename_all = "lowercase")]
73pub enum ThinkingType {
74 Enabled,
76 Disabled,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Thinking {
83 #[serde(rename = "type")]
85 pub thinking_type: ThinkingType,
86}
87
88impl Thinking {
89 pub fn new(thinking_type: ThinkingType) -> Self {
91 Self { thinking_type }
92 }
93
94 pub fn enabled() -> Self {
96 Self::new(ThinkingType::Enabled)
97 }
98
99 pub fn disabled() -> Self {
101 Self::new(ThinkingType::Disabled)
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "lowercase")]
108pub enum ToolChoice {
109 Auto,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
115#[serde(rename_all = "lowercase")]
116pub enum ResponseFormatType {
117 Text,
119 JsonObject,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ResponseFormat {
126 #[serde(rename = "type")]
128 pub format_type: ResponseFormatType,
129}
130
131impl ResponseFormat {
132 pub fn new(format_type: ResponseFormatType) -> Self {
134 Self { format_type }
135 }
136
137 pub fn text() -> Self {
139 Self::new(ResponseFormatType::Text)
140 }
141
142 pub fn json_object() -> Self {
144 Self::new(ResponseFormatType::JsonObject)
145 }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(untagged)]
151pub enum Stop {
152 Single(String),
154 Multiple(Vec<String>),
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ChatRequest {
161 pub model: String,
163 pub messages: Vec<Message>,
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub audio: Option<Audio>,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub frequency_penalty: Option<f32>,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub max_completion_tokens: Option<u32>,
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub presence_penalty: Option<f32>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub response_format: Option<ResponseFormat>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub stop: Option<Stop>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub stream: Option<bool>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub thinking: Option<Thinking>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub temperature: Option<f32>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub tool_choice: Option<ToolChoice>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub tools: Option<Vec<Tool>>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub top_p: Option<f32>,
201 #[serde(skip_serializing_if = "Option::is_none", rename = "webSearchEnabled")]
207 pub web_search_enabled: Option<bool>,
208}
209
210impl Default for ChatRequest {
211 fn default() -> Self {
212 Self {
213 model: "mimo-v2-flash".to_string(),
214 messages: Vec::new(),
215 audio: None,
216 frequency_penalty: None,
217 max_completion_tokens: None,
218 presence_penalty: None,
219 response_format: None,
220 stop: None,
221 stream: None,
222 thinking: None,
223 temperature: None,
224 tool_choice: None,
225 tools: None,
226 top_p: None,
227 web_search_enabled: None,
228 }
229 }
230}
231
232impl ChatRequest {
233 pub fn new(model: impl Into<String>) -> Self {
235 Self {
236 model: model.into(),
237 messages: Vec::new(),
238 audio: None,
239 frequency_penalty: None,
240 max_completion_tokens: None,
241 presence_penalty: None,
242 response_format: None,
243 stop: None,
244 stream: None,
245 thinking: None,
246 temperature: None,
247 tool_choice: None,
248 tools: None,
249 top_p: None,
250 web_search_enabled: None,
251 }
252 }
253
254 pub fn flash() -> Self {
256 Self::new(Model::MiMoV2Flash.as_str())
257 }
258
259 pub fn pro() -> Self {
261 Self::new(Model::MiMoV2Pro.as_str())
262 }
263
264 pub fn v25_pro() -> Self {
266 Self::new(Model::MiMoV25Pro.as_str())
267 }
268
269 pub fn v25() -> Self {
271 Self::new(Model::MiMoV25.as_str())
272 }
273
274 pub fn omni() -> Self {
276 Self::new(Model::MiMoV2Omni.as_str())
277 }
278
279 pub fn v25_tts() -> Self {
281 Self::new(Model::MiMoV25Tts.as_str())
282 }
283
284 pub fn v25_tts_voicedesign() -> Self {
286 Self::new(Model::MiMoV25TtsVoiceDesign.as_str())
287 }
288
289 pub fn v25_tts_voiceclone() -> Self {
291 Self::new(Model::MiMoV25TtsVoiceClone.as_str())
292 }
293
294 pub fn tts() -> Self {
296 Self::new(Model::MiMoV2Tts.as_str())
297 }
298
299 pub fn model(mut self, model: impl Into<String>) -> Self {
301 self.model = model.into();
302 self
303 }
304
305 pub fn message(mut self, message: Message) -> Self {
307 self.messages.push(message);
308 self
309 }
310
311 pub fn messages(mut self, messages: Vec<Message>) -> Self {
313 self.messages = messages;
314 self
315 }
316
317 pub fn system(mut self, content: impl Into<String>) -> Self {
319 self.messages.push(Message::system(MessageContent::Text(content.into())));
320 self
321 }
322
323 pub fn user(mut self, content: impl Into<String>) -> Self {
325 self.messages.push(Message::user(MessageContent::Text(content.into())));
326 self
327 }
328
329 pub fn assistant(mut self, content: impl Into<String>) -> Self {
331 self.messages.push(Message::assistant(MessageContent::Text(content.into())));
332 self
333 }
334
335 pub fn audio(mut self, audio: Audio) -> Self {
337 self.audio = Some(audio);
338 self
339 }
340
341 pub fn frequency_penalty(mut self, penalty: f32) -> Self {
343 self.frequency_penalty = Some(penalty);
344 self
345 }
346
347 pub fn max_completion_tokens(mut self, tokens: u32) -> Self {
349 self.max_completion_tokens = Some(tokens);
350 self
351 }
352
353 pub fn presence_penalty(mut self, penalty: f32) -> Self {
355 self.presence_penalty = Some(penalty);
356 self
357 }
358
359 pub fn response_format(mut self, format: ResponseFormat) -> Self {
361 self.response_format = Some(format);
362 self
363 }
364
365 pub fn stop(mut self, stop: Stop) -> Self {
367 self.stop = Some(stop);
368 self
369 }
370
371 pub fn stream(mut self, stream: bool) -> Self {
373 self.stream = Some(stream);
374 self
375 }
376
377 pub fn thinking(mut self, thinking: Thinking) -> Self {
379 self.thinking = Some(thinking);
380 self
381 }
382
383 pub fn enable_thinking(mut self) -> Self {
385 self.thinking = Some(Thinking::enabled());
386 self
387 }
388
389 pub fn disable_thinking(mut self) -> Self {
391 self.thinking = Some(Thinking::disabled());
392 self
393 }
394
395 pub fn temperature(mut self, temperature: f32) -> Self {
397 self.temperature = Some(temperature);
398 self
399 }
400
401 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
403 self.tool_choice = Some(choice);
404 self
405 }
406
407 pub fn tool(mut self, tool: Tool) -> Self {
409 if self.tools.is_none() {
410 self.tools = Some(Vec::new());
411 }
412 self.tools.as_mut().unwrap().push(tool);
413 self
414 }
415
416 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
418 self.tools = Some(tools);
419 self
420 }
421
422 pub fn top_p(mut self, top_p: f32) -> Self {
424 self.top_p = Some(top_p);
425 self
426 }
427
428 pub fn web_search_enabled(mut self, enabled: bool) -> Self {
434 self.web_search_enabled = Some(enabled);
435 self
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_model_as_str() {
445 assert_eq!(Model::MiMoV25Pro.as_str(), "mimo-v2.5-pro");
446 assert_eq!(Model::MiMoV25.as_str(), "mimo-v2.5");
447 assert_eq!(Model::MiMoV25Tts.as_str(), "mimo-v2.5-tts");
448 assert_eq!(Model::MiMoV25TtsVoiceDesign.as_str(), "mimo-v2.5-tts-voicedesign");
449 assert_eq!(Model::MiMoV25TtsVoiceClone.as_str(), "mimo-v2.5-tts-voiceclone");
450 assert_eq!(Model::MiMoV2Pro.as_str(), "mimo-v2-pro");
451 assert_eq!(Model::MiMoV2Omni.as_str(), "mimo-v2-omni");
452 assert_eq!(Model::MiMoV2Tts.as_str(), "mimo-v2-tts");
453 assert_eq!(Model::MiMoV2Flash.as_str(), "mimo-v2-flash");
454 }
455
456 #[test]
457 fn test_model_from_str() {
458 assert_eq!(Model::from("mimo-v2.5-pro"), Model::MiMoV25Pro);
459 assert_eq!(Model::from("mimo-v2.5-tts"), Model::MiMoV25Tts);
460 assert_eq!(Model::from("mimo-v2-pro"), Model::MiMoV2Pro);
461 assert_eq!(Model::from("mimo-v2-flash"), Model::MiMoV2Flash);
462 assert_eq!(Model::from("unknown"), Model::MiMoV2Flash);
463 }
464
465 #[test]
466 fn test_model_display() {
467 assert_eq!(format!("{}", Model::MiMoV25Pro), "mimo-v2.5-pro");
468 }
469
470 #[test]
471 fn test_thinking() {
472 let enabled = Thinking::enabled();
473 assert_eq!(enabled.thinking_type, ThinkingType::Enabled);
474
475 let disabled = Thinking::disabled();
476 assert_eq!(disabled.thinking_type, ThinkingType::Disabled);
477 }
478
479 #[test]
480 fn test_response_format() {
481 let text = ResponseFormat::text();
482 assert_eq!(text.format_type, ResponseFormatType::Text);
483
484 let json = ResponseFormat::json_object();
485 assert_eq!(json.format_type, ResponseFormatType::JsonObject);
486 }
487
488 #[test]
489 fn test_chat_request_builder() {
490 let request = ChatRequest::flash()
491 .system("You are a helpful assistant.")
492 .user("Hello!")
493 .temperature(0.7)
494 .max_completion_tokens(1024);
495
496 assert_eq!(request.model, "mimo-v2-flash");
497 assert_eq!(request.messages.len(), 2);
498 assert_eq!(request.temperature, Some(0.7));
499 assert_eq!(request.max_completion_tokens, Some(1024));
500 }
501
502 #[test]
503 fn test_chat_request_serialization() {
504 let request = ChatRequest::new("mimo-v2-flash")
505 .user("Hello!")
506 .temperature(0.5);
507
508 let json = serde_json::to_string(&request).unwrap();
509 assert!(json.contains("\"model\":\"mimo-v2-flash\""));
510 assert!(json.contains("\"temperature\":0.5"));
511 }
512}