Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ codewhale --provider openrouter --model minimax/minimax-m3
# Xiaomi MiMo
codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_KEY"
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
codewhale --provider xiaomi-mimo speech "Hello from MiMo" --model tts -o hello.wav

# Novita
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ codewhale --provider openrouter --model qwen/qwen3.7-max
# Xiaomi MiMo
codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_MIMO_API_KEY"
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
codewhale --provider xiaomi-mimo speech "???MiMo" --model tts -o hello.wav

# Novita
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
Expand Down
12 changes: 11 additions & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ base_url = "https://api.deepseek.com/beta"
# deepseek-ai/deepseek-v4-flash — default AtlasCloud model ID
# deepseek-reasoner — default Wanjie Ark model ID
# mimo-v2.5-pro — default Xiaomi MiMo model ID
# mimo-v2.5-tts ? Xiaomi MiMo speech/TTS model ID
# mimo-v2.5-tts-voicedesign ? Xiaomi MiMo voice-design TTS model ID
# mimo-v2.5-tts-voiceclone ? Xiaomi MiMo voice-clone TTS model ID
# accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID
# deepseek-ai/DeepSeek-V4-Pro — SiliconFlow hosted Pro model ID
# deepseek-ai/DeepSeek-V4-Flash — SiliconFlow hosted Flash model ID
Expand Down Expand Up @@ -120,6 +123,11 @@ memory_path = "~/.codewhale/memory.md"
# Parsed but currently unused (reserved for future versions):
# tools_file = "./tools.json"

# Xiaomi MiMo speech/TTS defaults. Also configurable with
# XIAOMI_MIMO_SPEECH_OUTPUT_DIR / MIMO_SPEECH_OUTPUT_DIR.
[speech]
# output_dir = "./speech"

# Native tool catalog controls (#2076). By default only the core tool surface
# is loaded into the model context; less common native tools are discoverable
# through ToolSearch and loaded on first use.
Expand Down Expand Up @@ -286,7 +294,9 @@ max_subagents = 10 # optional (1-20)
[providers.xiaomi_mimo]
# api_key = "YOUR_XIAOMI_KEY"
# base_url = "https://api.xiaomimimo.com/v1"
# model = "mimo-v2.5-pro"
# model = "mimo-v2.5-pro" # chat/reasoning
# TTS aliases are also accepted by `codewhale speech`: tts, voice-design, voice-clone
# TTS model IDs: mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts

# Novita AI-hosted inference (https://novita.ai)
[providers.novita]
Expand Down
56 changes: 56 additions & 0 deletions crates/agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,46 @@ impl Default for ModelRegistry {
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "mimo-v2.5-tts".to_string(),
provider: ProviderKind::XiaomiMimo,
aliases: vec![
"tts".to_string(),
"speech".to_string(),
"mimo-tts".to_string(),
],
supports_tools: false,
supports_reasoning: false,
},
ModelInfo {
id: "mimo-v2.5-tts-voicedesign".to_string(),
provider: ProviderKind::XiaomiMimo,
aliases: vec![
"voicedesign".to_string(),
"voice-design".to_string(),
"mimo-voice-design".to_string(),
],
supports_tools: false,
supports_reasoning: false,
},
ModelInfo {
id: "mimo-v2.5-tts-voiceclone".to_string(),
provider: ProviderKind::XiaomiMimo,
aliases: vec![
"voiceclone".to_string(),
"voice-clone".to_string(),
"mimo-voice-clone".to_string(),
],
supports_tools: false,
supports_reasoning: false,
},
ModelInfo {
id: "mimo-v2-tts".to_string(),
provider: ProviderKind::XiaomiMimo,
aliases: vec!["mimo-v2-speech".to_string()],
supports_tools: false,
supports_reasoning: false,
},
ModelInfo {
id: "deepseek/deepseek-v4-pro".to_string(),
provider: ProviderKind::Novita,
Expand Down Expand Up @@ -649,6 +689,22 @@ mod tests {
assert!(resolved.resolved.supports_reasoning);
}

#[test]
fn xiaomi_mimo_tts_aliases_resolve_when_provider_hinted() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("tts"), Some(ProviderKind::XiaomiMimo));
assert_eq!(resolved.resolved.provider, ProviderKind::XiaomiMimo);
assert_eq!(resolved.resolved.id, "mimo-v2.5-tts");
assert!(!resolved.resolved.supports_tools);
assert!(!resolved.resolved.supports_reasoning);

let resolved = registry.resolve(Some("voice-design"), Some(ProviderKind::XiaomiMimo));
assert_eq!(resolved.resolved.id, "mimo-v2.5-tts-voicedesign");

let resolved = registry.resolve(Some("voiceclone"), Some(ProviderKind::XiaomiMimo));
assert_eq!(resolved.resolved.id, "mimo-v2.5-tts-voiceclone");
}

#[test]
fn wanjie_ark_default_uses_reasoner_model_id() {
let registry = ModelRegistry::default();
Expand Down
7 changes: 7 additions & 0 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ enum Commands {
Doctor(TuiPassthroughArgs),
/// List live DeepSeek API models via the TUI binary.
Models(TuiPassthroughArgs),
/// Generate speech audio with Xiaomi MiMo TTS models via the TUI binary.
#[command(visible_alias = "tts")]
Speech(TuiPassthroughArgs),
/// List saved TUI sessions.
Sessions(TuiPassthroughArgs),
/// Resume a saved TUI session.
Expand Down Expand Up @@ -510,6 +513,10 @@ fn run() -> Result<()> {
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
delegate_to_tui(&cli, &resolved_runtime, tui_args("models", args))
}
Some(Commands::Speech(args)) => {
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
delegate_to_tui(&cli, &resolved_runtime, tui_args("speech", args))
}
Some(Commands::Sessions(args)) => {
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
delegate_to_tui(&cli, &resolved_runtime, tui_args("sessions", args))
Expand Down
62 changes: 62 additions & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const OPENROUTER_TENCENT_HY3_PREVIEW_MODEL: &str = "tencent/hy3-preview";
const OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL: &str = "xiaomi/mimo-v2.5-pro";
const OPENROUTER_XIAOMI_MIMO_V2_5_MODEL: &str = "xiaomi/mimo-v2.5";
const DEFAULT_XIAOMI_MIMO_MODEL: &str = "mimo-v2.5-pro";
const XIAOMI_MIMO_TTS_MODEL: &str = "mimo-v2.5-tts";
const XIAOMI_MIMO_TTS_VOICE_DESIGN_MODEL: &str = "mimo-v2.5-tts-voicedesign";
const XIAOMI_MIMO_TTS_VOICE_CLONE_MODEL: &str = "mimo-v2.5-tts-voiceclone";
const XIAOMI_MIMO_V2_TTS_MODEL: &str = "mimo-v2-tts";
const DEFAULT_NOVITA_MODEL: &str = "deepseek/deepseek-v4-pro";
const DEFAULT_NOVITA_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
const DEFAULT_FIREWORKS_MODEL: &str = "accounts/fireworks/models/deepseek-v4-pro";
Expand Down Expand Up @@ -1426,6 +1430,12 @@ pub fn load_project_config(workspace: &Path) -> Option<ConfigToml> {
}

fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
if matches!(provider, ProviderKind::XiaomiMimo)
&& let Some(canonical) = canonical_xiaomi_mimo_model_id(model)
{
return canonical.to_string();
}

if matches!(
provider,
ProviderKind::Atlascloud
Expand Down Expand Up @@ -1500,6 +1510,38 @@ fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
}
}

fn canonical_xiaomi_mimo_model_id(model: &str) -> Option<&'static str> {
let normalized = model.trim().to_ascii_lowercase();
let normalized = normalized.replace(['_', ' '], "-");
match normalized.as_str() {
"mimo"
| DEFAULT_XIAOMI_MIMO_MODEL
| "mimo-v2-5-pro"
| "xiaomi-mimo-v2.5-pro"
| "xiaomi-mimo-v2-5-pro" => Some(DEFAULT_XIAOMI_MIMO_MODEL),
"mimo-v2.5" | "mimo-v25" | "mimo-v2-5" | "xiaomi-mimo-v2.5" | "xiaomi-mimo-v2-5" => {
Some("mimo-v2.5")
}
"mimo-tts" | "mimo-v25-tts" | "mimo-v2.5-tts" | "tts" | "speech" => {
Some(XIAOMI_MIMO_TTS_MODEL)
}
"mimo-tts-voicedesign"
| "mimo-voice-design"
| "mimo-v25-tts-voicedesign"
| "mimo-v2.5-tts-voicedesign"
| "voicedesign"
| "voice-design" => Some(XIAOMI_MIMO_TTS_VOICE_DESIGN_MODEL),
"mimo-tts-voiceclone"
| "mimo-voice-clone"
| "mimo-v25-tts-voiceclone"
| "mimo-v2.5-tts-voiceclone"
| "voiceclone"
| "voice-clone" => Some(XIAOMI_MIMO_TTS_VOICE_CLONE_MODEL),
"mimo-v2-tts" => Some(XIAOMI_MIMO_V2_TTS_MODEL),
_ => None,
}
}

fn canonical_openrouter_recent_model_id(model: &str) -> Option<&'static str> {
let normalized = model.trim().to_ascii_lowercase();
let normalized = normalized.replace(['_', ' '], "-");
Expand Down Expand Up @@ -3263,6 +3305,26 @@ unix_socket_path = "/tmp/cw-hooks.sock"
assert_eq!(resolved.model, DEFAULT_XIAOMI_MIMO_MODEL);
}

#[test]
fn xiaomi_mimo_tts_aliases_resolve_to_canonical_models() {
assert_eq!(
normalize_model_for_provider(ProviderKind::XiaomiMimo, "tts"),
"mimo-v2.5-tts"
);
assert_eq!(
normalize_model_for_provider(ProviderKind::XiaomiMimo, "voice-design"),
"mimo-v2.5-tts-voicedesign"
);
assert_eq!(
normalize_model_for_provider(ProviderKind::XiaomiMimo, "voiceclone"),
"mimo-v2.5-tts-voiceclone"
);
assert_eq!(
normalize_model_for_provider(ProviderKind::XiaomiMimo, "custom-mimo-model"),
"custom-mimo-model"
);
}

#[test]
fn novita_provider_defaults_to_canonical_endpoint_and_model() {
let _lock = env_lock();
Expand Down
Loading