feat(config): add Xiaomi MiMo token plan mode
Harvested from PR #2627 by @xyuai. Refs #2621 reported by @springeye.
This commit is contained in:
@@ -65,6 +65,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`SOFYA_API_KEY` fallback, while keeping Sofya scoped to web search rather
|
||||
than model-provider routing (#2790). Thanks @yusufgurdogan for the
|
||||
implementation.
|
||||
- Added Xiaomi MiMo `mode` / `XIAOMI_MIMO_MODE` / `MIMO_MODE` selection for
|
||||
Token Plan region endpoints and pay-as-you-go routing, plus dedicated Token
|
||||
Plan env keys for `tp-*` subscriptions (#2621, #2627). Thanks @springeye for
|
||||
the request and @xyuai for the implementation.
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -253,6 +253,8 @@ codewhale --provider arcee --model trinity-large-thinking
|
||||
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
|
||||
XIAOMI_MIMO_TOKEN_PLAN_API_KEY="tp-..." XIAOMI_MIMO_MODE="token-plan-sgp" \
|
||||
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
|
||||
|
||||
codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"
|
||||
OPENAI_BASE_URL="https://openai-compatible.example/v4" \
|
||||
@@ -503,13 +505,13 @@ Key environment variables:
|
||||
| `DEEPSEEK_PROFILE` | Config profile name |
|
||||
| `DEEPSEEK_MEMORY` | Set to `on` to enable user memory |
|
||||
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | Allow non-local `http://` API base URLs on trusted networks |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `VOLCENGINE_ARK_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `XIAOMI_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `ARCEE_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` / `HUGGINGFACE_API_KEY` / `HF_TOKEN` | Provider auth |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `VOLCENGINE_ARK_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_TOKEN_PLAN_API_KEY` / `MIMO_TOKEN_PLAN_API_KEY` / `XIAOMI_MIMO_API_KEY` / `XIAOMI_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `ARCEE_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` / `HUGGINGFACE_API_KEY` / `HF_TOKEN` | Provider auth |
|
||||
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | Generic OpenAI-compatible endpoint and model ID |
|
||||
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud endpoint and model override |
|
||||
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark endpoint and model override |
|
||||
| `VOLCENGINE_BASE_URL` / `VOLCENGINE_ARK_BASE_URL` / `ARK_BASE_URL` / `VOLCENGINE_MODEL` / `VOLCENGINE_ARK_MODEL` | Volcengine Ark endpoint and model override |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter endpoint override |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo endpoint and model override; Token Plan default is `https://token-plan-sgp.xiaomimimo.com/v1` |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` / `XIAOMI_MIMO_MODE` / `MIMO_MODE` | Xiaomi MiMo endpoint, model, and Token Plan mode override; Token Plan default is `https://token-plan-sgp.xiaomimimo.com/v1` |
|
||||
| `NOVITA_BASE_URL` | Novita endpoint override |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks endpoint override |
|
||||
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | SiliconFlow endpoint and model override |
|
||||
|
||||
+4
-2
@@ -272,6 +272,8 @@ codewhale --provider openrouter --model qwen/qwen3.7-max
|
||||
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
|
||||
XIAOMI_MIMO_TOKEN_PLAN_API_KEY="tp-..." XIAOMI_MIMO_MODE="token-plan-sgp" \
|
||||
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
|
||||
|
||||
# Novita
|
||||
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
|
||||
@@ -427,13 +429,13 @@ DeepSeek 可作为自定义 Agent Client Protocol 服务器运行,供 Zed 等
|
||||
| `DEEPSEEK_PROFILE` | 配置 profile 名称 |
|
||||
| `DEEPSEEK_MEMORY` | 设为 `on` 启用用户记忆 |
|
||||
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | 在可信网络上允许非本机 `http://` API base URL |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` / `HUGGINGFACE_API_KEY` / `HF_TOKEN` | 提供商认证 |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_TOKEN_PLAN_API_KEY` / `MIMO_TOKEN_PLAN_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` / `HUGGINGFACE_API_KEY` / `HF_TOKEN` | 提供商认证 |
|
||||
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | 通用 OpenAI 兼容端点和模型 ID |
|
||||
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud 端点和模型覆盖 |
|
||||
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark 端点和模型覆盖 |
|
||||
| `VOLCENGINE_BASE_URL` / `ARK_BASE_URL` / `VOLCENGINE_MODEL` / `ARK_MODEL` | Volcengine Ark 端点和模型覆盖 |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter 端点覆盖 |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo 端点和模型覆盖 |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` / `XIAOMI_MIMO_MODE` / `MIMO_MODE` | Xiaomi MiMo 端点、模型和 Token Plan 模式覆盖 |
|
||||
| `NOVITA_BASE_URL` | Novita 端点覆盖 |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks 端点覆盖 |
|
||||
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | SiliconFlow 端点和模型覆盖 |
|
||||
|
||||
+6
-1
@@ -239,7 +239,7 @@ max_subagents = 10 # optional (1-20)
|
||||
# Volcengine Ark: VOLCENGINE_API_KEY (or VOLCENGINE_ARK_API_KEY / ARK_API_KEY), VOLCENGINE_BASE_URL, VOLCENGINE_MODEL
|
||||
# OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
|
||||
# Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or XIAOMI_API_KEY / MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL
|
||||
# Token Plan keys (`tp-...`) default to https://token-plan-sgp.xiaomimimo.com/v1.
|
||||
# Token Plan: XIAOMI_MIMO_TOKEN_PLAN_API_KEY (or MIMO_TOKEN_PLAN_API_KEY), XIAOMI_MIMO_MODE/MIMO_MODE
|
||||
# Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
|
||||
# Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
|
||||
# SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL
|
||||
@@ -329,6 +329,11 @@ max_subagents = 10 # optional (1-20)
|
||||
# # base_url = "https://api.xiaomimimo.com/v1" # Pay-as-you-go / sk- keys
|
||||
# model = "mimo-v2.5-pro" # chat/reasoning
|
||||
# Chat model IDs: mimo-v2.5-pro, mimo-v2.5
|
||||
# Token Plan subscriptions use separate tp-* API keys plus api-key auth.
|
||||
# mode = "token-plan-sgp" # default Token Plan endpoint
|
||||
# mode = "token-plan-cn" # China cluster
|
||||
# mode = "token-plan-ams" # Europe cluster
|
||||
# mode = "pay-as-you-go" # standard API / sk- keys
|
||||
# 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
|
||||
|
||||
|
||||
+267
-6
@@ -70,6 +70,9 @@ const DEFAULT_SGLANG_FLASH_MODEL: &str = "deepseek-ai/DeepSeek-V4-Flash";
|
||||
const DEFAULT_OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1";
|
||||
const XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL: &str = "https://api.xiaomimimo.com/v1";
|
||||
const DEFAULT_XIAOMI_MIMO_BASE_URL: &str = "https://token-plan-sgp.xiaomimimo.com/v1";
|
||||
const XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL: &str = "https://token-plan-cn.xiaomimimo.com/v1";
|
||||
const XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL: &str = DEFAULT_XIAOMI_MIMO_BASE_URL;
|
||||
const XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL: &str = "https://token-plan-ams.xiaomimimo.com/v1";
|
||||
const DEFAULT_NOVITA_BASE_URL: &str = "https://api.novita.ai/v1";
|
||||
const DEFAULT_FIREWORKS_BASE_URL: &str = "https://api.fireworks.ai/inference/v1";
|
||||
const DEFAULT_SILICONFLOW_BASE_URL: &str = "https://api.siliconflow.com/v1";
|
||||
@@ -196,6 +199,7 @@ pub struct ProviderConfigToml {
|
||||
pub api_key: Option<String>,
|
||||
pub base_url: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub mode: Option<String>,
|
||||
pub auth_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
pub http_headers: BTreeMap<String, String>,
|
||||
@@ -828,6 +832,7 @@ impl ConfigToml {
|
||||
"providers.xiaomi_mimo.api_key" => self.providers.xiaomi_mimo.api_key.clone(),
|
||||
"providers.xiaomi_mimo.base_url" => self.providers.xiaomi_mimo.base_url.clone(),
|
||||
"providers.xiaomi_mimo.model" => self.providers.xiaomi_mimo.model.clone(),
|
||||
"providers.xiaomi_mimo.mode" => self.providers.xiaomi_mimo.mode.clone(),
|
||||
"providers.xiaomi_mimo.http_headers" => {
|
||||
serialize_http_headers(&self.providers.xiaomi_mimo.http_headers)
|
||||
}
|
||||
@@ -1020,6 +1025,9 @@ impl ConfigToml {
|
||||
"providers.xiaomi_mimo.model" => {
|
||||
self.providers.xiaomi_mimo.model = Some(value.to_string());
|
||||
}
|
||||
"providers.xiaomi_mimo.mode" => {
|
||||
self.providers.xiaomi_mimo.mode = Some(value.to_string());
|
||||
}
|
||||
"providers.xiaomi_mimo.http_headers" => {
|
||||
self.providers.xiaomi_mimo.http_headers = parse_http_headers(value)?;
|
||||
}
|
||||
@@ -1208,6 +1216,7 @@ impl ConfigToml {
|
||||
"providers.xiaomi_mimo.api_key" => self.providers.xiaomi_mimo.api_key = None,
|
||||
"providers.xiaomi_mimo.base_url" => self.providers.xiaomi_mimo.base_url = None,
|
||||
"providers.xiaomi_mimo.model" => self.providers.xiaomi_mimo.model = None,
|
||||
"providers.xiaomi_mimo.mode" => self.providers.xiaomi_mimo.mode = None,
|
||||
"providers.xiaomi_mimo.http_headers" => {
|
||||
self.providers.xiaomi_mimo.http_headers.clear();
|
||||
}
|
||||
@@ -1403,6 +1412,9 @@ impl ConfigToml {
|
||||
if let Some(v) = self.providers.xiaomi_mimo.model.as_ref() {
|
||||
out.insert("providers.xiaomi_mimo.model".to_string(), v.clone());
|
||||
}
|
||||
if let Some(v) = self.providers.xiaomi_mimo.mode.as_ref() {
|
||||
out.insert("providers.xiaomi_mimo.mode".to_string(), v.clone());
|
||||
}
|
||||
if let Some(v) = serialize_http_headers(&self.providers.xiaomi_mimo.http_headers) {
|
||||
out.insert("providers.xiaomi_mimo.http_headers".to_string(), v);
|
||||
}
|
||||
@@ -1573,15 +1585,38 @@ impl ConfigToml {
|
||||
.or_else(|| provider_cfg.auth_mode.clone())
|
||||
.or_else(|| self.auth_mode.clone());
|
||||
let from_file = provider_cfg.api_key.clone().or(root_deepseek_api_key);
|
||||
let explicit_api_key_for_endpoint = cli.api_key.as_deref().or(from_file.as_deref());
|
||||
let configured_base_url = cli
|
||||
.base_url
|
||||
.clone()
|
||||
.or_else(|| env.base_url_for(provider))
|
||||
.or_else(|| provider_cfg.base_url.clone())
|
||||
.or(root_deepseek_base_url);
|
||||
let xiaomi_mimo_mode = if provider == ProviderKind::XiaomiMimo {
|
||||
env.xiaomi_mimo_mode
|
||||
.clone()
|
||||
.or_else(|| provider_cfg.mode.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let xiaomi_mimo_env_api_key = if provider == ProviderKind::XiaomiMimo {
|
||||
xiaomi_mimo_env_api_key_for_runtime(
|
||||
xiaomi_mimo_mode.as_deref(),
|
||||
configured_base_url.as_deref(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let explicit_api_key_for_endpoint = cli
|
||||
.api_key
|
||||
.as_deref()
|
||||
.or(from_file.as_deref())
|
||||
.or(xiaomi_mimo_env_api_key.as_deref());
|
||||
let base_url = if provider == ProviderKind::XiaomiMimo {
|
||||
resolve_xiaomi_mimo_base_url(configured_base_url, explicit_api_key_for_endpoint)
|
||||
resolve_xiaomi_mimo_base_url(
|
||||
configured_base_url,
|
||||
explicit_api_key_for_endpoint,
|
||||
xiaomi_mimo_mode.as_deref(),
|
||||
)
|
||||
} else {
|
||||
configured_base_url.unwrap_or_else(|| match provider {
|
||||
ProviderKind::Deepseek => DEFAULT_DEEPSEEK_BASE_URL.to_string(),
|
||||
@@ -1623,6 +1658,8 @@ impl ConfigToml {
|
||||
(None, None)
|
||||
} else if let Some(value) = from_file.clone().filter(|v| !v.trim().is_empty()) {
|
||||
(Some(value), Some(RuntimeApiKeySource::ConfigFile))
|
||||
} else if let Some(value) = xiaomi_mimo_env_api_key.filter(|v| !v.trim().is_empty()) {
|
||||
(Some(value), Some(RuntimeApiKeySource::Env))
|
||||
} else if should_skip_secret_store_for_provider(provider, &base_url, auth_mode.as_deref()) {
|
||||
match codewhale_secrets::env_for(provider.as_str()) {
|
||||
Some(value) => (Some(value), Some(RuntimeApiKeySource::Env)),
|
||||
@@ -2051,15 +2088,124 @@ fn moonshot_base_url_uses_kimi_code(base_url: &str) -> bool {
|
||||
|| normalized.starts_with("https://api.kimi.com/coding/")
|
||||
}
|
||||
|
||||
fn resolve_xiaomi_mimo_base_url(configured: Option<String>, api_key: Option<&str>) -> String {
|
||||
fn xiaomi_mimo_base_url_for_mode(mode: &str) -> Option<&'static str> {
|
||||
let normalized = mode.trim().to_ascii_lowercase().replace(['_', ' '], "-");
|
||||
if normalized.is_empty() || xiaomi_mimo_mode_uses_standard_endpoint(&normalized) {
|
||||
return None;
|
||||
}
|
||||
Some(match normalized.as_str() {
|
||||
"token-plan" | "tokenplan" | "subscription" | "subscribed" | "plan" => {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL
|
||||
}
|
||||
"token-plan-cn"
|
||||
| "token-plan-china"
|
||||
| "token-plan-mainland"
|
||||
| "token-plan-mainland-china"
|
||||
| "cn"
|
||||
| "china" => XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL,
|
||||
"token-plan-sgp"
|
||||
| "token-plan-sg"
|
||||
| "token-plan-singapore"
|
||||
| "sgp"
|
||||
| "sg"
|
||||
| "singapore" => XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL,
|
||||
"token-plan-ams"
|
||||
| "token-plan-eu"
|
||||
| "token-plan-europe"
|
||||
| "token-plan-amsterdam"
|
||||
| "ams"
|
||||
| "eu"
|
||||
| "europe"
|
||||
| "amsterdam" => XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL,
|
||||
_ => DEFAULT_XIAOMI_MIMO_BASE_URL,
|
||||
})
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_mode_uses_standard_endpoint(normalized_mode: &str) -> bool {
|
||||
matches!(
|
||||
normalized_mode,
|
||||
"standard" | "default" | "payg" | "paygo" | "pay-as-you-go" | "pay-as-go"
|
||||
)
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_base_url_uses_token_plan(base_url: &str) -> bool {
|
||||
let normalized = base_url.trim_end_matches('/').to_ascii_lowercase();
|
||||
normalized == XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL
|
||||
|| normalized == XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL
|
||||
|| normalized == XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_env_var(candidates: &[&str]) -> Option<String> {
|
||||
candidates.iter().find_map(|name| {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_env_api_key_for_runtime(
|
||||
mode: Option<&str>,
|
||||
base_url: Option<&str>,
|
||||
) -> Option<String> {
|
||||
const TOKEN_PLAN_ENV_VARS: &[&str] =
|
||||
&["XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "MIMO_TOKEN_PLAN_API_KEY"];
|
||||
const STANDARD_ENV_VARS: &[&str] = &["XIAOMI_MIMO_API_KEY", "XIAOMI_API_KEY", "MIMO_API_KEY"];
|
||||
|
||||
let normalized_mode =
|
||||
mode.map(|value| value.trim().to_ascii_lowercase().replace(['_', ' '], "-"));
|
||||
let standard_selected = normalized_mode
|
||||
.as_deref()
|
||||
.is_some_and(xiaomi_mimo_mode_uses_standard_endpoint)
|
||||
|| base_url.is_some_and(xiaomi_mimo_base_url_is_pay_as_you_go);
|
||||
if standard_selected {
|
||||
return xiaomi_mimo_env_var(STANDARD_ENV_VARS);
|
||||
}
|
||||
|
||||
let token_plan_selected = normalized_mode
|
||||
.as_deref()
|
||||
.and_then(xiaomi_mimo_base_url_for_mode)
|
||||
.is_some()
|
||||
|| base_url.is_some_and(xiaomi_mimo_base_url_uses_token_plan);
|
||||
if token_plan_selected {
|
||||
return xiaomi_mimo_env_var(TOKEN_PLAN_ENV_VARS);
|
||||
}
|
||||
|
||||
xiaomi_mimo_env_var(TOKEN_PLAN_ENV_VARS).or_else(|| xiaomi_mimo_env_var(STANDARD_ENV_VARS))
|
||||
}
|
||||
|
||||
fn resolve_xiaomi_mimo_base_url(
|
||||
configured: Option<String>,
|
||||
api_key: Option<&str>,
|
||||
mode: Option<&str>,
|
||||
) -> String {
|
||||
let normalized_mode =
|
||||
mode.map(|value| value.trim().to_ascii_lowercase().replace(['_', ' '], "-"));
|
||||
let uses_standard_mode = normalized_mode
|
||||
.as_deref()
|
||||
.is_some_and(xiaomi_mimo_mode_uses_standard_endpoint);
|
||||
let mode_base_url = normalized_mode
|
||||
.as_deref()
|
||||
.and_then(xiaomi_mimo_base_url_for_mode);
|
||||
let uses_token_plan = xiaomi_mimo_api_key_uses_token_plan(api_key);
|
||||
match configured {
|
||||
Some(base_url) if uses_standard_mode => base_url,
|
||||
Some(base_url) if uses_token_plan && xiaomi_mimo_base_url_is_pay_as_you_go(&base_url) => {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL.to_string()
|
||||
mode_base_url
|
||||
.unwrap_or(DEFAULT_XIAOMI_MIMO_BASE_URL)
|
||||
.to_string()
|
||||
}
|
||||
Some(base_url) => base_url,
|
||||
None if uses_token_plan || api_key.is_none() => DEFAULT_XIAOMI_MIMO_BASE_URL.to_string(),
|
||||
None => XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string(),
|
||||
None => {
|
||||
if let Some(base_url) = mode_base_url {
|
||||
base_url.to_string()
|
||||
} else if uses_standard_mode {
|
||||
XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string()
|
||||
} else if uses_token_plan || api_key.is_none() {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL.to_string()
|
||||
} else {
|
||||
XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2078,6 +2224,12 @@ fn base_url_is_custom_for_provider(provider: ProviderKind, base_url: &str) -> bo
|
||||
if provider.is_siliconflow() && siliconflow_base_url_is_official(base_url) {
|
||||
return false;
|
||||
}
|
||||
if provider == ProviderKind::XiaomiMimo
|
||||
&& (xiaomi_mimo_base_url_uses_token_plan(base_url)
|
||||
|| xiaomi_mimo_base_url_is_pay_as_you_go(base_url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let actual = base_url.trim_end_matches('/');
|
||||
let default = default_base_url_for_provider(provider).trim_end_matches('/');
|
||||
actual != default
|
||||
@@ -2633,6 +2785,7 @@ struct EnvRuntimeOverrides {
|
||||
openrouter_model: Option<String>,
|
||||
moonshot_model: Option<String>,
|
||||
xiaomi_mimo_model: Option<String>,
|
||||
xiaomi_mimo_mode: Option<String>,
|
||||
novita_model: Option<String>,
|
||||
fireworks_model: Option<String>,
|
||||
arcee_model: Option<String>,
|
||||
@@ -2698,6 +2851,10 @@ impl EnvRuntimeOverrides {
|
||||
.or_else(|_| std::env::var("MIMO_MODEL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
xiaomi_mimo_mode: std::env::var("XIAOMI_MIMO_MODE")
|
||||
.or_else(|_| std::env::var("MIMO_MODE"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
novita_model: std::env::var("NOVITA_MODEL")
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
@@ -3011,6 +3168,8 @@ mod tests {
|
||||
openrouter_api_key: Option<OsString>,
|
||||
openrouter_base_url: Option<OsString>,
|
||||
openrouter_model: Option<OsString>,
|
||||
xiaomi_mimo_token_plan_api_key: Option<OsString>,
|
||||
mimo_token_plan_api_key: Option<OsString>,
|
||||
xiaomi_mimo_api_key: Option<OsString>,
|
||||
xiaomi_api_key: Option<OsString>,
|
||||
mimo_api_key: Option<OsString>,
|
||||
@@ -3018,6 +3177,8 @@ mod tests {
|
||||
mimo_base_url: Option<OsString>,
|
||||
xiaomi_mimo_model: Option<OsString>,
|
||||
mimo_model: Option<OsString>,
|
||||
xiaomi_mimo_mode: Option<OsString>,
|
||||
mimo_mode: Option<OsString>,
|
||||
wanjie_ark_api_key: Option<OsString>,
|
||||
volcengine_api_key: Option<OsString>,
|
||||
volcengine_ark_api_key: Option<OsString>,
|
||||
@@ -3084,6 +3245,8 @@ mod tests {
|
||||
openrouter_api_key: env::var_os("OPENROUTER_API_KEY"),
|
||||
openrouter_base_url: env::var_os("OPENROUTER_BASE_URL"),
|
||||
openrouter_model: env::var_os("OPENROUTER_MODEL"),
|
||||
xiaomi_mimo_token_plan_api_key: env::var_os("XIAOMI_MIMO_TOKEN_PLAN_API_KEY"),
|
||||
mimo_token_plan_api_key: env::var_os("MIMO_TOKEN_PLAN_API_KEY"),
|
||||
xiaomi_mimo_api_key: env::var_os("XIAOMI_MIMO_API_KEY"),
|
||||
xiaomi_api_key: env::var_os("XIAOMI_API_KEY"),
|
||||
mimo_api_key: env::var_os("MIMO_API_KEY"),
|
||||
@@ -3091,6 +3254,8 @@ mod tests {
|
||||
mimo_base_url: env::var_os("MIMO_BASE_URL"),
|
||||
xiaomi_mimo_model: env::var_os("XIAOMI_MIMO_MODEL"),
|
||||
mimo_model: env::var_os("MIMO_MODEL"),
|
||||
xiaomi_mimo_mode: env::var_os("XIAOMI_MIMO_MODE"),
|
||||
mimo_mode: env::var_os("MIMO_MODE"),
|
||||
wanjie_ark_api_key: env::var_os("WANJIE_ARK_API_KEY"),
|
||||
volcengine_api_key: env::var_os("VOLCENGINE_API_KEY"),
|
||||
volcengine_ark_api_key: env::var_os("VOLCENGINE_ARK_API_KEY"),
|
||||
@@ -3152,6 +3317,8 @@ mod tests {
|
||||
env::remove_var("OPENROUTER_API_KEY");
|
||||
env::remove_var("OPENROUTER_BASE_URL");
|
||||
env::remove_var("OPENROUTER_MODEL");
|
||||
env::remove_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY");
|
||||
env::remove_var("MIMO_TOKEN_PLAN_API_KEY");
|
||||
env::remove_var("XIAOMI_MIMO_API_KEY");
|
||||
env::remove_var("XIAOMI_API_KEY");
|
||||
env::remove_var("MIMO_API_KEY");
|
||||
@@ -3159,6 +3326,8 @@ mod tests {
|
||||
env::remove_var("MIMO_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_MODEL");
|
||||
env::remove_var("MIMO_MODEL");
|
||||
env::remove_var("XIAOMI_MIMO_MODE");
|
||||
env::remove_var("MIMO_MODE");
|
||||
env::remove_var("WANJIE_ARK_API_KEY");
|
||||
env::remove_var("VOLCENGINE_API_KEY");
|
||||
env::remove_var("VOLCENGINE_ARK_API_KEY");
|
||||
@@ -3237,6 +3406,14 @@ mod tests {
|
||||
Self::restore_var("OPENROUTER_API_KEY", self.openrouter_api_key.take());
|
||||
Self::restore_var("OPENROUTER_BASE_URL", self.openrouter_base_url.take());
|
||||
Self::restore_var("OPENROUTER_MODEL", self.openrouter_model.take());
|
||||
Self::restore_var(
|
||||
"XIAOMI_MIMO_TOKEN_PLAN_API_KEY",
|
||||
self.xiaomi_mimo_token_plan_api_key.take(),
|
||||
);
|
||||
Self::restore_var(
|
||||
"MIMO_TOKEN_PLAN_API_KEY",
|
||||
self.mimo_token_plan_api_key.take(),
|
||||
);
|
||||
Self::restore_var("XIAOMI_MIMO_API_KEY", self.xiaomi_mimo_api_key.take());
|
||||
Self::restore_var("XIAOMI_API_KEY", self.xiaomi_api_key.take());
|
||||
Self::restore_var("MIMO_API_KEY", self.mimo_api_key.take());
|
||||
@@ -3244,6 +3421,8 @@ mod tests {
|
||||
Self::restore_var("MIMO_BASE_URL", self.mimo_base_url.take());
|
||||
Self::restore_var("XIAOMI_MIMO_MODEL", self.xiaomi_mimo_model.take());
|
||||
Self::restore_var("MIMO_MODEL", self.mimo_model.take());
|
||||
Self::restore_var("XIAOMI_MIMO_MODE", self.xiaomi_mimo_mode.take());
|
||||
Self::restore_var("MIMO_MODE", self.mimo_mode.take());
|
||||
Self::restore_var("WANJIE_ARK_API_KEY", self.wanjie_ark_api_key.take());
|
||||
Self::restore_var("VOLCENGINE_API_KEY", self.volcengine_api_key.take());
|
||||
Self::restore_var("VOLCENGINE_ARK_API_KEY", self.volcengine_ark_api_key.take());
|
||||
@@ -4305,6 +4484,46 @@ model = "mimo-v2.5-pro"
|
||||
assert_eq!(resolved.model, DEFAULT_XIAOMI_MIMO_MODEL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_token_plan_mode_accepts_region_aliases() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
let config: ConfigToml = toml::from_str(
|
||||
r#"
|
||||
provider = "mimo"
|
||||
|
||||
[providers.mimo]
|
||||
mode = "token-plan-ams"
|
||||
"#,
|
||||
)
|
||||
.expect("xiaomi token-plan region config");
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.base_url, XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_unknown_mode_stays_on_token_plan_endpoint() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
let config: ConfigToml = toml::from_str(
|
||||
r#"
|
||||
provider = "mimo"
|
||||
|
||||
[providers.mimo]
|
||||
mode = "token-plan-usa"
|
||||
"#,
|
||||
)
|
||||
.expect("xiaomi token-plan unknown mode config");
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.base_url, DEFAULT_XIAOMI_MIMO_BASE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_aliases_resolve_to_canonical_models() {
|
||||
assert_eq!(
|
||||
@@ -4772,6 +4991,48 @@ model = "mimo-v2.5-pro"
|
||||
assert_eq!(resolved.model, "mimo-v2.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_token_plan_mode_uses_token_plan_key_and_endpoint() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only environment mutation guarded by a module mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_PROVIDER", "xiaomi-mimo");
|
||||
env::set_var("XIAOMI_MIMO_MODE", "token-plan-cn");
|
||||
env::set_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "tp-env-key");
|
||||
env::set_var("XIAOMI_MIMO_API_KEY", "sk-env-key");
|
||||
}
|
||||
|
||||
let resolved =
|
||||
ConfigToml::default().resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.api_key.as_deref(), Some("tp-env-key"));
|
||||
assert_eq!(resolved.api_key_source, Some(RuntimeApiKeySource::Env));
|
||||
assert_eq!(resolved.base_url, XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_pay_as_you_go_mode_prefers_standard_key() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
// Safety: test-only environment mutation guarded by a module mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_PROVIDER", "xiaomi-mimo");
|
||||
env::set_var("XIAOMI_MIMO_MODE", "pay-as-you-go");
|
||||
env::set_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "tp-env-key");
|
||||
env::set_var("XIAOMI_MIMO_API_KEY", "sk-env-key");
|
||||
}
|
||||
|
||||
let resolved =
|
||||
ConfigToml::default().resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.api_key.as_deref(), Some("sk-env-key"));
|
||||
assert_eq!(resolved.api_key_source, Some(RuntimeApiKeySource::Env));
|
||||
assert_eq!(resolved.base_url, XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn novita_env_overrides_key_and_model_when_config_missing() {
|
||||
let _lock = env_lock();
|
||||
|
||||
@@ -65,6 +65,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`SOFYA_API_KEY` fallback, while keeping Sofya scoped to web search rather
|
||||
than model-provider routing (#2790). Thanks @yusufgurdogan for the
|
||||
implementation.
|
||||
- Added Xiaomi MiMo `mode` / `XIAOMI_MIMO_MODE` / `MIMO_MODE` selection for
|
||||
Token Plan region endpoints and pay-as-you-go routing, plus dedicated Token
|
||||
Plan env keys for `tp-*` subscriptions (#2621, #2627). Thanks @springeye for
|
||||
the request and @xyuai for the implementation.
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
+285
-17
@@ -99,6 +99,9 @@ pub const DEFAULT_OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1";
|
||||
pub const DEFAULT_XIAOMI_MIMO_MODEL: &str = "mimo-v2.5-pro";
|
||||
pub const XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL: &str = "https://api.xiaomimimo.com/v1";
|
||||
pub const DEFAULT_XIAOMI_MIMO_BASE_URL: &str = "https://token-plan-sgp.xiaomimimo.com/v1";
|
||||
pub const XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL: &str = "https://token-plan-cn.xiaomimimo.com/v1";
|
||||
pub const XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL: &str = DEFAULT_XIAOMI_MIMO_BASE_URL;
|
||||
pub const XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL: &str = "https://token-plan-ams.xiaomimimo.com/v1";
|
||||
pub const XIAOMI_MIMO_V2_5_OMNI_MODEL: &str = "mimo-v2.5";
|
||||
pub const XIAOMI_MIMO_ASR_MODEL: &str = "mimo-v2.5-asr";
|
||||
pub const XIAOMI_MIMO_TTS_MODEL: &str = "mimo-v2.5-tts";
|
||||
@@ -1883,6 +1886,7 @@ pub struct ProviderConfig {
|
||||
pub api_key: Option<String>,
|
||||
pub base_url: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub mode: Option<String>,
|
||||
pub auth_mode: Option<String>,
|
||||
pub http_headers: Option<HashMap<String, String>>,
|
||||
pub path_suffix: Option<String>,
|
||||
@@ -2405,12 +2409,16 @@ impl Config {
|
||||
};
|
||||
let configured_base_url = provider_base.or(root_base);
|
||||
let base = if provider == ApiProvider::XiaomiMimo {
|
||||
let env_api_key = xiaomi_mimo_env_api_key_for_base_url();
|
||||
let config_api_key = self
|
||||
.provider_config_for(provider)
|
||||
.and_then(|provider| provider.api_key.as_deref());
|
||||
let mode = self
|
||||
.provider_config_for(provider)
|
||||
.and_then(|provider| provider.mode.as_deref());
|
||||
let env_api_key =
|
||||
xiaomi_mimo_env_api_key_for_runtime(mode, configured_base_url.as_deref());
|
||||
let api_key = config_api_key.or(env_api_key.as_deref());
|
||||
resolve_xiaomi_mimo_base_url(configured_base_url, api_key)
|
||||
resolve_xiaomi_mimo_base_url(configured_base_url, api_key, mode)
|
||||
} else {
|
||||
configured_base_url.unwrap_or_else(|| {
|
||||
match provider {
|
||||
@@ -2522,6 +2530,17 @@ impl Config {
|
||||
|
||||
// 2. Environment variables. Do not query platform credential stores
|
||||
// here; routine startup and doctor checks must stay prompt-free.
|
||||
if provider == ApiProvider::XiaomiMimo {
|
||||
let mode = self
|
||||
.provider_config_for(provider)
|
||||
.and_then(|provider| provider.mode.as_deref());
|
||||
if let Some(value) =
|
||||
xiaomi_mimo_env_api_key_for_runtime(mode, Some(&self.deepseek_base_url()))
|
||||
&& !value.trim().is_empty()
|
||||
{
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
if let Some(value) = codewhale_secrets::env_for(slot)
|
||||
&& !value.trim().is_empty()
|
||||
{
|
||||
@@ -3459,6 +3478,16 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.xiaomi_mimo
|
||||
.base_url = Some(value);
|
||||
}
|
||||
if matches!(config.api_provider(), ApiProvider::XiaomiMimo)
|
||||
&& let Ok(value) = std::env::var("XIAOMI_MIMO_MODE").or_else(|_| std::env::var("MIMO_MODE"))
|
||||
&& !value.trim().is_empty()
|
||||
{
|
||||
config
|
||||
.providers
|
||||
.get_or_insert_with(ProvidersConfig::default)
|
||||
.xiaomi_mimo
|
||||
.mode = Some(value);
|
||||
}
|
||||
if matches!(config.api_provider(), ApiProvider::WanjieArk)
|
||||
&& let Ok(value) = std::env::var("WANJIE_ARK_BASE_URL")
|
||||
.or_else(|_| std::env::var("WANJIE_BASE_URL"))
|
||||
@@ -4117,24 +4146,125 @@ fn default_base_url_for_provider(provider: ApiProvider) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_xiaomi_mimo_base_url(configured: Option<String>, api_key: Option<&str>) -> String {
|
||||
let uses_token_plan = xiaomi_mimo_api_key_uses_token_plan(api_key);
|
||||
match configured {
|
||||
Some(base_url) if uses_token_plan && xiaomi_mimo_base_url_is_pay_as_you_go(&base_url) => {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL.to_string()
|
||||
}
|
||||
Some(base_url) => base_url,
|
||||
None if uses_token_plan || api_key.is_none() => DEFAULT_XIAOMI_MIMO_BASE_URL.to_string(),
|
||||
None => XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string(),
|
||||
fn xiaomi_mimo_base_url_for_mode(mode: &str) -> Option<&'static str> {
|
||||
let normalized = mode.trim().to_ascii_lowercase().replace(['_', ' '], "-");
|
||||
if normalized.is_empty() || xiaomi_mimo_mode_uses_standard_endpoint(&normalized) {
|
||||
return None;
|
||||
}
|
||||
Some(match normalized.as_str() {
|
||||
"token-plan" | "tokenplan" | "subscription" | "subscribed" | "plan" => {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL
|
||||
}
|
||||
"token-plan-cn"
|
||||
| "token-plan-china"
|
||||
| "token-plan-mainland"
|
||||
| "token-plan-mainland-china"
|
||||
| "cn"
|
||||
| "china" => XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL,
|
||||
"token-plan-sgp"
|
||||
| "token-plan-sg"
|
||||
| "token-plan-singapore"
|
||||
| "sgp"
|
||||
| "sg"
|
||||
| "singapore" => XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL,
|
||||
"token-plan-ams"
|
||||
| "token-plan-eu"
|
||||
| "token-plan-europe"
|
||||
| "token-plan-amsterdam"
|
||||
| "ams"
|
||||
| "eu"
|
||||
| "europe"
|
||||
| "amsterdam" => XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL,
|
||||
_ => DEFAULT_XIAOMI_MIMO_BASE_URL,
|
||||
})
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_env_api_key_for_base_url() -> Option<String> {
|
||||
std::env::var("XIAOMI_MIMO_API_KEY")
|
||||
.or_else(|_| std::env::var("XIAOMI_API_KEY"))
|
||||
.or_else(|_| std::env::var("MIMO_API_KEY"))
|
||||
.ok()
|
||||
.filter(|key| !key.trim().is_empty())
|
||||
fn xiaomi_mimo_mode_uses_standard_endpoint(normalized_mode: &str) -> bool {
|
||||
matches!(
|
||||
normalized_mode,
|
||||
"standard" | "default" | "payg" | "paygo" | "pay-as-you-go" | "pay-as-go"
|
||||
)
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_base_url_uses_token_plan(base_url: &str) -> bool {
|
||||
let normalized = normalize_base_url(base_url).to_ascii_lowercase();
|
||||
normalized == XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL
|
||||
|| normalized == XIAOMI_MIMO_TOKEN_PLAN_SGP_BASE_URL
|
||||
|| normalized == XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_env_var(candidates: &[&str]) -> Option<String> {
|
||||
candidates.iter().find_map(|name| {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_env_api_key_for_runtime(
|
||||
mode: Option<&str>,
|
||||
base_url: Option<&str>,
|
||||
) -> Option<String> {
|
||||
const TOKEN_PLAN_ENV_VARS: &[&str] =
|
||||
&["XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "MIMO_TOKEN_PLAN_API_KEY"];
|
||||
const STANDARD_ENV_VARS: &[&str] = &["XIAOMI_MIMO_API_KEY", "XIAOMI_API_KEY", "MIMO_API_KEY"];
|
||||
|
||||
let normalized_mode =
|
||||
mode.map(|value| value.trim().to_ascii_lowercase().replace(['_', ' '], "-"));
|
||||
let standard_selected = normalized_mode
|
||||
.as_deref()
|
||||
.is_some_and(xiaomi_mimo_mode_uses_standard_endpoint)
|
||||
|| base_url.is_some_and(xiaomi_mimo_base_url_is_pay_as_you_go);
|
||||
if standard_selected {
|
||||
return xiaomi_mimo_env_var(STANDARD_ENV_VARS);
|
||||
}
|
||||
|
||||
let token_plan_selected = normalized_mode
|
||||
.as_deref()
|
||||
.and_then(xiaomi_mimo_base_url_for_mode)
|
||||
.is_some()
|
||||
|| base_url.is_some_and(xiaomi_mimo_base_url_uses_token_plan);
|
||||
if token_plan_selected {
|
||||
return xiaomi_mimo_env_var(TOKEN_PLAN_ENV_VARS);
|
||||
}
|
||||
|
||||
xiaomi_mimo_env_var(TOKEN_PLAN_ENV_VARS).or_else(|| xiaomi_mimo_env_var(STANDARD_ENV_VARS))
|
||||
}
|
||||
|
||||
fn resolve_xiaomi_mimo_base_url(
|
||||
configured: Option<String>,
|
||||
api_key: Option<&str>,
|
||||
mode: Option<&str>,
|
||||
) -> String {
|
||||
let normalized_mode =
|
||||
mode.map(|value| value.trim().to_ascii_lowercase().replace(['_', ' '], "-"));
|
||||
let uses_standard_mode = normalized_mode
|
||||
.as_deref()
|
||||
.is_some_and(xiaomi_mimo_mode_uses_standard_endpoint);
|
||||
let mode_base_url = normalized_mode
|
||||
.as_deref()
|
||||
.and_then(xiaomi_mimo_base_url_for_mode);
|
||||
let uses_token_plan = xiaomi_mimo_api_key_uses_token_plan(api_key);
|
||||
match configured {
|
||||
Some(base_url) if uses_standard_mode => base_url,
|
||||
Some(base_url) if uses_token_plan && xiaomi_mimo_base_url_is_pay_as_you_go(&base_url) => {
|
||||
mode_base_url
|
||||
.unwrap_or(DEFAULT_XIAOMI_MIMO_BASE_URL)
|
||||
.to_string()
|
||||
}
|
||||
Some(base_url) => base_url,
|
||||
None => {
|
||||
if let Some(base_url) = mode_base_url {
|
||||
base_url.to_string()
|
||||
} else if uses_standard_mode {
|
||||
XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string()
|
||||
} else if uses_token_plan || api_key.is_none() {
|
||||
DEFAULT_XIAOMI_MIMO_BASE_URL.to_string()
|
||||
} else {
|
||||
XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xiaomi_mimo_api_key_uses_token_plan(api_key: Option<&str>) -> bool {
|
||||
@@ -4154,6 +4284,12 @@ fn base_url_is_custom_for_provider(provider: ApiProvider, base_url: &str) -> boo
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if provider == ApiProvider::XiaomiMimo
|
||||
&& (xiaomi_mimo_base_url_uses_token_plan(base_url)
|
||||
|| xiaomi_mimo_base_url_is_pay_as_you_go(base_url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
normalize_base_url(base_url) != normalize_base_url(default_base_url_for_provider(provider))
|
||||
}
|
||||
|
||||
@@ -4389,6 +4525,7 @@ fn merge_provider_config(base: ProviderConfig, override_cfg: ProviderConfig) ->
|
||||
api_key: override_cfg.api_key.or(base.api_key),
|
||||
base_url: override_cfg.base_url.or(base.base_url),
|
||||
model: override_cfg.model.or(base.model),
|
||||
mode: override_cfg.mode.or(base.mode),
|
||||
auth_mode: override_cfg.auth_mode.or(base.auth_mode),
|
||||
http_headers: override_cfg.http_headers.or(base.http_headers),
|
||||
path_suffix: override_cfg.path_suffix.or(base.path_suffix),
|
||||
@@ -5891,6 +6028,8 @@ mod tests {
|
||||
ark_base_url: Option<OsString>,
|
||||
volcengine_model: Option<OsString>,
|
||||
volcengine_ark_model: Option<OsString>,
|
||||
xiaomi_mimo_token_plan_api_key: Option<OsString>,
|
||||
mimo_token_plan_api_key: Option<OsString>,
|
||||
xiaomi_mimo_api_key: Option<OsString>,
|
||||
xiaomi_api_key: Option<OsString>,
|
||||
mimo_api_key: Option<OsString>,
|
||||
@@ -5898,6 +6037,8 @@ mod tests {
|
||||
mimo_base_url: Option<OsString>,
|
||||
xiaomi_mimo_model: Option<OsString>,
|
||||
mimo_model: Option<OsString>,
|
||||
xiaomi_mimo_mode: Option<OsString>,
|
||||
mimo_mode: Option<OsString>,
|
||||
novita_api_key: Option<OsString>,
|
||||
novita_base_url: Option<OsString>,
|
||||
novita_model: Option<OsString>,
|
||||
@@ -5990,6 +6131,8 @@ mod tests {
|
||||
let ark_base_url_prev = env::var_os("ARK_BASE_URL");
|
||||
let volcengine_model_prev = env::var_os("VOLCENGINE_MODEL");
|
||||
let volcengine_ark_model_prev = env::var_os("VOLCENGINE_ARK_MODEL");
|
||||
let xiaomi_mimo_token_plan_api_key_prev = env::var_os("XIAOMI_MIMO_TOKEN_PLAN_API_KEY");
|
||||
let mimo_token_plan_api_key_prev = env::var_os("MIMO_TOKEN_PLAN_API_KEY");
|
||||
let xiaomi_mimo_api_key_prev = env::var_os("XIAOMI_MIMO_API_KEY");
|
||||
let xiaomi_api_key_prev = env::var_os("XIAOMI_API_KEY");
|
||||
let mimo_api_key_prev = env::var_os("MIMO_API_KEY");
|
||||
@@ -5997,6 +6140,8 @@ mod tests {
|
||||
let mimo_base_url_prev = env::var_os("MIMO_BASE_URL");
|
||||
let xiaomi_mimo_model_prev = env::var_os("XIAOMI_MIMO_MODEL");
|
||||
let mimo_model_prev = env::var_os("MIMO_MODEL");
|
||||
let xiaomi_mimo_mode_prev = env::var_os("XIAOMI_MIMO_MODE");
|
||||
let mimo_mode_prev = env::var_os("MIMO_MODE");
|
||||
let novita_api_key_prev = env::var_os("NOVITA_API_KEY");
|
||||
let novita_base_url_prev = env::var_os("NOVITA_BASE_URL");
|
||||
let novita_model_prev = env::var_os("NOVITA_MODEL");
|
||||
@@ -6084,6 +6229,8 @@ mod tests {
|
||||
env::remove_var("ARK_BASE_URL");
|
||||
env::remove_var("VOLCENGINE_MODEL");
|
||||
env::remove_var("VOLCENGINE_ARK_MODEL");
|
||||
env::remove_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY");
|
||||
env::remove_var("MIMO_TOKEN_PLAN_API_KEY");
|
||||
env::remove_var("XIAOMI_MIMO_API_KEY");
|
||||
env::remove_var("XIAOMI_API_KEY");
|
||||
env::remove_var("MIMO_API_KEY");
|
||||
@@ -6091,6 +6238,8 @@ mod tests {
|
||||
env::remove_var("MIMO_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_MODEL");
|
||||
env::remove_var("MIMO_MODEL");
|
||||
env::remove_var("XIAOMI_MIMO_MODE");
|
||||
env::remove_var("MIMO_MODE");
|
||||
env::remove_var("NOVITA_API_KEY");
|
||||
env::remove_var("NOVITA_BASE_URL");
|
||||
env::remove_var("NOVITA_MODEL");
|
||||
@@ -6178,6 +6327,8 @@ mod tests {
|
||||
ark_base_url: ark_base_url_prev,
|
||||
volcengine_model: volcengine_model_prev,
|
||||
volcengine_ark_model: volcengine_ark_model_prev,
|
||||
xiaomi_mimo_token_plan_api_key: xiaomi_mimo_token_plan_api_key_prev,
|
||||
mimo_token_plan_api_key: mimo_token_plan_api_key_prev,
|
||||
xiaomi_mimo_api_key: xiaomi_mimo_api_key_prev,
|
||||
xiaomi_api_key: xiaomi_api_key_prev,
|
||||
mimo_api_key: mimo_api_key_prev,
|
||||
@@ -6185,6 +6336,8 @@ mod tests {
|
||||
mimo_base_url: mimo_base_url_prev,
|
||||
xiaomi_mimo_model: xiaomi_mimo_model_prev,
|
||||
mimo_model: mimo_model_prev,
|
||||
xiaomi_mimo_mode: xiaomi_mimo_mode_prev,
|
||||
mimo_mode: mimo_mode_prev,
|
||||
novita_api_key: novita_api_key_prev,
|
||||
novita_base_url: novita_base_url_prev,
|
||||
novita_model: novita_model_prev,
|
||||
@@ -6290,6 +6443,14 @@ mod tests {
|
||||
Self::restore_var("ARK_BASE_URL", self.ark_base_url.take());
|
||||
Self::restore_var("VOLCENGINE_MODEL", self.volcengine_model.take());
|
||||
Self::restore_var("VOLCENGINE_ARK_MODEL", self.volcengine_ark_model.take());
|
||||
Self::restore_var(
|
||||
"XIAOMI_MIMO_TOKEN_PLAN_API_KEY",
|
||||
self.xiaomi_mimo_token_plan_api_key.take(),
|
||||
);
|
||||
Self::restore_var(
|
||||
"MIMO_TOKEN_PLAN_API_KEY",
|
||||
self.mimo_token_plan_api_key.take(),
|
||||
);
|
||||
Self::restore_var("XIAOMI_MIMO_API_KEY", self.xiaomi_mimo_api_key.take());
|
||||
Self::restore_var("XIAOMI_API_KEY", self.xiaomi_api_key.take());
|
||||
Self::restore_var("MIMO_API_KEY", self.mimo_api_key.take());
|
||||
@@ -6297,6 +6458,8 @@ mod tests {
|
||||
Self::restore_var("MIMO_BASE_URL", self.mimo_base_url.take());
|
||||
Self::restore_var("XIAOMI_MIMO_MODEL", self.xiaomi_mimo_model.take());
|
||||
Self::restore_var("MIMO_MODEL", self.mimo_model.take());
|
||||
Self::restore_var("XIAOMI_MIMO_MODE", self.xiaomi_mimo_mode.take());
|
||||
Self::restore_var("MIMO_MODE", self.mimo_mode.take());
|
||||
Self::restore_var("NOVITA_API_KEY", self.novita_api_key.take());
|
||||
Self::restore_var("NOVITA_BASE_URL", self.novita_base_url.take());
|
||||
Self::restore_var("NOVITA_MODEL", self.novita_model.take());
|
||||
@@ -8190,6 +8353,43 @@ model = "mimo-v2.5-pro"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_token_plan_mode_accepts_region_aliases() -> Result<()> {
|
||||
let config: Config = toml::from_str(
|
||||
r#"
|
||||
provider = "mimo"
|
||||
|
||||
[providers.mimo]
|
||||
mode = "token-plan-ams"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
config.validate()?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(
|
||||
config.deepseek_base_url(),
|
||||
XIAOMI_MIMO_TOKEN_PLAN_AMS_BASE_URL
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_unknown_mode_stays_on_token_plan_endpoint() -> Result<()> {
|
||||
let config: Config = toml::from_str(
|
||||
r#"
|
||||
provider = "mimo"
|
||||
|
||||
[providers.mimo]
|
||||
mode = "token-plan-usa"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
config.validate()?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(config.deepseek_base_url(), DEFAULT_XIAOMI_MIMO_BASE_URL);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_overrides_provider_base_url_model_and_key() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
@@ -8224,6 +8424,74 @@ model = "mimo-v2.5-pro"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_token_plan_mode_uses_token_plan_key_and_endpoint() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-xiaomi-mimo-token-plan-env-test-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_PROVIDER", "xiaomi-mimo");
|
||||
env::set_var("XIAOMI_MIMO_MODE", "token-plan-cn");
|
||||
env::set_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "tp-env-key");
|
||||
env::set_var("XIAOMI_MIMO_API_KEY", "sk-env-key");
|
||||
env::set_var("XIAOMI_MIMO_MODEL", "voiceclone");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(config.deepseek_api_key()?, "tp-env-key");
|
||||
assert_eq!(
|
||||
config.deepseek_base_url(),
|
||||
XIAOMI_MIMO_TOKEN_PLAN_CN_BASE_URL
|
||||
);
|
||||
assert_eq!(config.default_model(), "voiceclone");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_pay_as_you_go_mode_prefers_standard_key() -> Result<()> {
|
||||
let _lock = lock_test_env();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-xiaomi-mimo-payg-env-test-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_PROVIDER", "xiaomi-mimo");
|
||||
env::set_var("XIAOMI_MIMO_MODE", "pay-as-you-go");
|
||||
env::set_var("XIAOMI_MIMO_TOKEN_PLAN_API_KEY", "tp-env-key");
|
||||
env::set_var("XIAOMI_MIMO_API_KEY", "sk-env-key");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(config.deepseek_api_key()?, "sk-env-key");
|
||||
assert_eq!(
|
||||
config.deepseek_base_url(),
|
||||
XIAOMI_MIMO_PAY_AS_YOU_GO_BASE_URL
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atlascloud_provider_uses_documented_defaults() -> Result<()> {
|
||||
let config = Config {
|
||||
|
||||
@@ -434,9 +434,10 @@ Remaining variables:
|
||||
- `VOLCENGINE_MODEL` or `VOLCENGINE_ARK_MODEL`
|
||||
- `OPENROUTER_API_KEY`
|
||||
- `OPENROUTER_BASE_URL`
|
||||
- `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, or `MIMO_API_KEY`
|
||||
- `XIAOMI_MIMO_TOKEN_PLAN_API_KEY`, `MIMO_TOKEN_PLAN_API_KEY`, `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, or `MIMO_API_KEY`
|
||||
- `XIAOMI_MIMO_BASE_URL` or `MIMO_BASE_URL`
|
||||
- `XIAOMI_MIMO_MODEL` or `MIMO_MODEL`
|
||||
- `XIAOMI_MIMO_MODE` or `MIMO_MODE`
|
||||
- `NOVITA_API_KEY`
|
||||
- `NOVITA_BASE_URL`
|
||||
- `FIREWORKS_API_KEY`
|
||||
|
||||
+4
-2
@@ -118,7 +118,7 @@ endpoint.
|
||||
| `wanjie-ark` | `[providers.wanjie_ark]` | `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, `WANJIE_MAAS_API_KEY` | `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, `WANJIE_MAAS_BASE_URL`; default `https://maas-openapi.wanjiedata.com/api/v1` | `deepseek-reasoner` | OpenAI-compatible hosted route. `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, and `WANJIE_MAAS_MODEL` are accepted. |
|
||||
| `volcengine` | `[providers.volcengine]` | `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, `ARK_API_KEY` | `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, `ARK_BASE_URL`; default `https://ark.cn-beijing.volces.com/api/coding/v3` | `DeepSeek-V4-Pro`, `DeepSeek-V4-Flash` | Volcengine/Volcano Engine Ark OpenAI-compatible coding endpoint. `VOLCENGINE_MODEL` and `VOLCENGINE_ARK_MODEL` are accepted. |
|
||||
| `openrouter` | `[providers.openrouter]` | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL`; default `https://openrouter.ai/api/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`; recent large IDs include `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-flash`, `qwen/qwen3.6-35b-a3b`, `qwen/qwen3.6-max-preview`, `qwen/qwen3.6-27b`, `qwen/qwen3.6-plus`, `google/gemma-4-31b-it`, `z-ai/glm-5.1`, `moonshotai/kimi-k2.6` | Additive open-model routing layer. It does not replace DeepSeek; it lets users route supported model IDs through OpenRouter when they choose it. |
|
||||
| `xiaomi-mimo` | `[providers.xiaomi_mimo]` | `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, `MIMO_API_KEY` | `XIAOMI_MIMO_BASE_URL`, `MIMO_BASE_URL`; default `https://token-plan-sgp.xiaomimimo.com/v1` | Chat: `mimo-v2.5-pro`, `mimo-v2.5`; speech/TTS: `mimo-v2.5-tts`, `mimo-v2.5-tts-voicedesign`, `mimo-v2.5-tts-voiceclone`, `mimo-v2-tts` | Xiaomi MiMo OpenAI-compatible chat completions route. Token Plan keys (`tp-...`) use the token-plan endpoint by default; pay-as-you-go keys can set `base_url = "https://api.xiaomimimo.com/v1"`. It sends `max_completion_tokens` and uses MiMo's `thinking` field for reasoning control. `codewhale speech` / `tts` uses the TTS models. |
|
||||
| `xiaomi-mimo` | `[providers.xiaomi_mimo]` | `XIAOMI_MIMO_TOKEN_PLAN_API_KEY`, `MIMO_TOKEN_PLAN_API_KEY`, `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, `MIMO_API_KEY` | `XIAOMI_MIMO_BASE_URL`, `MIMO_BASE_URL`, `XIAOMI_MIMO_MODE`, `MIMO_MODE`; default `https://token-plan-sgp.xiaomimimo.com/v1` | Chat: `mimo-v2.5-pro`, `mimo-v2.5`; speech/TTS: `mimo-v2.5-tts`, `mimo-v2.5-tts-voicedesign`, `mimo-v2.5-tts-voiceclone`, `mimo-v2-tts` | Xiaomi MiMo OpenAI-compatible chat completions route. Token Plan keys (`tp-...`) use `api-key` auth and the token-plan endpoint by default; pay-as-you-go mode uses standard API keys (`sk-...`) and `https://api.xiaomimimo.com/v1`. It sends `max_completion_tokens` and uses MiMo's `thinking` field for reasoning control. `codewhale speech` / `tts` uses the TTS models. |
|
||||
| `novita` | `[providers.novita]` | `NOVITA_API_KEY` | `NOVITA_BASE_URL`; default `https://api.novita.ai/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | OpenAI-compatible hosted route for DeepSeek model IDs. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. |
|
||||
| `fireworks` | `[providers.fireworks]` | `FIREWORKS_API_KEY` | `FIREWORKS_BASE_URL`; default `https://api.fireworks.ai/inference/v1` | `accounts/fireworks/models/deepseek-v4-pro` | OpenAI-compatible hosted route. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. |
|
||||
| `siliconflow` | `[providers.siliconflow]` | `SILICONFLOW_API_KEY` | `SILICONFLOW_BASE_URL`; default `https://api.siliconflow.com/v1` | `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` | OpenAI-compatible hosted route. Official docs use the `.com` endpoint. `SILICONFLOW_MODEL` is accepted. Reasoning aliases `deepseek-reasoner` and `deepseek-r1` map to Pro; `deepseek-chat` and `deepseek-v3` map to Flash. |
|
||||
@@ -141,7 +141,9 @@ Agent/YOLO mode.
|
||||
Token Plan keys default to the Singapore endpoint
|
||||
`https://token-plan-sgp.xiaomimimo.com/v1`. If your MiMo account is provisioned
|
||||
for the China region, set `base_url = "https://token-plan-cn.xiaomimimo.com/v1"`
|
||||
explicitly in `[providers.xiaomi_mimo]`.
|
||||
explicitly in `[providers.xiaomi_mimo]` or set `mode = "token-plan-cn"`. Europe
|
||||
Token Plan accounts can use `mode = "token-plan-ams"`; `mode = "pay-as-you-go"`
|
||||
selects the standard API endpoint and standard MiMo key family.
|
||||
|
||||
Voice-design and voice-clone shorthands map to `mimo-v2.5-tts-voicedesign` and
|
||||
`mimo-v2.5-tts-voiceclone`. Xiaomi's current
|
||||
|
||||
Reference in New Issue
Block a user