feat: add Xiaomi MiMo provider
Adds native xiaomi-mimo provider configuration, auth/env aliases, model registry entries, TUI request handling, tests, and docs. Keeps credentials in existing provider-scoped config/env/keyring paths and uses placeholders only in docs.
This commit is contained in:
+7
-2
@@ -216,6 +216,10 @@ codewhale --provider wanjie-ark --model deepseek-reasoner
|
||||
codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY"
|
||||
codewhale --provider openrouter --model deepseek/deepseek-v4-pro
|
||||
|
||||
# Xiaomi MiMo
|
||||
codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
|
||||
|
||||
# Novita
|
||||
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
|
||||
codewhale --provider novita --model deepseek/deepseek-v4-pro
|
||||
@@ -321,15 +325,16 @@ codewhale update # バイナリ更新の確認
|
||||
| `DEEPSEEK_HTTP_HEADERS` | 任意のモデルリクエストヘッダー |
|
||||
| `DEEPSEEK_MODEL` | デフォルトモデル |
|
||||
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | ストリームのアイドルタイムアウト秒数 |
|
||||
| `DEEPSEEK_PROVIDER` | `codewhale`(デフォルト)、`nvidia-nim`、`openai`、`atlascloud`、`wanjie-ark`、`openrouter`、`novita`、`fireworks`、`sglang`、`vllm`、`ollama` |
|
||||
| `DEEPSEEK_PROVIDER` | `codewhale`(デフォルト)、`nvidia-nim`、`openai`、`atlascloud`、`wanjie-ark`、`openrouter`、`xiaomi-mimo`、`novita`、`fireworks`、`sglang`、`vllm`、`ollama` |
|
||||
| `DEEPSEEK_PROFILE` | 設定プロファイル名 |
|
||||
| `DEEPSEEK_MEMORY` | `on` に設定するとユーザーメモリを有効化 |
|
||||
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | 信頼できるネットワークで非ローカル `http://` API ベース URL を許可 |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | プロバイダー認証 |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | プロバイダー認証 |
|
||||
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | 汎用 OpenAI 互換エンドポイントとモデル ID |
|
||||
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud エンドポイントとモデル上書き |
|
||||
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark エンドポイントとモデル上書き |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter エンドポイント上書き |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo エンドポイントとモデル上書き |
|
||||
| `NOVITA_BASE_URL` | Novita エンドポイント上書き |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks エンドポイント上書き |
|
||||
| `SGLANG_BASE_URL` | セルフホスト SGLang のエンドポイント |
|
||||
|
||||
@@ -313,6 +313,10 @@ codewhale --provider wanjie-ark --model deepseek-reasoner
|
||||
codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY"
|
||||
codewhale --provider openrouter --model deepseek/deepseek-v4-pro
|
||||
|
||||
# Xiaomi MiMo
|
||||
codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
|
||||
|
||||
# Novita
|
||||
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
|
||||
codewhale --provider novita --model deepseek/deepseek-v4-pro
|
||||
@@ -490,15 +494,16 @@ Key environment variables:
|
||||
| `DEEPSEEK_HTTP_HEADERS` | Optional custom model request headers, e.g. `X-Model-Provider-Id=your-model-provider` |
|
||||
| `DEEPSEEK_MODEL` | Default model |
|
||||
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | Stream idle timeout in seconds, default `300`, clamped to `1..=3600` |
|
||||
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, `ollama` |
|
||||
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, `ollama` |
|
||||
| `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` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | Provider auth |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | 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 |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter endpoint override |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo endpoint and model override |
|
||||
| `NOVITA_BASE_URL` | Novita endpoint override |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks endpoint override |
|
||||
| `SGLANG_BASE_URL` | Self-hosted SGLang endpoint |
|
||||
|
||||
+7
-2
@@ -261,6 +261,10 @@ codewhale --provider wanjie-ark --model deepseek-reasoner
|
||||
codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY"
|
||||
codewhale --provider openrouter --model deepseek/deepseek-v4-pro
|
||||
|
||||
# Xiaomi MiMo
|
||||
codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
codewhale --provider xiaomi-mimo --model mimo-v2.5-pro
|
||||
|
||||
# Novita
|
||||
codewhale auth set --provider novita --api-key "YOUR_NOVITA_API_KEY"
|
||||
codewhale --provider novita --model deepseek/deepseek-v4-pro
|
||||
@@ -402,15 +406,16 @@ DeepSeek 可作为自定义 Agent Client Protocol 服务器运行,供 Zed 等
|
||||
| `DEEPSEEK_HTTP_HEADERS` | 可选模型请求头,例如 `X-Model-Provider-Id=your-model-provider` |
|
||||
| `DEEPSEEK_MODEL` | 默认模型 |
|
||||
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | 流式响应空闲超时秒数,默认 `300`,限制在 `1..=3600` |
|
||||
| `DEEPSEEK_PROVIDER` | `codewhale`(默认)、`nvidia-nim`、`openai`、`atlascloud`、`wanjie-ark`、`openrouter`、`novita`、`fireworks`、`sglang`、`vllm`、`ollama` |
|
||||
| `DEEPSEEK_PROVIDER` | `codewhale`(默认)、`nvidia-nim`、`openai`、`atlascloud`、`wanjie-ark`、`openrouter`、`xiaomi-mimo`、`novita`、`fireworks`、`sglang`、`vllm`、`ollama` |
|
||||
| `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` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | 提供商认证 |
|
||||
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | 提供商认证 |
|
||||
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | 通用 OpenAI 兼容端点和模型 ID |
|
||||
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud 端点和模型覆盖 |
|
||||
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark 端点和模型覆盖 |
|
||||
| `OPENROUTER_BASE_URL` | OpenRouter 端点覆盖 |
|
||||
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo 端点和模型覆盖 |
|
||||
| `NOVITA_BASE_URL` | Novita 端点覆盖 |
|
||||
| `FIREWORKS_BASE_URL` | Fireworks 端点覆盖 |
|
||||
| `SGLANG_BASE_URL` | 自托管 SGLang 端点 |
|
||||
|
||||
+17
-3
@@ -13,11 +13,12 @@
|
||||
# `[providers.*]` sections near the bottom of
|
||||
# this file — keeping both stored at once means `/provider deepseek` and
|
||||
# `/provider nvidia-nim` (or `--provider openai`, `--provider wanjie-ark`,
|
||||
# `--provider fireworks`, `/provider sglang`, `/provider vllm`, `/provider ollama`)
|
||||
# toggle without having to re-enter keys. Top-level `api_key` / `base_url` are
|
||||
# `--provider xiaomi-mimo`, `--provider fireworks`, `/provider sglang`,
|
||||
# `/provider vllm`, `/provider ollama`) toggle without having to re-enter keys.
|
||||
# Top-level `api_key` / `base_url` are
|
||||
# still read as DeepSeek defaults when `[providers.deepseek]` is absent
|
||||
# (backward compatibility).
|
||||
provider = "deepseek" # deepseek | deepseek-cn | nvidia-nim | openai | atlascloud | wanjie-ark | openrouter | novita | fireworks | sglang | vllm | ollama
|
||||
provider = "deepseek" # deepseek | deepseek-cn | nvidia-nim | openai | atlascloud | wanjie-ark | openrouter | xiaomi-mimo | novita | fireworks | sglang | vllm | ollama
|
||||
api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty
|
||||
base_url = "https://api.deepseek.com/beta"
|
||||
# provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com)
|
||||
@@ -37,6 +38,7 @@ base_url = "https://api.deepseek.com/beta"
|
||||
# gpt-4.1 — default generic OpenAI-compatible model ID
|
||||
# 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
|
||||
# accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID
|
||||
# deepseek-ai/DeepSeek-V4-Pro — SGLang self-hosted Pro model ID
|
||||
# deepseek-ai/DeepSeek-V4-Flash — SGLang self-hosted Flash model ID
|
||||
@@ -186,6 +188,7 @@ max_subagents = 10 # optional (1-20)
|
||||
# OpenAI-compatible: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
|
||||
# Wanjie Ark: WANJIE_ARK_API_KEY (or WANJIE_API_KEY), WANJIE_ARK_BASE_URL, WANJIE_ARK_MODEL
|
||||
# OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
|
||||
# Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL
|
||||
# Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
|
||||
# Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
|
||||
# SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY
|
||||
@@ -244,6 +247,12 @@ max_subagents = 10 # optional (1-20)
|
||||
# base_url = "https://openrouter.ai/api/v1"
|
||||
# model = "deepseek/deepseek-v4-pro" # or deepseek/deepseek-v4-flash
|
||||
|
||||
# Xiaomi MiMo OpenAI-compatible endpoint (https://platform.xiaomimimo.com)
|
||||
[providers.xiaomi_mimo]
|
||||
# api_key = "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
# base_url = "https://api.xiaomimimo.com/v1"
|
||||
# model = "mimo-v2.5-pro"
|
||||
|
||||
# Novita AI-hosted inference (https://novita.ai)
|
||||
[providers.novita]
|
||||
# api_key = "YOUR_NOVITA_API_KEY"
|
||||
@@ -380,6 +389,11 @@ exec_policy = true
|
||||
# model = "gemini-3.1-flash-lite-preview" # Required: vision-capable model ID
|
||||
# api_key = "YOUR_API_KEY" # Optional: defaults to main api_key
|
||||
# base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" # Optional
|
||||
#
|
||||
# Xiaomi MiMo image understanding can be configured through the same tool:
|
||||
# model = "mimo-v2.5"
|
||||
# api_key = "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
# base_url = "https://api.xiaomimimo.com/v1"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────────
|
||||
# Retry Configuration
|
||||
|
||||
@@ -119,6 +119,20 @@ impl Default for ModelRegistry {
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "mimo-v2.5-pro".to_string(),
|
||||
provider: ProviderKind::XiaomiMimo,
|
||||
aliases: vec!["mimo".to_string()],
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "mimo-v2.5".to_string(),
|
||||
provider: ProviderKind::XiaomiMimo,
|
||||
aliases: vec!["xiaomi-mimo-v2.5".to_string()],
|
||||
supports_tools: true,
|
||||
supports_reasoning: true,
|
||||
},
|
||||
ModelInfo {
|
||||
id: "deepseek/deepseek-v4-pro".to_string(),
|
||||
provider: ProviderKind::Novita,
|
||||
@@ -382,6 +396,16 @@ mod tests {
|
||||
assert_eq!(resolved.resolved.id, "deepseek/deepseek-v4-pro");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_default_uses_canonical_model_id() {
|
||||
let registry = ModelRegistry::default();
|
||||
let resolved = registry.resolve(None, Some(ProviderKind::XiaomiMimo));
|
||||
|
||||
assert_eq!(resolved.resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.resolved.id, "mimo-v2.5-pro");
|
||||
assert!(resolved.resolved.supports_reasoning);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wanjie_ark_default_uses_reasoner_model_id() {
|
||||
let registry = ModelRegistry::default();
|
||||
|
||||
+13
-2
@@ -29,6 +29,7 @@ enum ProviderArg {
|
||||
Atlascloud,
|
||||
WanjieArk,
|
||||
Openrouter,
|
||||
XiaomiMimo,
|
||||
Novita,
|
||||
Fireworks,
|
||||
Moonshot,
|
||||
@@ -46,6 +47,7 @@ impl From<ProviderArg> for ProviderKind {
|
||||
ProviderArg::Atlascloud => ProviderKind::Atlascloud,
|
||||
ProviderArg::WanjieArk => ProviderKind::WanjieArk,
|
||||
ProviderArg::Openrouter => ProviderKind::Openrouter,
|
||||
ProviderArg::XiaomiMimo => ProviderKind::XiaomiMimo,
|
||||
ProviderArg::Novita => ProviderKind::Novita,
|
||||
ProviderArg::Fireworks => ProviderKind::Fireworks,
|
||||
ProviderArg::Moonshot => ProviderKind::Moonshot,
|
||||
@@ -721,6 +723,7 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Atlascloud => "atlascloud",
|
||||
ProviderKind::WanjieArk => "wanjie-ark",
|
||||
ProviderKind::Openrouter => "openrouter",
|
||||
ProviderKind::XiaomiMimo => "xiaomi-mimo",
|
||||
ProviderKind::Novita => "novita",
|
||||
ProviderKind::Fireworks => "fireworks",
|
||||
ProviderKind::Moonshot => "moonshot",
|
||||
@@ -731,13 +734,14 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
|
||||
}
|
||||
|
||||
/// Provider order used by the `auth list` and `auth status` outputs.
|
||||
const PROVIDER_LIST: [ProviderKind; 12] = [
|
||||
const PROVIDER_LIST: [ProviderKind; 13] = [
|
||||
ProviderKind::Deepseek,
|
||||
ProviderKind::NvidiaNim,
|
||||
ProviderKind::Openai,
|
||||
ProviderKind::Atlascloud,
|
||||
ProviderKind::WanjieArk,
|
||||
ProviderKind::Openrouter,
|
||||
ProviderKind::XiaomiMimo,
|
||||
ProviderKind::Novita,
|
||||
ProviderKind::Fireworks,
|
||||
ProviderKind::Moonshot,
|
||||
@@ -792,6 +796,7 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] {
|
||||
match provider {
|
||||
ProviderKind::Deepseek => &["DEEPSEEK_API_KEY"],
|
||||
ProviderKind::Openrouter => &["OPENROUTER_API_KEY"],
|
||||
ProviderKind::XiaomiMimo => &["XIAOMI_MIMO_API_KEY", "MIMO_API_KEY"],
|
||||
ProviderKind::Novita => &["NOVITA_API_KEY"],
|
||||
ProviderKind::NvidiaNim => &["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY", "DEEPSEEK_API_KEY"],
|
||||
ProviderKind::Fireworks => &["FIREWORKS_API_KEY"],
|
||||
@@ -1476,6 +1481,7 @@ fn build_tui_command(
|
||||
| ProviderKind::Atlascloud
|
||||
| ProviderKind::WanjieArk
|
||||
| ProviderKind::Openrouter
|
||||
| ProviderKind::XiaomiMimo
|
||||
| ProviderKind::Novita
|
||||
| ProviderKind::Fireworks
|
||||
| ProviderKind::Moonshot
|
||||
@@ -1484,7 +1490,7 @@ fn build_tui_command(
|
||||
| ProviderKind::Ollama
|
||||
) {
|
||||
bail!(
|
||||
"The interactive TUI supports DeepSeek, NVIDIA NIM, OpenAI-compatible, AtlasCloud, Wanjie Ark, OpenRouter, Novita, Fireworks, Moonshot/Kimi, SGLang, vLLM, and Ollama providers. Remove --provider {} or use `codewhale model ...` for provider registry inspection.",
|
||||
"The interactive TUI supports DeepSeek, NVIDIA NIM, OpenAI-compatible, AtlasCloud, Wanjie Ark, OpenRouter, Xiaomi MiMo, Novita, Fireworks, Moonshot/Kimi, SGLang, vLLM, and Ollama providers. Remove --provider {} or use `codewhale model ...` for provider registry inspection.",
|
||||
resolved_runtime.provider.as_str()
|
||||
);
|
||||
}
|
||||
@@ -2897,6 +2903,11 @@ mod tests {
|
||||
"openrouter",
|
||||
&["OPENROUTER_API_KEY"],
|
||||
),
|
||||
(
|
||||
ProviderKind::XiaomiMimo,
|
||||
"xiaomi-mimo",
|
||||
&["XIAOMI_MIMO_API_KEY", "MIMO_API_KEY"],
|
||||
),
|
||||
(ProviderKind::Novita, "novita", &["NOVITA_API_KEY"]),
|
||||
(
|
||||
ProviderKind::NvidiaNim,
|
||||
|
||||
+143
-1
@@ -27,6 +27,7 @@ const DEFAULT_WANJIE_ARK_MODEL: &str = "deepseek-reasoner";
|
||||
const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.com/api/v1";
|
||||
const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
|
||||
const DEFAULT_XIAOMI_MIMO_MODEL: &str = "mimo-v2.5-pro";
|
||||
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";
|
||||
@@ -37,6 +38,7 @@ const DEFAULT_KIMI_CODE_BASE_URL: &str = "https://api.kimi.com/coding/v1";
|
||||
const DEFAULT_SGLANG_MODEL: &str = "deepseek-ai/DeepSeek-V4-Pro";
|
||||
const DEFAULT_SGLANG_FLASH_MODEL: &str = "deepseek-ai/DeepSeek-V4-Flash";
|
||||
const DEFAULT_OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1";
|
||||
const DEFAULT_XIAOMI_MIMO_BASE_URL: &str = "https://api.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_SGLANG_BASE_URL: &str = "http://localhost:30000/v1";
|
||||
@@ -71,6 +73,8 @@ pub enum ProviderKind {
|
||||
)]
|
||||
WanjieArk,
|
||||
Openrouter,
|
||||
#[serde(alias = "mimo", alias = "xiaomi", alias = "xiaomi_mimo")]
|
||||
XiaomiMimo,
|
||||
Novita,
|
||||
Fireworks,
|
||||
Moonshot,
|
||||
@@ -89,6 +93,7 @@ impl ProviderKind {
|
||||
Self::Atlascloud => "atlascloud",
|
||||
Self::WanjieArk => "wanjie-ark",
|
||||
Self::Openrouter => "openrouter",
|
||||
Self::XiaomiMimo => "xiaomi-mimo",
|
||||
Self::Novita => "novita",
|
||||
Self::Fireworks => "fireworks",
|
||||
Self::Moonshot => "moonshot",
|
||||
@@ -109,6 +114,9 @@ impl ProviderKind {
|
||||
"wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark"
|
||||
| "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk),
|
||||
"openrouter" | "open_router" => Some(Self::Openrouter),
|
||||
"xiaomi-mimo" | "xiaomi_mimo" | "xiaomimimo" | "mimo" | "xiaomi" => {
|
||||
Some(Self::XiaomiMimo)
|
||||
}
|
||||
"novita" => Some(Self::Novita),
|
||||
"fireworks" | "fireworks-ai" => Some(Self::Fireworks),
|
||||
"moonshot" | "moonshot-ai" | "kimi" | "kimi-k2" => Some(Self::Moonshot),
|
||||
@@ -145,6 +153,8 @@ pub struct ProvidersToml {
|
||||
#[serde(default)]
|
||||
pub openrouter: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub xiaomi_mimo: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub novita: ProviderConfigToml,
|
||||
#[serde(default)]
|
||||
pub fireworks: ProviderConfigToml,
|
||||
@@ -168,6 +178,7 @@ impl ProvidersToml {
|
||||
ProviderKind::Atlascloud => &self.atlascloud,
|
||||
ProviderKind::WanjieArk => &self.wanjie_ark,
|
||||
ProviderKind::Openrouter => &self.openrouter,
|
||||
ProviderKind::XiaomiMimo => &self.xiaomi_mimo,
|
||||
ProviderKind::Novita => &self.novita,
|
||||
ProviderKind::Fireworks => &self.fireworks,
|
||||
ProviderKind::Moonshot => &self.moonshot,
|
||||
@@ -185,6 +196,7 @@ impl ProvidersToml {
|
||||
ProviderKind::Atlascloud => &mut self.atlascloud,
|
||||
ProviderKind::WanjieArk => &mut self.wanjie_ark,
|
||||
ProviderKind::Openrouter => &mut self.openrouter,
|
||||
ProviderKind::XiaomiMimo => &mut self.xiaomi_mimo,
|
||||
ProviderKind::Novita => &mut self.novita,
|
||||
ProviderKind::Fireworks => &mut self.fireworks,
|
||||
ProviderKind::Moonshot => &mut self.moonshot,
|
||||
@@ -405,6 +417,10 @@ impl ConfigToml {
|
||||
&mut self.providers.openrouter,
|
||||
&project.providers.openrouter,
|
||||
);
|
||||
merge_project_provider_config(
|
||||
&mut self.providers.xiaomi_mimo,
|
||||
&project.providers.xiaomi_mimo,
|
||||
);
|
||||
merge_project_provider_config(&mut self.providers.novita, &project.providers.novita);
|
||||
merge_project_provider_config(&mut self.providers.fireworks, &project.providers.fireworks);
|
||||
merge_project_provider_config(&mut self.providers.sglang, &project.providers.sglang);
|
||||
@@ -464,6 +480,12 @@ impl ConfigToml {
|
||||
"providers.openrouter.http_headers" => {
|
||||
serialize_http_headers(&self.providers.openrouter.http_headers)
|
||||
}
|
||||
"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.http_headers" => {
|
||||
serialize_http_headers(&self.providers.xiaomi_mimo.http_headers)
|
||||
}
|
||||
"providers.novita.api_key" => self.providers.novita.api_key.clone(),
|
||||
"providers.novita.base_url" => self.providers.novita.base_url.clone(),
|
||||
"providers.novita.model" => self.providers.novita.model.clone(),
|
||||
@@ -609,6 +631,18 @@ impl ConfigToml {
|
||||
"providers.openrouter.http_headers" => {
|
||||
self.providers.openrouter.http_headers = parse_http_headers(value)?;
|
||||
}
|
||||
"providers.xiaomi_mimo.api_key" => {
|
||||
self.providers.xiaomi_mimo.api_key = Some(value.to_string());
|
||||
}
|
||||
"providers.xiaomi_mimo.base_url" => {
|
||||
self.providers.xiaomi_mimo.base_url = Some(value.to_string());
|
||||
}
|
||||
"providers.xiaomi_mimo.model" => {
|
||||
self.providers.xiaomi_mimo.model = Some(value.to_string());
|
||||
}
|
||||
"providers.xiaomi_mimo.http_headers" => {
|
||||
self.providers.xiaomi_mimo.http_headers = parse_http_headers(value)?;
|
||||
}
|
||||
"providers.novita.api_key" => {
|
||||
self.providers.novita.api_key = Some(value.to_string());
|
||||
}
|
||||
@@ -744,6 +778,12 @@ impl ConfigToml {
|
||||
"providers.openrouter.base_url" => self.providers.openrouter.base_url = None,
|
||||
"providers.openrouter.model" => self.providers.openrouter.model = None,
|
||||
"providers.openrouter.http_headers" => self.providers.openrouter.http_headers.clear(),
|
||||
"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.http_headers" => {
|
||||
self.providers.xiaomi_mimo.http_headers.clear();
|
||||
}
|
||||
"providers.novita.api_key" => self.providers.novita.api_key = None,
|
||||
"providers.novita.base_url" => self.providers.novita.base_url = None,
|
||||
"providers.novita.model" => self.providers.novita.model = None,
|
||||
@@ -886,6 +926,21 @@ impl ConfigToml {
|
||||
if let Some(v) = serialize_http_headers(&self.providers.openrouter.http_headers) {
|
||||
out.insert("providers.openrouter.http_headers".to_string(), v);
|
||||
}
|
||||
if let Some(v) = self.providers.xiaomi_mimo.api_key.as_ref() {
|
||||
out.insert(
|
||||
"providers.xiaomi_mimo.api_key".to_string(),
|
||||
redact_secret(v),
|
||||
);
|
||||
}
|
||||
if let Some(v) = self.providers.xiaomi_mimo.base_url.as_ref() {
|
||||
out.insert("providers.xiaomi_mimo.base_url".to_string(), v.clone());
|
||||
}
|
||||
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) = serialize_http_headers(&self.providers.xiaomi_mimo.http_headers) {
|
||||
out.insert("providers.xiaomi_mimo.http_headers".to_string(), v);
|
||||
}
|
||||
if let Some(v) = self.providers.novita.api_key.as_ref() {
|
||||
out.insert("providers.novita.api_key".to_string(), redact_secret(v));
|
||||
}
|
||||
@@ -1023,6 +1078,7 @@ impl ConfigToml {
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL.to_string(),
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL.to_string(),
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL.to_string(),
|
||||
ProviderKind::XiaomiMimo => DEFAULT_XIAOMI_MIMO_BASE_URL.to_string(),
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL.to_string(),
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL.to_string(),
|
||||
ProviderKind::Moonshot => {
|
||||
@@ -1225,7 +1281,10 @@ pub fn load_project_config(workspace: &Path) -> Option<ConfigToml> {
|
||||
fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
|
||||
if matches!(
|
||||
provider,
|
||||
ProviderKind::Atlascloud | ProviderKind::WanjieArk | ProviderKind::Ollama
|
||||
ProviderKind::Atlascloud
|
||||
| ProviderKind::WanjieArk
|
||||
| ProviderKind::XiaomiMimo
|
||||
| ProviderKind::Ollama
|
||||
) {
|
||||
return model.to_string();
|
||||
}
|
||||
@@ -1288,6 +1347,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_MODEL,
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_MODEL,
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_MODEL,
|
||||
ProviderKind::XiaomiMimo => DEFAULT_XIAOMI_MIMO_MODEL,
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_MODEL,
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_MODEL,
|
||||
ProviderKind::Moonshot => DEFAULT_MOONSHOT_MODEL,
|
||||
@@ -1305,6 +1365,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
|
||||
ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL,
|
||||
ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL,
|
||||
ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL,
|
||||
ProviderKind::XiaomiMimo => DEFAULT_XIAOMI_MIMO_BASE_URL,
|
||||
ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL,
|
||||
ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL,
|
||||
ProviderKind::Moonshot => DEFAULT_MOONSHOT_BASE_URL,
|
||||
@@ -1800,6 +1861,7 @@ struct EnvRuntimeOverrides {
|
||||
model: Option<String>,
|
||||
wanjie_ark_model: Option<String>,
|
||||
moonshot_model: Option<String>,
|
||||
xiaomi_mimo_model: Option<String>,
|
||||
output_mode: Option<String>,
|
||||
auth_mode: Option<String>,
|
||||
log_level: Option<String>,
|
||||
@@ -1814,6 +1876,7 @@ struct EnvRuntimeOverrides {
|
||||
atlascloud_base_url: Option<String>,
|
||||
wanjie_ark_base_url: Option<String>,
|
||||
openrouter_base_url: Option<String>,
|
||||
xiaomi_mimo_base_url: Option<String>,
|
||||
novita_base_url: Option<String>,
|
||||
fireworks_base_url: Option<String>,
|
||||
moonshot_base_url: Option<String>,
|
||||
@@ -1844,6 +1907,10 @@ impl EnvRuntimeOverrides {
|
||||
.or_else(|_| std::env::var("KIMI_MODEL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
xiaomi_mimo_model: std::env::var("XIAOMI_MIMO_MODEL")
|
||||
.or_else(|_| std::env::var("MIMO_MODEL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
output_mode: std::env::var("DEEPSEEK_OUTPUT_MODE").ok(),
|
||||
auth_mode: std::env::var("DEEPSEEK_AUTH_MODE").ok(),
|
||||
log_level: std::env::var("DEEPSEEK_LOG_LEVEL").ok(),
|
||||
@@ -1882,6 +1949,10 @@ impl EnvRuntimeOverrides {
|
||||
openrouter_base_url: std::env::var("OPENROUTER_BASE_URL")
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
xiaomi_mimo_base_url: std::env::var("XIAOMI_MIMO_BASE_URL")
|
||||
.or_else(|_| std::env::var("MIMO_BASE_URL"))
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
novita_base_url: std::env::var("NOVITA_BASE_URL")
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty()),
|
||||
@@ -1914,6 +1985,7 @@ impl EnvRuntimeOverrides {
|
||||
ProviderKind::Atlascloud => self.atlascloud_base_url.clone(),
|
||||
ProviderKind::WanjieArk => self.wanjie_ark_base_url.clone(),
|
||||
ProviderKind::Openrouter => self.openrouter_base_url.clone(),
|
||||
ProviderKind::XiaomiMimo => self.xiaomi_mimo_base_url.clone(),
|
||||
ProviderKind::Novita => self.novita_base_url.clone(),
|
||||
ProviderKind::Fireworks => self.fireworks_base_url.clone(),
|
||||
ProviderKind::Moonshot => self.moonshot_base_url.clone(),
|
||||
@@ -1927,6 +1999,7 @@ impl EnvRuntimeOverrides {
|
||||
match provider {
|
||||
ProviderKind::WanjieArk => self.wanjie_ark_model.clone(),
|
||||
ProviderKind::Moonshot => self.moonshot_model.clone(),
|
||||
ProviderKind::XiaomiMimo => self.xiaomi_mimo_model.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1975,6 +2048,12 @@ mod tests {
|
||||
nvidia_nim_base_url: Option<OsString>,
|
||||
openrouter_api_key: Option<OsString>,
|
||||
openrouter_base_url: Option<OsString>,
|
||||
xiaomi_mimo_api_key: Option<OsString>,
|
||||
mimo_api_key: Option<OsString>,
|
||||
xiaomi_mimo_base_url: Option<OsString>,
|
||||
mimo_base_url: Option<OsString>,
|
||||
xiaomi_mimo_model: Option<OsString>,
|
||||
mimo_model: Option<OsString>,
|
||||
wanjie_ark_api_key: Option<OsString>,
|
||||
wanjie_ark_base_url: Option<OsString>,
|
||||
wanjie_base_url: Option<OsString>,
|
||||
@@ -2024,6 +2103,12 @@ mod tests {
|
||||
nvidia_nim_base_url: env::var_os("NVIDIA_NIM_BASE_URL"),
|
||||
openrouter_api_key: env::var_os("OPENROUTER_API_KEY"),
|
||||
openrouter_base_url: env::var_os("OPENROUTER_BASE_URL"),
|
||||
xiaomi_mimo_api_key: env::var_os("XIAOMI_MIMO_API_KEY"),
|
||||
mimo_api_key: env::var_os("MIMO_API_KEY"),
|
||||
xiaomi_mimo_base_url: env::var_os("XIAOMI_MIMO_BASE_URL"),
|
||||
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"),
|
||||
wanjie_ark_api_key: env::var_os("WANJIE_ARK_API_KEY"),
|
||||
wanjie_ark_base_url: env::var_os("WANJIE_ARK_BASE_URL"),
|
||||
wanjie_base_url: env::var_os("WANJIE_BASE_URL"),
|
||||
@@ -2068,6 +2153,12 @@ mod tests {
|
||||
env::remove_var("NVIDIA_NIM_BASE_URL");
|
||||
env::remove_var("OPENROUTER_API_KEY");
|
||||
env::remove_var("OPENROUTER_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_API_KEY");
|
||||
env::remove_var("MIMO_API_KEY");
|
||||
env::remove_var("XIAOMI_MIMO_BASE_URL");
|
||||
env::remove_var("MIMO_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_MODEL");
|
||||
env::remove_var("MIMO_MODEL");
|
||||
env::remove_var("WANJIE_ARK_API_KEY");
|
||||
env::remove_var("WANJIE_ARK_BASE_URL");
|
||||
env::remove_var("WANJIE_BASE_URL");
|
||||
@@ -2129,6 +2220,12 @@ mod tests {
|
||||
Self::restore_var("NVIDIA_NIM_BASE_URL", self.nvidia_nim_base_url.take());
|
||||
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("XIAOMI_MIMO_API_KEY", self.xiaomi_mimo_api_key.take());
|
||||
Self::restore_var("MIMO_API_KEY", self.mimo_api_key.take());
|
||||
Self::restore_var("XIAOMI_MIMO_BASE_URL", self.xiaomi_mimo_base_url.take());
|
||||
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("WANJIE_ARK_API_KEY", self.wanjie_ark_api_key.take());
|
||||
Self::restore_var("WANJIE_ARK_BASE_URL", self.wanjie_ark_base_url.take());
|
||||
Self::restore_var("WANJIE_BASE_URL", self.wanjie_base_url.take());
|
||||
@@ -2712,6 +2809,14 @@ mod tests {
|
||||
ProviderKind::parse("OPEN_ROUTER"),
|
||||
Some(ProviderKind::Openrouter)
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderKind::parse("xiaomi-mimo"),
|
||||
Some(ProviderKind::XiaomiMimo)
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderKind::parse("xiaomi"),
|
||||
Some(ProviderKind::XiaomiMimo)
|
||||
);
|
||||
assert_eq!(ProviderKind::parse("novita"), Some(ProviderKind::Novita));
|
||||
assert_eq!(ProviderKind::parse("Novita"), Some(ProviderKind::Novita));
|
||||
assert_eq!(
|
||||
@@ -2777,6 +2882,22 @@ mod tests {
|
||||
assert_eq!(resolved.model, DEFAULT_OPENROUTER_MODEL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_provider_defaults_to_canonical_endpoint_and_model() {
|
||||
let _lock = env_lock();
|
||||
let _env = EnvGuard::without_deepseek_runtime_overrides();
|
||||
let config = ConfigToml {
|
||||
provider: ProviderKind::XiaomiMimo,
|
||||
..ConfigToml::default()
|
||||
};
|
||||
|
||||
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.base_url, DEFAULT_XIAOMI_MIMO_BASE_URL);
|
||||
assert_eq!(resolved.model, DEFAULT_XIAOMI_MIMO_MODEL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn novita_provider_defaults_to_canonical_endpoint_and_model() {
|
||||
let _lock = env_lock();
|
||||
@@ -3181,6 +3302,27 @@ mod tests {
|
||||
assert_eq!(resolved.base_url, DEFAULT_OPENROUTER_BASE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_overrides_provider_key_base_url_and_model() {
|
||||
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("MIMO_API_KEY", "mimo-env-key");
|
||||
env::set_var("MIMO_BASE_URL", "https://mimo-gateway.example/v1");
|
||||
env::set_var("MIMO_MODEL", "mimo-v2.5");
|
||||
}
|
||||
|
||||
let resolved =
|
||||
ConfigToml::default().resolve_runtime_options(&CliRuntimeOverrides::default());
|
||||
|
||||
assert_eq!(resolved.provider, ProviderKind::XiaomiMimo);
|
||||
assert_eq!(resolved.api_key.as_deref(), Some("mimo-env-key"));
|
||||
assert_eq!(resolved.base_url, "https://mimo-gateway.example/v1");
|
||||
assert_eq!(resolved.model, "mimo-v2.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn novita_env_api_key_falls_back_when_config_missing() {
|
||||
let _lock = env_lock();
|
||||
|
||||
@@ -525,6 +525,9 @@ pub fn env_for(name: &str) -> Option<String> {
|
||||
let candidates: &[&str] = match name.to_ascii_lowercase().as_str() {
|
||||
"deepseek" => &["DEEPSEEK_API_KEY"],
|
||||
"openrouter" => &["OPENROUTER_API_KEY"],
|
||||
"xiaomi-mimo" | "xiaomi_mimo" | "xiaomimimo" | "mimo" | "xiaomi" => {
|
||||
&["XIAOMI_MIMO_API_KEY", "MIMO_API_KEY"]
|
||||
}
|
||||
"novita" => &["NOVITA_API_KEY"],
|
||||
// NVIDIA NIM falls back to `DEEPSEEK_API_KEY` last because the
|
||||
// catalog endpoint accepts the same DeepSeek-issued key when no
|
||||
@@ -587,6 +590,8 @@ mod tests {
|
||||
"WANJIE_ARK_API_KEY",
|
||||
"WANJIE_API_KEY",
|
||||
"WANJIE_MAAS_API_KEY",
|
||||
"XIAOMI_MIMO_API_KEY",
|
||||
"MIMO_API_KEY",
|
||||
SECRET_BACKEND_ENV,
|
||||
] {
|
||||
// Safety: tests serialise on env_lock(); the broader
|
||||
@@ -764,6 +769,20 @@ mod tests {
|
||||
clear_known_envs();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_env_aliases_resolve() {
|
||||
let _guard = env_lock();
|
||||
clear_known_envs();
|
||||
unsafe { std::env::set_var("MIMO_API_KEY", "mimo-key") };
|
||||
|
||||
assert_eq!(env_for("xiaomi-mimo").as_deref(), Some("mimo-key"));
|
||||
assert_eq!(env_for("xiaomimimo").as_deref(), Some("mimo-key"));
|
||||
assert_eq!(env_for("mimo").as_deref(), Some("mimo-key"));
|
||||
assert_eq!(env_for("xiaomi").as_deref(), Some("mimo-key"));
|
||||
|
||||
clear_known_envs();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fireworks_env_aliases_resolve() {
|
||||
let _lock = env_lock();
|
||||
|
||||
@@ -882,6 +882,7 @@ pub(super) fn apply_reasoning_effort(
|
||||
ApiProvider::Deepseek
|
||||
| ApiProvider::DeepseekCN
|
||||
| ApiProvider::Openrouter
|
||||
| ApiProvider::XiaomiMimo
|
||||
| ApiProvider::Novita
|
||||
| ApiProvider::Sglang => {
|
||||
body["thinking"] = json!({ "type": "disabled" });
|
||||
@@ -930,6 +931,9 @@ pub(super) fn apply_reasoning_effort(
|
||||
body["reasoning_effort"] = json!(value);
|
||||
body["thinking"] = json!({ "type": "enabled" });
|
||||
}
|
||||
ApiProvider::XiaomiMimo => {
|
||||
body["thinking"] = json!({ "type": "enabled" });
|
||||
}
|
||||
ApiProvider::Fireworks => {
|
||||
body["reasoning_effort"] = json!("high");
|
||||
}
|
||||
@@ -967,6 +971,9 @@ pub(super) fn apply_reasoning_effort(
|
||||
body["reasoning_effort"] = json!("xhigh");
|
||||
body["thinking"] = json!({ "type": "enabled" });
|
||||
}
|
||||
ApiProvider::XiaomiMimo => {
|
||||
body["thinking"] = json!({ "type": "enabled" });
|
||||
}
|
||||
ApiProvider::Fireworks => {
|
||||
body["reasoning_effort"] = json!("max");
|
||||
}
|
||||
@@ -2044,6 +2051,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_uses_xiaomi_mimo_thinking_parameter_only() {
|
||||
for input in ["low", "medium", "max", "xhigh"] {
|
||||
let mut body = json!({});
|
||||
apply_reasoning_effort(&mut body, Some(input), ApiProvider::XiaomiMimo);
|
||||
|
||||
assert_eq!(
|
||||
body.pointer("/thinking/type").and_then(Value::as_str),
|
||||
Some("enabled"),
|
||||
"MiMo thinking mapping for {input}"
|
||||
);
|
||||
assert!(body.get("reasoning_effort").is_none());
|
||||
}
|
||||
|
||||
let mut body = json!({});
|
||||
apply_reasoning_effort(&mut body, Some("off"), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(
|
||||
body.pointer("/thinking/type").and_then(Value::as_str),
|
||||
Some("disabled")
|
||||
);
|
||||
assert!(body.get("reasoning_effort").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chat_parser_accepts_nvidia_nim_reasoning_field() -> Result<()> {
|
||||
let response = parse_chat_message(&json!({
|
||||
|
||||
@@ -71,6 +71,17 @@ use super::{
|
||||
release_stream_buffer, system_to_instructions, to_api_tool_name,
|
||||
};
|
||||
|
||||
fn apply_provider_token_limit(body: &mut Value, provider: ApiProvider, max_tokens: u32) {
|
||||
if provider != ApiProvider::XiaomiMimo {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(object) = body.as_object_mut() {
|
||||
object.remove("max_tokens");
|
||||
}
|
||||
body["max_completion_tokens"] = json!(max_tokens);
|
||||
}
|
||||
|
||||
impl DeepSeekClient {
|
||||
pub(super) async fn create_message_chat(
|
||||
&self,
|
||||
@@ -82,6 +93,7 @@ impl DeepSeekClient {
|
||||
"messages": messages,
|
||||
"max_tokens": request.max_tokens,
|
||||
});
|
||||
apply_provider_token_limit(&mut body, self.api_provider, request.max_tokens);
|
||||
|
||||
if let Some(temperature) = request.temperature {
|
||||
body["temperature"] = json!(temperature);
|
||||
@@ -156,6 +168,7 @@ impl DeepSeekClient {
|
||||
"include_usage": true
|
||||
},
|
||||
});
|
||||
apply_provider_token_limit(&mut body, self.api_provider, request.max_tokens);
|
||||
|
||||
if let Some(temperature) = request.temperature {
|
||||
body["temperature"] = json!(temperature);
|
||||
@@ -1729,6 +1742,7 @@ fn provider_accepts_reasoning_content(provider: ApiProvider) -> bool {
|
||||
| ApiProvider::DeepseekCN
|
||||
| ApiProvider::NvidiaNim
|
||||
| ApiProvider::Openrouter
|
||||
| ApiProvider::XiaomiMimo
|
||||
| ApiProvider::Novita
|
||||
| ApiProvider::Fireworks
|
||||
| ApiProvider::Sglang
|
||||
@@ -3092,11 +3106,12 @@ mod alias_thinking_detection_tests {
|
||||
//! turn. See upstream API docs:
|
||||
//! https://api-docs.deepseek.com/guides/thinking_mode
|
||||
use super::{
|
||||
is_reasoning_model_for_stream, provider_accepts_reasoning_content,
|
||||
requires_reasoning_content, should_replay_reasoning_content,
|
||||
should_replay_reasoning_content_for_provider,
|
||||
apply_provider_token_limit, is_reasoning_model_for_stream,
|
||||
provider_accepts_reasoning_content, requires_reasoning_content,
|
||||
should_replay_reasoning_content, should_replay_reasoning_content_for_provider,
|
||||
};
|
||||
use crate::config::ApiProvider;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn aliases_routed_to_v4_require_reasoning_content() {
|
||||
@@ -3162,6 +3177,25 @@ mod alias_thinking_detection_tests {
|
||||
assert!(!provider_accepts_reasoning_content(ApiProvider::Openai));
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::Deepseek));
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::NvidiaNim));
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::XiaomiMimo));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_uses_max_completion_tokens_payload_key() {
|
||||
let mut body = json!({
|
||||
"model": "mimo-v2.5-pro",
|
||||
"messages": [],
|
||||
"max_tokens": 8192,
|
||||
});
|
||||
|
||||
apply_provider_token_limit(&mut body, ApiProvider::XiaomiMimo, 8192);
|
||||
|
||||
assert!(body.get("max_tokens").is_none());
|
||||
assert_eq!(
|
||||
body.get("max_completion_tokens")
|
||||
.and_then(serde_json::Value::as_u64),
|
||||
Some(8192)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn provider(app: &mut App, args: Option<&str>) -> CommandResult {
|
||||
|
||||
let Some(target) = ApiProvider::parse(name) else {
|
||||
return CommandResult::error(format!(
|
||||
"Unknown provider '{name}'. Expected: deepseek, nvidia-nim, openai, atlascloud, wanjie-ark, openrouter, novita, fireworks, sglang, vllm, or ollama."
|
||||
"Unknown provider '{name}'. Expected: deepseek, nvidia-nim, openai, atlascloud, wanjie-ark, openrouter, xiaomi-mimo, novita, fireworks, sglang, vllm, or ollama."
|
||||
));
|
||||
};
|
||||
|
||||
@@ -112,6 +112,7 @@ mod tests {
|
||||
let msg = result.message.expect("expected error message");
|
||||
assert!(msg.contains("Unknown provider"));
|
||||
assert!(msg.contains("openrouter"));
|
||||
assert!(msg.contains("xiaomi-mimo"));
|
||||
assert!(msg.contains("novita"));
|
||||
assert!(result.action.is_none());
|
||||
}
|
||||
@@ -129,6 +130,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_xiaomi_mimo_emits_action() {
|
||||
let mut app = create_test_app();
|
||||
let result = provider(&mut app, Some("xiaomi-mimo"));
|
||||
match result.action {
|
||||
Some(AppAction::SwitchProvider { provider, model }) => {
|
||||
assert_eq!(provider, ApiProvider::XiaomiMimo);
|
||||
assert_eq!(model, None);
|
||||
}
|
||||
other => panic!("expected SwitchProvider, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_to_atlascloud_emits_action() {
|
||||
let mut app = create_test_app();
|
||||
|
||||
+188
-1
@@ -46,6 +46,8 @@ pub const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.c
|
||||
pub const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
pub const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
|
||||
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 DEFAULT_XIAOMI_MIMO_BASE_URL: &str = "https://api.xiaomimimo.com/v1";
|
||||
pub const DEFAULT_NOVITA_MODEL: &str = "deepseek/deepseek-v4-pro";
|
||||
pub const DEFAULT_NOVITA_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash";
|
||||
pub const DEFAULT_NOVITA_BASE_URL: &str = "https://api.novita.ai/v1";
|
||||
@@ -91,6 +93,7 @@ pub enum ApiProvider {
|
||||
Atlascloud,
|
||||
WanjieArk,
|
||||
Openrouter,
|
||||
XiaomiMimo,
|
||||
Novita,
|
||||
Fireworks,
|
||||
Moonshot,
|
||||
@@ -113,6 +116,9 @@ impl ApiProvider {
|
||||
"wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark"
|
||||
| "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk),
|
||||
"openrouter" | "open_router" => Some(Self::Openrouter),
|
||||
"xiaomi-mimo" | "xiaomi_mimo" | "xiaomimimo" | "mimo" | "xiaomi" => {
|
||||
Some(Self::XiaomiMimo)
|
||||
}
|
||||
"novita" => Some(Self::Novita),
|
||||
"fireworks" | "fireworks-ai" => Some(Self::Fireworks),
|
||||
"moonshot" | "moonshot-ai" | "kimi" | "kimi-k2" => Some(Self::Moonshot),
|
||||
@@ -133,6 +139,7 @@ impl ApiProvider {
|
||||
Self::Atlascloud => "atlascloud",
|
||||
Self::WanjieArk => "wanjie-ark",
|
||||
Self::Openrouter => "openrouter",
|
||||
Self::XiaomiMimo => "xiaomi-mimo",
|
||||
Self::Novita => "novita",
|
||||
Self::Fireworks => "fireworks",
|
||||
Self::Moonshot => "moonshot",
|
||||
@@ -153,6 +160,7 @@ impl ApiProvider {
|
||||
Self::Atlascloud => "AtlasCloud",
|
||||
Self::WanjieArk => "Wanjie Ark",
|
||||
Self::Openrouter => "OpenRouter",
|
||||
Self::XiaomiMimo => "Xiaomi MiMo",
|
||||
Self::Novita => "Novita AI",
|
||||
Self::Fireworks => "Fireworks AI",
|
||||
Self::Moonshot => "Moonshot/Kimi",
|
||||
@@ -172,6 +180,7 @@ impl ApiProvider {
|
||||
Self::Atlascloud,
|
||||
Self::WanjieArk,
|
||||
Self::Openrouter,
|
||||
Self::XiaomiMimo,
|
||||
Self::Novita,
|
||||
Self::Fireworks,
|
||||
Self::Moonshot,
|
||||
@@ -259,6 +268,19 @@ pub fn provider_capability(provider: ApiProvider, resolved_model: &str) -> Provi
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(provider, ApiProvider::XiaomiMimo) {
|
||||
return ProviderCapability {
|
||||
provider,
|
||||
resolved_model: resolved_model.to_string(),
|
||||
context_window: 1_000_000,
|
||||
max_output: 128_000,
|
||||
thinking_supported: true,
|
||||
cache_telemetry_supported: false,
|
||||
request_payload_mode: RequestPayloadMode::ChatCompletions,
|
||||
alias_deprecation: None,
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(provider, ApiProvider::Ollama) {
|
||||
return ProviderCapability {
|
||||
provider,
|
||||
@@ -443,6 +465,7 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati
|
||||
ApiProvider::Deepseek | ApiProvider::DeepseekCN => OFFICIAL_DEEPSEEK_MODELS.to_vec(),
|
||||
ApiProvider::NvidiaNim => vec![DEFAULT_NVIDIA_NIM_MODEL, DEFAULT_NVIDIA_NIM_FLASH_MODEL],
|
||||
ApiProvider::Openrouter => vec![DEFAULT_OPENROUTER_MODEL, DEFAULT_OPENROUTER_FLASH_MODEL],
|
||||
ApiProvider::XiaomiMimo => vec![DEFAULT_XIAOMI_MIMO_MODEL, "mimo-v2.5"],
|
||||
ApiProvider::Novita => vec![DEFAULT_NOVITA_MODEL, DEFAULT_NOVITA_FLASH_MODEL],
|
||||
ApiProvider::Fireworks => vec![DEFAULT_FIREWORKS_MODEL],
|
||||
ApiProvider::Moonshot => vec![DEFAULT_MOONSHOT_MODEL],
|
||||
@@ -1342,6 +1365,8 @@ pub struct ProvidersConfig {
|
||||
#[serde(default)]
|
||||
pub openrouter: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub xiaomi_mimo: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub novita: ProviderConfig,
|
||||
#[serde(default)]
|
||||
pub fireworks: ProviderConfig,
|
||||
@@ -1501,6 +1526,7 @@ impl Config {
|
||||
ApiProvider::Atlascloud => "providers.atlascloud",
|
||||
ApiProvider::WanjieArk => "providers.wanjie_ark",
|
||||
ApiProvider::Openrouter => "providers.openrouter",
|
||||
ApiProvider::XiaomiMimo => "providers.xiaomi_mimo",
|
||||
ApiProvider::Novita => "providers.novita",
|
||||
ApiProvider::Fireworks => "providers.fireworks",
|
||||
ApiProvider::Moonshot => "providers.moonshot",
|
||||
@@ -1523,7 +1549,7 @@ impl Config {
|
||||
&& ApiProvider::parse(provider).is_none()
|
||||
{
|
||||
anyhow::bail!(
|
||||
"Invalid provider '{provider}': expected deepseek, deepseek-cn, nvidia-nim, openai, atlascloud, wanjie-ark, openrouter, novita, fireworks, sglang, vllm, or ollama."
|
||||
"Invalid provider '{provider}': expected deepseek, deepseek-cn, nvidia-nim, openai, atlascloud, wanjie-ark, openrouter, xiaomi-mimo, novita, fireworks, sglang, vllm, or ollama."
|
||||
);
|
||||
}
|
||||
if let Some(ref key) = self.api_key
|
||||
@@ -1643,6 +1669,7 @@ impl Config {
|
||||
ApiProvider::Atlascloud => &providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &providers.wanjie_ark,
|
||||
ApiProvider::Openrouter => &providers.openrouter,
|
||||
ApiProvider::XiaomiMimo => &providers.xiaomi_mimo,
|
||||
ApiProvider::Novita => &providers.novita,
|
||||
ApiProvider::Fireworks => &providers.fireworks,
|
||||
ApiProvider::Moonshot => &providers.moonshot,
|
||||
@@ -1733,6 +1760,7 @@ impl Config {
|
||||
ApiProvider::Atlascloud => DEFAULT_ATLASCLOUD_MODEL,
|
||||
ApiProvider::WanjieArk => DEFAULT_WANJIE_ARK_MODEL,
|
||||
ApiProvider::Openrouter => DEFAULT_OPENROUTER_MODEL,
|
||||
ApiProvider::XiaomiMimo => DEFAULT_XIAOMI_MIMO_MODEL,
|
||||
ApiProvider::Novita => DEFAULT_NOVITA_MODEL,
|
||||
ApiProvider::Fireworks => DEFAULT_FIREWORKS_MODEL,
|
||||
ApiProvider::Moonshot => DEFAULT_MOONSHOT_MODEL,
|
||||
@@ -1765,6 +1793,7 @@ impl Config {
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::Openrouter
|
||||
| ApiProvider::XiaomiMimo
|
||||
| ApiProvider::Novita
|
||||
| ApiProvider::Fireworks
|
||||
| ApiProvider::Moonshot
|
||||
@@ -1781,6 +1810,7 @@ impl Config {
|
||||
ApiProvider::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL,
|
||||
ApiProvider::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL,
|
||||
ApiProvider::Openrouter => DEFAULT_OPENROUTER_BASE_URL,
|
||||
ApiProvider::XiaomiMimo => DEFAULT_XIAOMI_MIMO_BASE_URL,
|
||||
ApiProvider::Novita => DEFAULT_NOVITA_BASE_URL,
|
||||
ApiProvider::Fireworks => DEFAULT_FIREWORKS_BASE_URL,
|
||||
ApiProvider::Moonshot => {
|
||||
@@ -1824,6 +1854,7 @@ impl Config {
|
||||
ApiProvider::Atlascloud => "atlascloud",
|
||||
ApiProvider::WanjieArk => "wanjie-ark",
|
||||
ApiProvider::Openrouter => "openrouter",
|
||||
ApiProvider::XiaomiMimo => "xiaomi-mimo",
|
||||
ApiProvider::Novita => "novita",
|
||||
ApiProvider::Fireworks => "fireworks",
|
||||
ApiProvider::Moonshot => "moonshot",
|
||||
@@ -1909,6 +1940,10 @@ impl Config {
|
||||
"OpenRouter API key not found. Run 'codewhale auth set --provider openrouter', \
|
||||
set OPENROUTER_API_KEY, or add [providers.openrouter] api_key in ~/.deepseek/config.toml."
|
||||
),
|
||||
ApiProvider::XiaomiMimo => anyhow::bail!(
|
||||
"Xiaomi MiMo API key not found. Run 'codewhale auth set --provider xiaomi-mimo', \
|
||||
set XIAOMI_MIMO_API_KEY/MIMO_API_KEY, or add [providers.xiaomi_mimo] api_key in ~/.deepseek/config.toml."
|
||||
),
|
||||
ApiProvider::Novita => anyhow::bail!(
|
||||
"Novita API key not found. Run 'codewhale auth set --provider novita', \
|
||||
set NOVITA_API_KEY, or add [providers.novita] api_key in ~/.deepseek/config.toml."
|
||||
@@ -2497,6 +2532,13 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.openrouter
|
||||
.base_url = Some(value);
|
||||
}
|
||||
ApiProvider::XiaomiMimo => {
|
||||
config
|
||||
.providers
|
||||
.get_or_insert_with(ProvidersConfig::default)
|
||||
.xiaomi_mimo
|
||||
.base_url = Some(value);
|
||||
}
|
||||
ApiProvider::WanjieArk => {
|
||||
config
|
||||
.providers
|
||||
@@ -2599,6 +2641,17 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.openrouter
|
||||
.base_url = Some(value);
|
||||
}
|
||||
if matches!(config.api_provider(), ApiProvider::XiaomiMimo)
|
||||
&& let Ok(value) =
|
||||
std::env::var("XIAOMI_MIMO_BASE_URL").or_else(|_| std::env::var("MIMO_BASE_URL"))
|
||||
&& !value.trim().is_empty()
|
||||
{
|
||||
config
|
||||
.providers
|
||||
.get_or_insert_with(ProvidersConfig::default)
|
||||
.xiaomi_mimo
|
||||
.base_url = 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"))
|
||||
@@ -2682,6 +2735,7 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
ApiProvider::Atlascloud => &mut providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &mut providers.wanjie_ark,
|
||||
ApiProvider::Openrouter => &mut providers.openrouter,
|
||||
ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo,
|
||||
ApiProvider::Novita => &mut providers.novita,
|
||||
ApiProvider::Fireworks => &mut providers.fireworks,
|
||||
ApiProvider::Moonshot => &mut providers.moonshot,
|
||||
@@ -2727,6 +2781,16 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
.openai
|
||||
.model = Some(value);
|
||||
}
|
||||
if matches!(config.api_provider(), ApiProvider::XiaomiMimo)
|
||||
&& let Ok(value) =
|
||||
std::env::var("XIAOMI_MIMO_MODEL").or_else(|_| std::env::var("MIMO_MODEL"))
|
||||
{
|
||||
config
|
||||
.providers
|
||||
.get_or_insert_with(ProvidersConfig::default)
|
||||
.xiaomi_mimo
|
||||
.model = Some(value);
|
||||
}
|
||||
if matches!(config.api_provider(), ApiProvider::Atlascloud)
|
||||
&& let Ok(value) = std::env::var("ATLASCLOUD_MODEL")
|
||||
{
|
||||
@@ -2786,6 +2850,7 @@ fn apply_env_overrides(config: &mut Config) {
|
||||
ApiProvider::Atlascloud => &mut providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &mut providers.wanjie_ark,
|
||||
ApiProvider::Openrouter => &mut providers.openrouter,
|
||||
ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo,
|
||||
ApiProvider::Novita => &mut providers.novita,
|
||||
ApiProvider::Fireworks => &mut providers.fireworks,
|
||||
ApiProvider::Moonshot => &mut providers.moonshot,
|
||||
@@ -3050,6 +3115,7 @@ pub(crate) fn provider_passes_model_through(provider: ApiProvider) -> bool {
|
||||
ApiProvider::Openai
|
||||
| ApiProvider::Atlascloud
|
||||
| ApiProvider::WanjieArk
|
||||
| ApiProvider::XiaomiMimo
|
||||
| ApiProvider::Moonshot
|
||||
| ApiProvider::Ollama
|
||||
)
|
||||
@@ -3071,6 +3137,7 @@ fn default_base_url_for_provider(provider: ApiProvider) -> &'static str {
|
||||
ApiProvider::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL,
|
||||
ApiProvider::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL,
|
||||
ApiProvider::Openrouter => DEFAULT_OPENROUTER_BASE_URL,
|
||||
ApiProvider::XiaomiMimo => DEFAULT_XIAOMI_MIMO_BASE_URL,
|
||||
ApiProvider::Novita => DEFAULT_NOVITA_BASE_URL,
|
||||
ApiProvider::Fireworks => DEFAULT_FIREWORKS_BASE_URL,
|
||||
ApiProvider::Moonshot => DEFAULT_MOONSHOT_BASE_URL,
|
||||
@@ -3328,6 +3395,7 @@ fn merge_providers(
|
||||
atlascloud: merge_provider_config(base.atlascloud, override_cfg.atlascloud),
|
||||
wanjie_ark: merge_provider_config(base.wanjie_ark, override_cfg.wanjie_ark),
|
||||
openrouter: merge_provider_config(base.openrouter, override_cfg.openrouter),
|
||||
xiaomi_mimo: merge_provider_config(base.xiaomi_mimo, override_cfg.xiaomi_mimo),
|
||||
novita: merge_provider_config(base.novita, override_cfg.novita),
|
||||
fireworks: merge_provider_config(base.fireworks, override_cfg.fireworks),
|
||||
moonshot: merge_provider_config(base.moonshot, override_cfg.moonshot),
|
||||
@@ -3748,6 +3816,10 @@ pub fn active_provider_has_env_api_key(config: &Config) -> bool {
|
||||
ApiProvider::Openrouter => {
|
||||
std::env::var("OPENROUTER_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
}
|
||||
ApiProvider::XiaomiMimo => {
|
||||
std::env::var("XIAOMI_MIMO_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
|| std::env::var("MIMO_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
}
|
||||
ApiProvider::Novita => std::env::var("NOVITA_API_KEY").is_ok_and(|k| !k.trim().is_empty()),
|
||||
ApiProvider::Fireworks => {
|
||||
std::env::var("FIREWORKS_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
@@ -3779,6 +3851,7 @@ pub fn has_api_key_for(config: &Config, provider: ApiProvider) -> bool {
|
||||
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
|
||||
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY",
|
||||
ApiProvider::Openrouter => "OPENROUTER_API_KEY",
|
||||
ApiProvider::XiaomiMimo => "XIAOMI_MIMO_API_KEY",
|
||||
ApiProvider::Novita => "NOVITA_API_KEY",
|
||||
ApiProvider::Fireworks => "FIREWORKS_API_KEY",
|
||||
ApiProvider::Moonshot => "MOONSHOT_API_KEY",
|
||||
@@ -3800,6 +3873,11 @@ pub fn has_api_key_for(config: &Config, provider: ApiProvider) -> bool {
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if matches!(provider, ApiProvider::XiaomiMimo)
|
||||
&& std::env::var("MIMO_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if matches!(provider, ApiProvider::Moonshot)
|
||||
&& std::env::var("KIMI_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|
||||
{
|
||||
@@ -3873,6 +3951,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
|
||||
ApiProvider::Atlascloud => "providers.atlascloud",
|
||||
ApiProvider::WanjieArk => "providers.wanjie_ark",
|
||||
ApiProvider::Openrouter => "providers.openrouter",
|
||||
ApiProvider::XiaomiMimo => "providers.xiaomi_mimo",
|
||||
ApiProvider::Novita => "providers.novita",
|
||||
ApiProvider::Fireworks => "providers.fireworks",
|
||||
ApiProvider::Moonshot => "providers.moonshot",
|
||||
@@ -3910,6 +3989,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
|
||||
ApiProvider::Atlascloud => "atlascloud",
|
||||
ApiProvider::WanjieArk => "wanjie_ark",
|
||||
ApiProvider::Openrouter => "openrouter",
|
||||
ApiProvider::XiaomiMimo => "xiaomi_mimo",
|
||||
ApiProvider::Novita => "novita",
|
||||
ApiProvider::Fireworks => "fireworks",
|
||||
ApiProvider::Moonshot => "moonshot",
|
||||
@@ -3999,6 +4079,7 @@ fn provider_config_key(provider: ApiProvider) -> Result<&'static str> {
|
||||
ApiProvider::Atlascloud => Ok("atlascloud"),
|
||||
ApiProvider::WanjieArk => Ok("wanjie_ark"),
|
||||
ApiProvider::Openrouter => Ok("openrouter"),
|
||||
ApiProvider::XiaomiMimo => Ok("xiaomi_mimo"),
|
||||
ApiProvider::Novita => Ok("novita"),
|
||||
ApiProvider::Fireworks => Ok("fireworks"),
|
||||
ApiProvider::Moonshot => Ok("moonshot"),
|
||||
@@ -4391,6 +4472,12 @@ mod tests {
|
||||
wanjie_maas_model: Option<OsString>,
|
||||
openrouter_api_key: Option<OsString>,
|
||||
openrouter_base_url: Option<OsString>,
|
||||
xiaomi_mimo_api_key: Option<OsString>,
|
||||
mimo_api_key: Option<OsString>,
|
||||
xiaomi_mimo_base_url: Option<OsString>,
|
||||
mimo_base_url: Option<OsString>,
|
||||
xiaomi_mimo_model: Option<OsString>,
|
||||
mimo_model: Option<OsString>,
|
||||
novita_api_key: Option<OsString>,
|
||||
novita_base_url: Option<OsString>,
|
||||
fireworks_api_key: Option<OsString>,
|
||||
@@ -4456,6 +4543,12 @@ mod tests {
|
||||
let wanjie_maas_model_prev = env::var_os("WANJIE_MAAS_MODEL");
|
||||
let openrouter_api_key_prev = env::var_os("OPENROUTER_API_KEY");
|
||||
let openrouter_base_url_prev = env::var_os("OPENROUTER_BASE_URL");
|
||||
let xiaomi_mimo_api_key_prev = env::var_os("XIAOMI_MIMO_API_KEY");
|
||||
let mimo_api_key_prev = env::var_os("MIMO_API_KEY");
|
||||
let xiaomi_mimo_base_url_prev = env::var_os("XIAOMI_MIMO_BASE_URL");
|
||||
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 novita_api_key_prev = env::var_os("NOVITA_API_KEY");
|
||||
let novita_base_url_prev = env::var_os("NOVITA_BASE_URL");
|
||||
let fireworks_api_key_prev = env::var_os("FIREWORKS_API_KEY");
|
||||
@@ -4516,6 +4609,12 @@ mod tests {
|
||||
env::remove_var("WANJIE_MAAS_MODEL");
|
||||
env::remove_var("OPENROUTER_API_KEY");
|
||||
env::remove_var("OPENROUTER_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_API_KEY");
|
||||
env::remove_var("MIMO_API_KEY");
|
||||
env::remove_var("XIAOMI_MIMO_BASE_URL");
|
||||
env::remove_var("MIMO_BASE_URL");
|
||||
env::remove_var("XIAOMI_MIMO_MODEL");
|
||||
env::remove_var("MIMO_MODEL");
|
||||
env::remove_var("NOVITA_API_KEY");
|
||||
env::remove_var("NOVITA_BASE_URL");
|
||||
env::remove_var("FIREWORKS_API_KEY");
|
||||
@@ -4576,6 +4675,12 @@ mod tests {
|
||||
wanjie_maas_model: wanjie_maas_model_prev,
|
||||
openrouter_api_key: openrouter_api_key_prev,
|
||||
openrouter_base_url: openrouter_base_url_prev,
|
||||
xiaomi_mimo_api_key: xiaomi_mimo_api_key_prev,
|
||||
mimo_api_key: mimo_api_key_prev,
|
||||
xiaomi_mimo_base_url: xiaomi_mimo_base_url_prev,
|
||||
mimo_base_url: mimo_base_url_prev,
|
||||
xiaomi_mimo_model: xiaomi_mimo_model_prev,
|
||||
mimo_model: mimo_model_prev,
|
||||
novita_api_key: novita_api_key_prev,
|
||||
novita_base_url: novita_base_url_prev,
|
||||
fireworks_api_key: fireworks_api_key_prev,
|
||||
@@ -4645,6 +4750,12 @@ mod tests {
|
||||
Self::restore_var("WANJIE_MAAS_MODEL", self.wanjie_maas_model.take());
|
||||
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("XIAOMI_MIMO_API_KEY", self.xiaomi_mimo_api_key.take());
|
||||
Self::restore_var("MIMO_API_KEY", self.mimo_api_key.take());
|
||||
Self::restore_var("XIAOMI_MIMO_BASE_URL", self.xiaomi_mimo_base_url.take());
|
||||
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("NOVITA_API_KEY", self.novita_api_key.take());
|
||||
Self::restore_var("NOVITA_BASE_URL", self.novita_base_url.take());
|
||||
Self::restore_var("FIREWORKS_API_KEY", self.fireworks_api_key.take());
|
||||
@@ -5987,6 +6098,54 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_provider_uses_documented_defaults() -> Result<()> {
|
||||
let config = Config {
|
||||
provider: Some("xiaomi-mimo".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
config.validate()?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(config.default_model(), DEFAULT_XIAOMI_MIMO_MODEL);
|
||||
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();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"codewhale-tui-xiaomi-mimo-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", "mimo");
|
||||
env::set_var("MIMO_API_KEY", "mimo-env-key");
|
||||
env::set_var("MIMO_BASE_URL", "https://mimo-gateway.example/v1");
|
||||
env::set_var("MIMO_MODEL", "mimo-v2.5");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_provider(), ApiProvider::XiaomiMimo);
|
||||
assert_eq!(config.deepseek_api_key()?, "mimo-env-key");
|
||||
assert_eq!(
|
||||
config.deepseek_base_url(),
|
||||
"https://mimo-gateway.example/v1"
|
||||
);
|
||||
assert_eq!(config.default_model(), "mimo-v2.5");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atlascloud_provider_uses_documented_defaults() -> Result<()> {
|
||||
let config = Config {
|
||||
@@ -7034,6 +7193,7 @@ api_key = "moonshot-platform-key"
|
||||
assert!(!has_api_key_for(&config, ApiProvider::Openai));
|
||||
assert!(!has_api_key_for(&config, ApiProvider::WanjieArk));
|
||||
assert!(!has_api_key_for(&config, ApiProvider::Openrouter));
|
||||
assert!(!has_api_key_for(&config, ApiProvider::XiaomiMimo));
|
||||
assert!(
|
||||
has_api_key_for(&config, ApiProvider::Sglang),
|
||||
"SGLang is self-hosted and does not require a key by default"
|
||||
@@ -7048,10 +7208,12 @@ api_key = "moonshot-platform-key"
|
||||
env::set_var("OPENROUTER_API_KEY", "or-env");
|
||||
env::set_var("OPENAI_API_KEY", "openai-env");
|
||||
env::set_var("WANJIE_API_KEY", "wanjie-env");
|
||||
env::set_var("MIMO_API_KEY", "mimo-env");
|
||||
}
|
||||
assert!(has_api_key_for(&config, ApiProvider::Openai));
|
||||
assert!(has_api_key_for(&config, ApiProvider::WanjieArk));
|
||||
assert!(has_api_key_for(&config, ApiProvider::Openrouter));
|
||||
assert!(has_api_key_for(&config, ApiProvider::XiaomiMimo));
|
||||
assert!(!has_api_key_for(&config, ApiProvider::Novita));
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
@@ -7059,14 +7221,17 @@ api_key = "moonshot-platform-key"
|
||||
env::remove_var("OPENROUTER_API_KEY");
|
||||
env::remove_var("OPENAI_API_KEY");
|
||||
env::remove_var("WANJIE_API_KEY");
|
||||
env::remove_var("MIMO_API_KEY");
|
||||
}
|
||||
let mut providers = ProvidersConfig::default();
|
||||
providers.openai.api_key = Some("file-openai".to_string());
|
||||
providers.wanjie_ark.api_key = Some("file-wanjie".to_string());
|
||||
providers.xiaomi_mimo.api_key = Some("file-mimo".to_string());
|
||||
providers.novita.api_key = Some("file-novita".to_string());
|
||||
config.providers = Some(providers);
|
||||
assert!(has_api_key_for(&config, ApiProvider::Openai));
|
||||
assert!(has_api_key_for(&config, ApiProvider::WanjieArk));
|
||||
assert!(has_api_key_for(&config, ApiProvider::XiaomiMimo));
|
||||
assert!(has_api_key_for(&config, ApiProvider::Novita));
|
||||
assert!(!has_api_key_for(&config, ApiProvider::Openrouter));
|
||||
Ok(())
|
||||
@@ -7159,6 +7324,7 @@ api_key = "moonshot-platform-key"
|
||||
save_api_key_for(ApiProvider::Openai, "openai-saved-key")?;
|
||||
save_api_key_for(ApiProvider::WanjieArk, "wanjie-saved-key")?;
|
||||
save_api_key_for(ApiProvider::Fireworks, "fireworks-saved-key")?;
|
||||
save_api_key_for(ApiProvider::XiaomiMimo, "mimo-saved-key")?;
|
||||
save_api_key_for(ApiProvider::Sglang, "sglang-saved-key")?;
|
||||
let contents = fs::read_to_string(&path)?;
|
||||
let parsed: toml::Value = toml::from_str(&contents)?;
|
||||
@@ -7186,6 +7352,14 @@ api_key = "moonshot-platform-key"
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("fireworks-saved-key")
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.get("providers")
|
||||
.and_then(|p| p.get("xiaomi_mimo"))
|
||||
.and_then(|t| t.get("api_key"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("mimo-saved-key")
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.get("providers")
|
||||
@@ -7421,6 +7595,19 @@ model = "deepseek-ai/deepseek-v4-pro"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_xiaomi_mimo_has_thinking_no_cache() {
|
||||
let cap = provider_capability(ApiProvider::XiaomiMimo, DEFAULT_XIAOMI_MIMO_MODEL);
|
||||
assert_eq!(cap.context_window, 1_000_000);
|
||||
assert_eq!(cap.max_output, 128_000);
|
||||
assert!(cap.thinking_supported);
|
||||
assert!(!cap.cache_telemetry_supported);
|
||||
assert_eq!(
|
||||
cap.request_payload_mode,
|
||||
RequestPayloadMode::ChatCompletions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_novita_v4_pro_has_thinking_no_cache() {
|
||||
let cap = provider_capability(ApiProvider::Novita, DEFAULT_NOVITA_MODEL);
|
||||
|
||||
@@ -395,6 +395,7 @@ impl Engine {
|
||||
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
|
||||
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY/WANJIE_API_KEY/WANJIE_MAAS_API_KEY",
|
||||
ApiProvider::Openrouter => "OPENROUTER_API_KEY",
|
||||
ApiProvider::XiaomiMimo => "XIAOMI_MIMO_API_KEY/MIMO_API_KEY",
|
||||
ApiProvider::Novita => "NOVITA_API_KEY",
|
||||
ApiProvider::Fireworks => "FIREWORKS_API_KEY",
|
||||
ApiProvider::Moonshot => "MOONSHOT_API_KEY/KIMI_API_KEY",
|
||||
|
||||
@@ -1876,6 +1876,10 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
|
||||
"OPENROUTER_API_KEY",
|
||||
"codewhale auth set --provider openrouter --api-key \"...\"",
|
||||
),
|
||||
crate::config::ApiProvider::XiaomiMimo => (
|
||||
"XIAOMI_MIMO_API_KEY/MIMO_API_KEY",
|
||||
"codewhale auth set --provider xiaomi-mimo --api-key \"...\"",
|
||||
),
|
||||
crate::config::ApiProvider::Novita => (
|
||||
"NOVITA_API_KEY",
|
||||
"codewhale auth set --provider novita --api-key \"...\"",
|
||||
@@ -1912,6 +1916,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
|
||||
crate::config::ApiProvider::Atlascloud => "atlascloud",
|
||||
crate::config::ApiProvider::WanjieArk => "wanjie_ark",
|
||||
crate::config::ApiProvider::Openrouter => "openrouter",
|
||||
crate::config::ApiProvider::XiaomiMimo => "xiaomi_mimo",
|
||||
crate::config::ApiProvider::Novita => "novita",
|
||||
crate::config::ApiProvider::Fireworks => "fireworks",
|
||||
crate::config::ApiProvider::Moonshot => "moonshot",
|
||||
@@ -2218,6 +2223,11 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
|
||||
"openrouter",
|
||||
&["OPENROUTER_API_KEY"][..],
|
||||
),
|
||||
(
|
||||
crate::config::ApiProvider::XiaomiMimo,
|
||||
"xiaomi-mimo",
|
||||
&["XIAOMI_MIMO_API_KEY", "MIMO_API_KEY"][..],
|
||||
),
|
||||
(
|
||||
crate::config::ApiProvider::Novita,
|
||||
"novita",
|
||||
|
||||
@@ -102,6 +102,7 @@ impl ProviderPickerView {
|
||||
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
|
||||
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY",
|
||||
ApiProvider::Openrouter => "OPENROUTER_API_KEY",
|
||||
ApiProvider::XiaomiMimo => "XIAOMI_MIMO_API_KEY / MIMO_API_KEY",
|
||||
ApiProvider::Novita => "NOVITA_API_KEY",
|
||||
ApiProvider::Fireworks => "FIREWORKS_API_KEY",
|
||||
ApiProvider::Moonshot => "MOONSHOT_API_KEY / KIMI_API_KEY",
|
||||
@@ -473,6 +474,7 @@ mod tests {
|
||||
"AtlasCloud",
|
||||
"Wanjie Ark",
|
||||
"OpenRouter",
|
||||
"Xiaomi MiMo",
|
||||
"Novita AI",
|
||||
"Fireworks AI",
|
||||
"Moonshot/Kimi",
|
||||
|
||||
@@ -5932,6 +5932,7 @@ fn render(f: &mut Frame, app: &mut App) {
|
||||
crate::config::ApiProvider::Atlascloud => Some("Atlas"),
|
||||
crate::config::ApiProvider::WanjieArk => Some("Wanjie"),
|
||||
crate::config::ApiProvider::Openrouter => Some("OR"),
|
||||
crate::config::ApiProvider::XiaomiMimo => Some("MiMo"),
|
||||
crate::config::ApiProvider::Novita => Some("Novita"),
|
||||
crate::config::ApiProvider::Fireworks => Some("Fireworks"),
|
||||
crate::config::ApiProvider::Moonshot => Some("Kimi"),
|
||||
@@ -6849,6 +6850,7 @@ async fn apply_provider_picker_api_key(
|
||||
ApiProvider::Atlascloud => &mut providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &mut providers.wanjie_ark,
|
||||
ApiProvider::Openrouter => &mut providers.openrouter,
|
||||
ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo,
|
||||
ApiProvider::Novita => &mut providers.novita,
|
||||
ApiProvider::Fireworks => &mut providers.fireworks,
|
||||
ApiProvider::Moonshot => &mut providers.moonshot,
|
||||
@@ -6901,6 +6903,7 @@ fn set_provider_auth_mode_in_memory(config: &mut Config, provider: ApiProvider,
|
||||
ApiProvider::Atlascloud => &mut providers.atlascloud,
|
||||
ApiProvider::WanjieArk => &mut providers.wanjie_ark,
|
||||
ApiProvider::Openrouter => &mut providers.openrouter,
|
||||
ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo,
|
||||
ApiProvider::Novita => &mut providers.novita,
|
||||
ApiProvider::Fireworks => &mut providers.fireworks,
|
||||
ApiProvider::Moonshot => &mut providers.moonshot,
|
||||
|
||||
@@ -13,6 +13,8 @@ use crate::tools::spec::{
|
||||
ToolCapability, ToolContext, ToolError, ToolResult, ToolSpec, required_str,
|
||||
};
|
||||
|
||||
const DEFAULT_VISION_MAX_OUTPUT_TOKENS: u32 = 4096;
|
||||
|
||||
pub struct ImageAnalyzeTool {
|
||||
config: VisionModelConfig,
|
||||
client: reqwest::Client,
|
||||
@@ -67,6 +69,48 @@ impl ImageAnalyzeTool {
|
||||
fn api_key(&self) -> String {
|
||||
self.config.api_key.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn uses_max_completion_tokens(base_url: &str) -> bool {
|
||||
let Ok(url) = reqwest::Url::parse(base_url) else {
|
||||
return false;
|
||||
};
|
||||
let Some(domain) = url.domain() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
domain.eq_ignore_ascii_case("xiaomimimo.com")
|
||||
|| domain.to_ascii_lowercase().ends_with(".xiaomimimo.com")
|
||||
}
|
||||
|
||||
fn request_payload(&self, prompt: &str, image_data: &str, mime_type: &str) -> Value {
|
||||
let mut payload = json!({
|
||||
"model": self.config.model,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": format!("data:{};base64,{}", mime_type, image_data)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"temperature": 0.7
|
||||
});
|
||||
|
||||
let token_limit_field = if Self::uses_max_completion_tokens(&self.base_url()) {
|
||||
"max_completion_tokens"
|
||||
} else {
|
||||
"max_tokens"
|
||||
};
|
||||
payload[token_limit_field] = json!(DEFAULT_VISION_MAX_OUTPUT_TOKENS);
|
||||
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -122,25 +166,7 @@ impl ToolSpec for ImageAnalyzeTool {
|
||||
let resolved_path = context.workspace.join(image_path_buf);
|
||||
let (image_data, mime_type) = Self::read_image_file(&resolved_path).await?;
|
||||
|
||||
let payload = json!({
|
||||
"model": self.config.model,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": format!("data:{};base64,{}", mime_type, image_data)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"max_tokens": 4096,
|
||||
"temperature": 0.7
|
||||
});
|
||||
let payload = self.request_payload(prompt, &image_data, &mime_type);
|
||||
|
||||
let url = format!("{}/chat/completions", self.base_url());
|
||||
let api_key = self.api_key();
|
||||
@@ -262,6 +288,35 @@ mod tests {
|
||||
assert!(err.to_string().contains("Unsupported image format"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_vision_payload_uses_max_tokens() {
|
||||
let tool = ImageAnalyzeTool::new(fake_config());
|
||||
|
||||
let payload = tool.request_payload("describe", "abc123", "image/png");
|
||||
|
||||
assert_eq!(
|
||||
payload.get("max_tokens").and_then(Value::as_u64),
|
||||
Some(u64::from(DEFAULT_VISION_MAX_OUTPUT_TOKENS))
|
||||
);
|
||||
assert!(payload.get("max_completion_tokens").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xiaomi_mimo_vision_payload_uses_max_completion_tokens() {
|
||||
let mut config = fake_config();
|
||||
config.model = "mimo-v2.5".to_string();
|
||||
config.base_url = Some("https://api.xiaomimimo.com/v1".to_string());
|
||||
let tool = ImageAnalyzeTool::new(config);
|
||||
|
||||
let payload = tool.request_payload("describe", "abc123", "image/png");
|
||||
|
||||
assert_eq!(
|
||||
payload.get("max_completion_tokens").and_then(Value::as_u64),
|
||||
Some(u64::from(DEFAULT_VISION_MAX_OUTPUT_TOKENS))
|
||||
);
|
||||
assert!(payload.get("max_tokens").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_rejects_absolute_path() {
|
||||
// Trust-boundary pin: image_path must stay inside the workspace
|
||||
|
||||
+29
-6
@@ -63,8 +63,8 @@ provider's keyring entry.
|
||||
|
||||
For hosted, generic OpenAI-compatible, or self-hosted providers, set
|
||||
`provider = "nvidia-nim"`, `"openai"`, `"atlascloud"`, `"wanjie-ark"`,
|
||||
`"openrouter"`, `"novita"`, `"fireworks"`, `"moonshot"`, `"sglang"`,
|
||||
`"vllm"`, or `"ollama"` or pass `codewhale --provider <name>`.
|
||||
`"openrouter"`, `"xiaomi-mimo"`, `"novita"`, `"fireworks"`, `"moonshot"`,
|
||||
`"sglang"`, `"vllm"`, or `"ollama"` or pass `codewhale --provider <name>`.
|
||||
For the provider-by-provider registry, including auth variables, default base
|
||||
URLs, model IDs, and capability metadata, see [PROVIDERS.md](PROVIDERS.md).
|
||||
The facade saves provider credentials to the shared user config and forwards
|
||||
@@ -73,6 +73,7 @@ the resolved key, base URL, provider, and model to the TUI process. Use
|
||||
`codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"` or
|
||||
`codewhale auth set --provider atlascloud --api-key "YOUR_ATLASCLOUD_API_KEY"` or
|
||||
`codewhale auth set --provider wanjie-ark --api-key "YOUR_WANJIE_API_KEY"` or
|
||||
`codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_MIMO_API_KEY"` or
|
||||
`codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"`
|
||||
to save provider keys through the facade. The generic `openai` provider defaults
|
||||
to `https://api.openai.com/v1`, accepts `OPENAI_BASE_URL`, and defaults to
|
||||
@@ -129,6 +130,25 @@ environment override is `DEEPSEEK_HTTP_HEADERS`, using comma-separated
|
||||
and `Content-Type` are managed by the client and are not overridden by this
|
||||
setting.
|
||||
|
||||
### Vision Model
|
||||
|
||||
CodeWhale's chat provider and `image_analyze` tool are configured separately.
|
||||
The main chat path remains the selected text/tool provider; image analysis runs
|
||||
through `[vision_model]` when the `vision_model` feature is enabled.
|
||||
|
||||
Xiaomi's current image-understanding docs include `mimo-v2.5` for image input.
|
||||
To use MiMo for `image_analyze`, configure the vision model explicitly:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
vision_model = true
|
||||
|
||||
[vision_model]
|
||||
model = "mimo-v2.5"
|
||||
api_key = "YOUR_XIAOMI_MIMO_API_KEY"
|
||||
base_url = "https://api.xiaomimimo.com/v1"
|
||||
```
|
||||
|
||||
To bootstrap MCP and skills directories at their resolved paths, run `codewhale-tui setup`.
|
||||
To only scaffold MCP, run `codewhale-tui mcp init`.
|
||||
|
||||
@@ -207,7 +227,7 @@ aliases. When both forms are set the `CODEWHALE_*` value wins; the
|
||||
`DEEPSEEK_*` form is kept for older shells:
|
||||
|
||||
- `CODEWHALE_PROVIDER` (preferred) / `DEEPSEEK_PROVIDER` (legacy alias) —
|
||||
`deepseek|nvidia-nim|openai|atlascloud|wanjie-ark|openrouter|novita|fireworks|moonshot|sglang|vllm|ollama`
|
||||
`deepseek|nvidia-nim|openai|atlascloud|wanjie-ark|openrouter|xiaomi-mimo|novita|fireworks|moonshot|sglang|vllm|ollama`
|
||||
- `CODEWHALE_MODEL` (preferred) / `DEEPSEEK_MODEL` (legacy alias) — default model for the active provider
|
||||
- `CODEWHALE_BASE_URL` (preferred) / `DEEPSEEK_BASE_URL` (legacy alias) — base URL for the active provider
|
||||
|
||||
@@ -232,6 +252,9 @@ Remaining variables:
|
||||
- `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, or `WANJIE_MAAS_MODEL`
|
||||
- `OPENROUTER_API_KEY`
|
||||
- `OPENROUTER_BASE_URL`
|
||||
- `XIAOMI_MIMO_API_KEY` or `MIMO_API_KEY`
|
||||
- `XIAOMI_MIMO_BASE_URL` or `MIMO_BASE_URL`
|
||||
- `XIAOMI_MIMO_MODEL` or `MIMO_MODEL`
|
||||
- `NOVITA_API_KEY`
|
||||
- `NOVITA_BASE_URL`
|
||||
- `FIREWORKS_API_KEY`
|
||||
@@ -441,10 +464,10 @@ If you are upgrading from older releases:
|
||||
|
||||
### Core keys (used by the TUI/engine)
|
||||
|
||||
- `provider` (string, optional): `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, or `ollama`. Legacy `deepseek-cn` configs are still accepted as an alias for `deepseek`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/v1`; `openrouter` targets `https://openrouter.ai/api/v1`; `novita` targets `https://api.novita.ai/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `moonshot` targets Moonshot/Kimi, defaulting to `https://api.moonshot.ai/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`.
|
||||
- `provider` (string, optional): `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, or `ollama`. Legacy `deepseek-cn` configs are still accepted as an alias for `deepseek`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/v1`; `openrouter` targets `https://openrouter.ai/api/v1`; `xiaomi-mimo` targets Xiaomi MiMo's OpenAI-compatible endpoint at `https://api.xiaomimimo.com/v1`; `novita` targets `https://api.novita.ai/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `moonshot` targets Moonshot/Kimi, defaulting to `https://api.moonshot.ai/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`.
|
||||
- `api_key` (string, required for hosted providers): must be non-empty for DeepSeek/hosted providers (or set the provider API key env var). Self-hosted SGLang, vLLM, and Ollama can omit it.
|
||||
- `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API, including legacy `provider = "deepseek-cn"` configs. Other defaults are `https://integrate.api.nvidia.com/v1` for `nvidia-nim`, `https://api.openai.com/v1` for `openai`, `https://api.atlascloud.ai/v1` for `atlascloud`, `https://maas-openapi.wanjiedata.com/api/v1` for `wanjie-ark`, `https://openrouter.ai/api/v1` for `openrouter`, `https://api.novita.ai/v1` for `novita`, `https://api.fireworks.ai/inference/v1` for `fireworks`, `https://api.moonshot.ai/v1` for `moonshot`, `http://localhost:30000/v1` for `sglang`, `http://localhost:8000/v1` for `vllm`, and `http://localhost:11434/v1` for `ollama`. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features.
|
||||
- `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `deepseek/deepseek-v4-pro` for OpenRouter and Novita, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `kimi-k2.6` for Moonshot, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-coder:1.3b` for Ollama. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. Legacy `deepseek-chat` and `deepseek-reasoner` remain compatibility aliases for `deepseek-v4-flash` until July 24, 2026. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. Generic `openai`, `atlascloud`, `wanjie-ark`, and Ollama model IDs are passed through unchanged. OpenRouter provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `CODEWHALE_MODEL` overrides this for a single process; `DEEPSEEK_MODEL` is the legacy alias.
|
||||
- `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API, including legacy `provider = "deepseek-cn"` configs. Other defaults are `https://integrate.api.nvidia.com/v1` for `nvidia-nim`, `https://api.openai.com/v1` for `openai`, `https://api.atlascloud.ai/v1` for `atlascloud`, `https://maas-openapi.wanjiedata.com/api/v1` for `wanjie-ark`, `https://openrouter.ai/api/v1` for `openrouter`, `https://api.xiaomimimo.com/v1` for `xiaomi-mimo`, `https://api.novita.ai/v1` for `novita`, `https://api.fireworks.ai/inference/v1` for `fireworks`, `https://api.moonshot.ai/v1` for `moonshot`, `http://localhost:30000/v1` for `sglang`, `http://localhost:8000/v1` for `vllm`, and `http://localhost:11434/v1` for `ollama`. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features.
|
||||
- `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `deepseek/deepseek-v4-pro` for OpenRouter and Novita, `mimo-v2.5-pro` for Xiaomi MiMo, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `kimi-k2.6` for Moonshot, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-coder:1.3b` for Ollama. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. Legacy `deepseek-chat` and `deepseek-reasoner` remain compatibility aliases for `deepseek-v4-flash` until July 24, 2026. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. Generic `openai`, `atlascloud`, `wanjie-ark`, `xiaomi-mimo`, and Ollama model IDs are passed through unchanged. OpenRouter provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `CODEWHALE_MODEL` overrides this for a single process; `DEEPSEEK_MODEL` is the legacy alias.
|
||||
- `reasoning_effort` (string, optional): `off`, `low`, `medium`, `high`, or `max`; defaults to the configured UI tier. DeepSeek Platform receives top-level `thinking` / `reasoning_effort` fields. NVIDIA NIM receives equivalent settings through `chat_template_kwargs`.
|
||||
- `allow_shell` (bool, optional): defaults to `true` (sandboxed).
|
||||
- `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases.
|
||||
|
||||
+19
-6
@@ -6,11 +6,11 @@ limited to provider IDs, config keys, auth paths, base URLs, model resolution,
|
||||
and capability metadata that the code already knows about.
|
||||
|
||||
DeepSeek remains the first-class default provider. NVIDIA NIM, OpenRouter,
|
||||
Novita, Fireworks, generic OpenAI-compatible endpoints, self-hosted runtimes,
|
||||
and Moonshot/Kimi are additive routes for running the same terminal harness
|
||||
against other hosted or local model endpoints. Hugging Face Inference Providers
|
||||
are a planned additive open-model routing layer; they are not a native provider
|
||||
in this checkout yet.
|
||||
Xiaomi MiMo, Novita, Fireworks, generic OpenAI-compatible endpoints,
|
||||
self-hosted runtimes, and Moonshot/Kimi are additive routes for running the
|
||||
same terminal harness against other hosted or local model endpoints. Hugging
|
||||
Face Inference Providers are a planned additive open-model routing layer; they
|
||||
are not a native provider in this checkout yet.
|
||||
|
||||
Sources to keep in sync:
|
||||
|
||||
@@ -30,7 +30,8 @@ Sources to keep in sync:
|
||||
The canonical provider IDs are:
|
||||
|
||||
`deepseek`, `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`,
|
||||
`novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, and `ollama`.
|
||||
`xiaomi-mimo`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, and
|
||||
`ollama`.
|
||||
|
||||
Use any of these surfaces to select a provider:
|
||||
|
||||
@@ -116,6 +117,7 @@ endpoint.
|
||||
| `atlascloud` | `[providers.atlascloud]` | `ATLASCLOUD_API_KEY` | `ATLASCLOUD_BASE_URL`; default `https://api.atlascloud.ai/v1` | Default config model `deepseek-ai/deepseek-v4-flash` | OpenAI-compatible hosted route. `ATLASCLOUD_MODEL` is accepted by the TUI config path. The static `ModelRegistry` does not currently list AtlasCloud rows. |
|
||||
| `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. |
|
||||
| `openrouter` | `[providers.openrouter]` | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL`; default `https://openrouter.ai/api/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | 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`, `MIMO_API_KEY` | `XIAOMI_MIMO_BASE_URL`, `MIMO_BASE_URL`; default `https://api.xiaomimimo.com/v1` | `mimo-v2.5-pro`, `mimo-v2.5` | Xiaomi MiMo OpenAI-compatible chat completions route. It sends `max_completion_tokens` and uses MiMo's `thinking` field for reasoning control. |
|
||||
| `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. |
|
||||
| `moonshot` | `[providers.moonshot]` | `MOONSHOT_API_KEY`, `KIMI_API_KEY` | `MOONSHOT_BASE_URL`, `KIMI_BASE_URL`; default `https://api.moonshot.ai/v1` | `kimi-k2.6`; Kimi Code path uses `kimi-for-coding` at `https://api.kimi.com/coding/v1` | Moonshot/Kimi route. `MOONSHOT_MODEL`, `KIMI_MODEL_NAME`, and `KIMI_MODEL` are accepted. `[providers.moonshot] auth_mode = "kimi_oauth"` reads Kimi CLI OAuth credentials when present. |
|
||||
@@ -123,6 +125,15 @@ endpoint.
|
||||
| `vllm` | `[providers.vllm]` | Optional `VLLM_API_KEY` | `VLLM_BASE_URL`; default `http://localhost:8000/v1` | `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` | Self-hosted vLLM OpenAI-compatible route. Localhost deployments commonly omit auth. `VLLM_MODEL` is accepted. |
|
||||
| `ollama` | `[providers.ollama]` | Optional `OLLAMA_API_KEY` | `OLLAMA_BASE_URL`; default `http://localhost:11434/v1` | `deepseek-coder:1.3b`; provider-hinted custom tags pass through | Self-hosted Ollama OpenAI-compatible route. Localhost deployments commonly omit auth. `OLLAMA_MODEL` is accepted. |
|
||||
|
||||
### Xiaomi MiMo Notes
|
||||
|
||||
`xiaomi-mimo` defaults to `mimo-v2.5-pro` for long-context reasoning and coding
|
||||
work, while the static registry also exposes `mimo-v2.5`. Xiaomi's current
|
||||
[image-understanding guide](https://platform.xiaomimimo.com/docs/en-US/usage-guide/multimodal-understanding/image-understanding)
|
||||
includes `mimo-v2.5` for image input. CodeWhale exposes image analysis through the
|
||||
separate `[vision_model]` / `image_analyze` path; set that model to
|
||||
`mimo-v2.5` when using MiMo for vision.
|
||||
|
||||
## Static Model Registry
|
||||
|
||||
`codewhale model list` and `codewhale model resolve` use the static registry in
|
||||
@@ -137,6 +148,7 @@ endpoint when the endpoint supports model listing.
|
||||
| `openai` | `deepseek-v4-pro`, `deepseek-v4-flash` | yes | yes |
|
||||
| `wanjie-ark` | `deepseek-reasoner` | yes | yes |
|
||||
| `openrouter` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | yes | yes |
|
||||
| `xiaomi-mimo` | `mimo-v2.5-pro`, `mimo-v2.5` | yes | yes |
|
||||
| `novita` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | yes | yes |
|
||||
| `fireworks` | `accounts/fireworks/models/deepseek-v4-pro` | yes | yes |
|
||||
| `moonshot` | `kimi-k2.6` | yes | yes |
|
||||
@@ -164,6 +176,7 @@ All shipped providers use the Chat Completions request payload mode today.
|
||||
| DeepSeek compatibility aliases (`deepseek-chat`, `deepseek-reasoner`) | 1,000,000 | 384,000 | yes | yes | DeepSeek beta only |
|
||||
| NVIDIA NIM V4 registry models | 1,000,000 | 384,000 | yes | yes | not documented in code |
|
||||
| OpenRouter, Novita, Fireworks, SGLang, and vLLM V4 model IDs | 1,000,000 | 384,000 | yes | no | not documented in code |
|
||||
| Xiaomi MiMo models | 1,000,000 | 128,000 | yes | no | not documented in code |
|
||||
| Wanjie Ark `reasoner` / `r1` model IDs | 128,000 | 4,096 | yes | no | not documented in code |
|
||||
| Generic `openai`, AtlasCloud, and Moonshot/Kimi | 128,000 | 4,096 | no in doctor capability metadata | no | not documented in code |
|
||||
| Ollama | 8,192 | 4,096 | no | no | not documented in code |
|
||||
|
||||
Reference in New Issue
Block a user