use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Model {
MiMoV25Pro,
MiMoV25,
MiMoV25Tts,
MiMoV25TtsVoiceDesign,
MiMoV25TtsVoiceClone,
MiMoV2Pro,
MiMoV2Omni,
MiMoV2Tts,
MiMoV2Flash,
}
impl Model {
pub fn as_str(&self) -> &'static str {
match self {
Model::MiMoV25Pro => "mimo-v2.5-pro",
Model::MiMoV25 => "mimo-v2.5",
Model::MiMoV25Tts => "mimo-v2.5-tts",
Model::MiMoV25TtsVoiceDesign => "mimo-v2.5-tts-voicedesign",
Model::MiMoV25TtsVoiceClone => "mimo-v2.5-tts-voiceclone",
Model::MiMoV2Pro => "mimo-v2-pro",
Model::MiMoV2Omni => "mimo-v2-omni",
Model::MiMoV2Tts => "mimo-v2-tts",
Model::MiMoV2Flash => "mimo-v2-flash",
}
}
}
impl std::fmt::Display for Model {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<&str> for Model {
fn from(s: &str) -> Self {
match s {
"mimo-v2.5-pro" => Model::MiMoV25Pro,
"mimo-v2.5" => Model::MiMoV25,
"mimo-v2.5-tts" => Model::MiMoV25Tts,
"mimo-v2.5-tts-voicedesign" => Model::MiMoV25TtsVoiceDesign,
"mimo-v2.5-tts-voiceclone" => Model::MiMoV25TtsVoiceClone,
"mimo-v2-pro" => Model::MiMoV2Pro,
"mimo-v2-omni" => Model::MiMoV2Omni,
"mimo-v2-tts" => Model::MiMoV2Tts,
"mimo-v2-flash" => Model::MiMoV2Flash,
_ => Model::MiMoV2Flash,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ThinkingType {
Enabled,
Disabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thinking {
#[serde(rename = "type")]
pub thinking_type: ThinkingType,
}
impl Thinking {
pub fn new(thinking_type: ThinkingType) -> Self {
Self { thinking_type }
}
pub fn enabled() -> Self {
Self::new(ThinkingType::Enabled)
}
pub fn disabled() -> Self {
Self::new(ThinkingType::Disabled)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoice {
Auto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ResponseFormatType {
Text,
JsonObject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseFormat {
#[serde(rename = "type")]
pub format_type: ResponseFormatType,
}
impl ResponseFormat {
pub fn new(format_type: ResponseFormatType) -> Self {
Self { format_type }
}
pub fn text() -> Self {
Self::new(ResponseFormatType::Text)
}
pub fn json_object() -> Self {
Self::new(ResponseFormatType::JsonObject)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Stop {
Single(String),
Multiple(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatRequest {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<Audio>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_completion_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Stop>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<Thinking>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none", rename = "webSearchEnabled")]
pub web_search_enabled: Option<bool>,
}
impl Default for ChatRequest {
fn default() -> Self {
Self {
model: "mimo-v2-flash".to_string(),
messages: Vec::new(),
audio: None,
frequency_penalty: None,
max_completion_tokens: None,
presence_penalty: None,
response_format: None,
stop: None,
stream: None,
thinking: None,
temperature: None,
tool_choice: None,
tools: None,
top_p: None,
web_search_enabled: None,
}
}
}
impl ChatRequest {
pub fn new(model: impl Into<String>) -> Self {
Self {
model: model.into(),
messages: Vec::new(),
audio: None,
frequency_penalty: None,
max_completion_tokens: None,
presence_penalty: None,
response_format: None,
stop: None,
stream: None,
thinking: None,
temperature: None,
tool_choice: None,
tools: None,
top_p: None,
web_search_enabled: None,
}
}
pub fn flash() -> Self {
Self::new(Model::MiMoV2Flash.as_str())
}
pub fn pro() -> Self {
Self::new(Model::MiMoV2Pro.as_str())
}
pub fn v25_pro() -> Self {
Self::new(Model::MiMoV25Pro.as_str())
}
pub fn v25() -> Self {
Self::new(Model::MiMoV25.as_str())
}
pub fn omni() -> Self {
Self::new(Model::MiMoV2Omni.as_str())
}
pub fn v25_tts() -> Self {
Self::new(Model::MiMoV25Tts.as_str())
}
pub fn v25_tts_voicedesign() -> Self {
Self::new(Model::MiMoV25TtsVoiceDesign.as_str())
}
pub fn v25_tts_voiceclone() -> Self {
Self::new(Model::MiMoV25TtsVoiceClone.as_str())
}
pub fn tts() -> Self {
Self::new(Model::MiMoV2Tts.as_str())
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
pub fn message(mut self, message: Message) -> Self {
self.messages.push(message);
self
}
pub fn messages(mut self, messages: Vec<Message>) -> Self {
let new_has_system = messages
.first()
.is_some_and(|m| m.role == super::message::Role::System);
if new_has_system {
self.messages = messages;
} else {
let existing_system = self.messages
.first()
.filter(|m| m.role == super::message::Role::System)
.cloned();
self.messages = messages;
if let Some(sys_msg) = existing_system {
self.messages.insert(0, sys_msg);
}
}
self
}
pub fn system(mut self, content: impl Into<String>) -> Self {
let sys_msg = Message::system(MessageContent::Text(content.into()));
if self.messages.first().is_some_and(|m| m.role == super::message::Role::System) {
self.messages[0] = sys_msg;
} else {
self.messages.insert(0, sys_msg);
}
self
}
pub fn user(mut self, content: impl Into<String>) -> Self {
self.messages
.push(Message::user(MessageContent::Text(content.into())));
self
}
pub fn assistant(mut self, content: impl Into<String>) -> Self {
self.messages
.push(Message::assistant(MessageContent::Text(content.into())));
self
}
pub fn audio(mut self, audio: Audio) -> Self {
self.audio = Some(audio);
self
}
pub fn frequency_penalty(mut self, penalty: f32) -> Self {
self.frequency_penalty = Some(penalty);
self
}
pub fn max_completion_tokens(mut self, tokens: u32) -> Self {
self.max_completion_tokens = Some(tokens);
self
}
pub fn presence_penalty(mut self, penalty: f32) -> Self {
self.presence_penalty = Some(penalty);
self
}
pub fn response_format(mut self, format: ResponseFormat) -> Self {
self.response_format = Some(format);
self
}
pub fn stop(mut self, stop: Stop) -> Self {
self.stop = Some(stop);
self
}
pub fn stream(mut self, stream: bool) -> Self {
self.stream = Some(stream);
self
}
pub fn thinking(mut self, thinking: Thinking) -> Self {
self.thinking = Some(thinking);
self
}
pub fn enable_thinking(mut self) -> Self {
self.thinking = Some(Thinking::enabled());
self
}
pub fn disable_thinking(mut self) -> Self {
self.thinking = Some(Thinking::disabled());
self
}
pub fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
self.tool_choice = Some(choice);
self
}
pub fn tool(mut self, tool: Tool) -> Self {
if self.tools.is_none() {
self.tools = Some(Vec::new());
}
self.tools.as_mut().unwrap().push(tool);
self
}
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub fn top_p(mut self, top_p: f32) -> Self {
self.top_p = Some(top_p);
self
}
pub fn web_search_enabled(mut self, enabled: bool) -> Self {
self.web_search_enabled = Some(enabled);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_as_str() {
assert_eq!(Model::MiMoV25Pro.as_str(), "mimo-v2.5-pro");
assert_eq!(Model::MiMoV25.as_str(), "mimo-v2.5");
assert_eq!(Model::MiMoV25Tts.as_str(), "mimo-v2.5-tts");
assert_eq!(
Model::MiMoV25TtsVoiceDesign.as_str(),
"mimo-v2.5-tts-voicedesign"
);
assert_eq!(
Model::MiMoV25TtsVoiceClone.as_str(),
"mimo-v2.5-tts-voiceclone"
);
assert_eq!(Model::MiMoV2Pro.as_str(), "mimo-v2-pro");
assert_eq!(Model::MiMoV2Omni.as_str(), "mimo-v2-omni");
assert_eq!(Model::MiMoV2Tts.as_str(), "mimo-v2-tts");
assert_eq!(Model::MiMoV2Flash.as_str(), "mimo-v2-flash");
}
#[test]
fn test_model_from_str() {
assert_eq!(Model::from("mimo-v2.5-pro"), Model::MiMoV25Pro);
assert_eq!(Model::from("mimo-v2.5-tts"), Model::MiMoV25Tts);
assert_eq!(Model::from("mimo-v2-pro"), Model::MiMoV2Pro);
assert_eq!(Model::from("mimo-v2-flash"), Model::MiMoV2Flash);
assert_eq!(Model::from("unknown"), Model::MiMoV2Flash);
}
#[test]
fn test_model_display() {
assert_eq!(format!("{}", Model::MiMoV25Pro), "mimo-v2.5-pro");
}
#[test]
fn test_thinking() {
let enabled = Thinking::enabled();
assert_eq!(enabled.thinking_type, ThinkingType::Enabled);
let disabled = Thinking::disabled();
assert_eq!(disabled.thinking_type, ThinkingType::Disabled);
}
#[test]
fn test_response_format() {
let text = ResponseFormat::text();
assert_eq!(text.format_type, ResponseFormatType::Text);
let json = ResponseFormat::json_object();
assert_eq!(json.format_type, ResponseFormatType::JsonObject);
}
#[test]
fn test_chat_request_builder() {
let request = ChatRequest::flash()
.system("You are a helpful assistant.")
.user("Hello!")
.temperature(0.7)
.max_completion_tokens(1024);
assert_eq!(request.model, "mimo-v2-flash");
assert_eq!(request.messages.len(), 2);
assert_eq!(request.temperature, Some(0.7));
assert_eq!(request.max_completion_tokens, Some(1024));
}
#[test]
fn test_system_then_messages() {
let messages = vec![
Message::user(MessageContent::Text("Hello".into())),
Message::assistant(MessageContent::Text("Hi".into())),
];
let request = ChatRequest::flash()
.system("You are a helpful assistant.")
.messages(messages);
assert_eq!(request.messages.len(), 3);
assert_eq!(request.messages[0].role, Role::System);
assert_eq!(request.messages[1].role, Role::User);
assert_eq!(request.messages[2].role, Role::Assistant);
}
#[test]
fn test_messages_then_system() {
let messages = vec![
Message::user(MessageContent::Text("Hello".into())),
Message::assistant(MessageContent::Text("Hi".into())),
];
let request = ChatRequest::flash()
.messages(messages)
.system("You are a helpful assistant.");
assert_eq!(request.messages.len(), 3);
assert_eq!(request.messages[0].role, Role::System);
assert_eq!(request.messages[1].role, Role::User);
assert_eq!(request.messages[2].role, Role::Assistant);
}
#[test]
fn test_system_replaces_existing_system() {
let request = ChatRequest::flash()
.system("Old prompt")
.system("New prompt")
.user("Hello");
assert_eq!(request.messages.len(), 2);
assert_eq!(request.messages[0].role, Role::System);
assert_eq!(request.messages[1].role, Role::User);
}
#[test]
fn test_messages_preserves_system() {
let messages = vec![
Message::user(MessageContent::Text("Question".into())),
];
let request = ChatRequest::flash()
.system("Old system.")
.messages(messages);
assert_eq!(request.messages.len(), 2);
assert_eq!(request.messages[0].role, Role::System);
assert_eq!(request.messages[1].role, Role::User);
}
#[test]
fn test_messages_with_own_system_replaces() {
let messages = vec![
Message::system(MessageContent::Text("New system.".into())),
Message::user(MessageContent::Text("Question".into())),
];
let request = ChatRequest::flash()
.system("Old system.")
.messages(messages);
assert_eq!(request.messages.len(), 2);
assert_eq!(request.messages[0].role, Role::System);
assert_eq!(request.messages[1].role, Role::User);
}
#[test]
fn test_chat_request_serialization() {
let request = ChatRequest::new("mimo-v2-flash")
.user("Hello!")
.temperature(0.5);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"model\":\"mimo-v2-flash\""));
assert!(json.contains("\"temperature\":0.5"));
}
}