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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ codewhale --provider novita --model deepseek/deepseek-v4-pro
codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"
codewhale --provider fireworks --model deepseek-v4-pro

# SiliconFlow
codewhale auth set --provider siliconflow --api-key "YOUR_SILICONFLOW_API_KEY"
codewhale --provider siliconflow --model deepseek-ai/DeepSeek-V4-Pro

# Generic OpenAI-compatible endpoint
codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"
OPENAI_BASE_URL="https://openai-compatible.example/v4" codewhale --provider openai --model glm-5
Expand Down Expand Up @@ -477,17 +481,18 @@ 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`, `novita`, `fireworks`, `siliconflow`, `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` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_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 |
| `NOVITA_BASE_URL` | Novita endpoint override |
| `FIREWORKS_BASE_URL` | Fireworks endpoint override |
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | SiliconFlow endpoint and model override |
| `SGLANG_BASE_URL` | Self-hosted SGLang endpoint |
| `SGLANG_MODEL` | Self-hosted SGLang model ID |
| `VLLM_BASE_URL` | Self-hosted vLLM endpoint |
Expand Down
9 changes: 7 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ codewhale --provider novita --model deepseek/deepseek-v4-pro
codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"
codewhale --provider fireworks --model deepseek-v4-pro

# SiliconFlow
codewhale auth set --provider siliconflow --api-key "YOUR_SILICONFLOW_API_KEY"
codewhale --provider siliconflow --model deepseek-ai/DeepSeek-V4-Pro

# 通用 OpenAI 兼容端点
codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"
OPENAI_BASE_URL="https://openai-compatible.example/v4" codewhale --provider openai --model glm-5
Expand Down Expand Up @@ -400,17 +404,18 @@ 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` | `deepseek`(默认)、`nvidia-nim`、`openai`、`atlascloud`、`wanjie-ark`、`openrouter`、`novita`、`fireworks`、`siliconflow`、`moonshot`、`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` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_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 端点覆盖 |
| `NOVITA_BASE_URL` | Novita 端点覆盖 |
| `FIREWORKS_BASE_URL` | Fireworks 端点覆盖 |
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | SiliconFlow 端点和模型覆盖 |
| `SGLANG_BASE_URL` | 自托管 SGLang 端点 |
| `SGLANG_MODEL` | 自托管 SGLang 模型 ID |
| `VLLM_BASE_URL` | 自托管 vLLM 端点 |
Expand Down
18 changes: 14 additions & 4 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
# `--provider fireworks`, `--provider siliconflow`, `/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 | novita | fireworks | siliconflow | 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)
Expand All @@ -38,6 +39,8 @@ base_url = "https://api.deepseek.com/beta"
# deepseek-ai/deepseek-v4-flash — default AtlasCloud model ID
# deepseek-reasoner — default Wanjie Ark model ID
# accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID
# deepseek-ai/DeepSeek-V4-Pro — SiliconFlow Pro model ID
# deepseek-ai/DeepSeek-V4-Flash — SiliconFlow Flash model ID
# deepseek-ai/DeepSeek-V4-Pro — SGLang self-hosted Pro model ID
# deepseek-ai/DeepSeek-V4-Flash — SGLang self-hosted Flash model ID
default_text_model = "deepseek-v4-pro"
Expand Down Expand Up @@ -178,8 +181,8 @@ max_subagents = 10 # optional (1-20)
# ─────────────────────────────────────────────────────────────────────────────────
# Providers can be stored at once; `provider = "..."` (top of file) or
# `/provider deepseek` / `/provider nvidia-nim` / `--provider openai` /
# `--provider wanjie-ark` / `/provider fireworks` switches between them without
# having to re-enter keys. Env vars override anything set here:
# `--provider wanjie-ark` / `/provider fireworks` / `--provider siliconflow`
# switches between them without having to re-enter keys. Env vars override anything set here:
# DeepSeek: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL
# NIM: NVIDIA_API_KEY (or NVIDIA_NIM_API_KEY), NIM_BASE_URL
# (or NVIDIA_NIM_BASE_URL / NVIDIA_BASE_URL), NVIDIA_NIM_MODEL
Expand All @@ -188,6 +191,7 @@ max_subagents = 10 # optional (1-20)
# OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
# Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
# Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
# SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL
# SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY
# vLLM: VLLM_BASE_URL, VLLM_MODEL, optional VLLM_API_KEY
# Ollama: OLLAMA_BASE_URL, OLLAMA_MODEL, optional OLLAMA_API_KEY
Expand Down Expand Up @@ -244,6 +248,12 @@ max_subagents = 10 # optional (1-20)
# base_url = "https://api.fireworks.ai/inference/v1"
# model = "accounts/fireworks/models/deepseek-v4-pro"

# SiliconFlow-hosted DeepSeek V4 (https://siliconflow.com)
[providers.siliconflow]
# api_key = "YOUR_SILICONFLOW_API_KEY"
# base_url = "https://api.siliconflow.com/v1"
# model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash

# Self-hosted SGLang OpenAI-compatible server
[providers.sglang]
# api_key = "OPTIONAL_SGLANG_TOKEN"
Expand Down
52 changes: 52 additions & 0 deletions crates/agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,30 @@ impl Default for ModelRegistry {
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "deepseek-ai/DeepSeek-V4-Pro".to_string(),
provider: ProviderKind::Siliconflow,
aliases: vec![
"deepseek-v4-pro".to_string(),
"deepseek-reasoner".to_string(),
"deepseek-r1".to_string(),
"siliconflow-deepseek-v4-pro".to_string(),
],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "deepseek-ai/DeepSeek-V4-Flash".to_string(),
provider: ProviderKind::Siliconflow,
aliases: vec![
"deepseek-v4-flash".to_string(),
"deepseek-chat".to_string(),
"deepseek-v3".to_string(),
"siliconflow-deepseek-v4-flash".to_string(),
],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "kimi-k2.6".to_string(),
provider: ProviderKind::Moonshot,
Expand Down Expand Up @@ -413,6 +437,34 @@ mod tests {
);
}

#[test]
fn siliconflow_default_uses_canonical_pro_model_id() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(None, Some(ProviderKind::Siliconflow));

assert_eq!(resolved.resolved.provider, ProviderKind::Siliconflow);
assert_eq!(resolved.resolved.id, "deepseek-ai/DeepSeek-V4-Pro");
assert!(resolved.resolved.supports_reasoning);
}

#[test]
fn deepseek_reasoner_alias_resolves_to_siliconflow_pro_when_provider_hinted() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("deepseek-reasoner"), Some(ProviderKind::Siliconflow));

assert_eq!(resolved.resolved.provider, ProviderKind::Siliconflow);
assert_eq!(resolved.resolved.id, "deepseek-ai/DeepSeek-V4-Pro");
}

#[test]
fn deepseek_v4_flash_alias_resolves_to_siliconflow_flash_when_provider_hinted() {
let registry = ModelRegistry::default();
let resolved = registry.resolve(Some("deepseek-v4-flash"), Some(ProviderKind::Siliconflow));

assert_eq!(resolved.resolved.provider, ProviderKind::Siliconflow);
assert_eq!(resolved.resolved.id, "deepseek-ai/DeepSeek-V4-Flash");
}

#[test]
fn sglang_default_uses_canonical_model_id() {
let registry = ModelRegistry::default();
Expand Down
12 changes: 6 additions & 6 deletions crates/cli/src/bin/codew_legacy_shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ fn spawn_codewhale(args: &[String]) -> std::io::Result<std::process::ExitStatus>
// same directory as this shim but not on PATH (#2006).
#[cfg(windows)]
{
if let Ok(exe_path) = env::current_exe() {
if let Some(dir) = exe_path.parent() {
let sibling = dir.join("codewhale.exe");
if sibling.is_file() {
return Command::new(sibling).args(args).status();
}
if let Ok(exe_path) = env::current_exe()
&& let Some(dir) = exe_path.parent()
{
let sibling = dir.join("codewhale.exe");
if sibling.is_file() {
return Command::new(sibling).args(args).status();
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/cli/src/bin/deepseek_legacy_shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ fn spawn_codewhale(args: &[String]) -> std::io::Result<std::process::ExitStatus>
// same directory as this shim but not on PATH (#2006).
#[cfg(windows)]
{
if let Ok(exe_path) = env::current_exe() {
if let Some(dir) = exe_path.parent() {
let sibling = dir.join("codewhale.exe");
if sibling.is_file() {
return Command::new(sibling).args(args).status();
}
if let Ok(exe_path) = env::current_exe()
&& let Some(dir) = exe_path.parent()
{
let sibling = dir.join("codewhale.exe");
if sibling.is_file() {
return Command::new(sibling).args(args).status();
}
}
}
Expand Down
30 changes: 28 additions & 2 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum ProviderArg {
Openrouter,
Novita,
Fireworks,
Siliconflow,
Moonshot,
Sglang,
Vllm,
Expand All @@ -48,6 +49,7 @@ impl From<ProviderArg> for ProviderKind {
ProviderArg::Openrouter => ProviderKind::Openrouter,
ProviderArg::Novita => ProviderKind::Novita,
ProviderArg::Fireworks => ProviderKind::Fireworks,
ProviderArg::Siliconflow => ProviderKind::Siliconflow,
ProviderArg::Moonshot => ProviderKind::Moonshot,
ProviderArg::Sglang => ProviderKind::Sglang,
ProviderArg::Vllm => ProviderKind::Vllm,
Expand Down Expand Up @@ -720,6 +722,7 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
ProviderKind::Openrouter => "openrouter",
ProviderKind::Novita => "novita",
ProviderKind::Fireworks => "fireworks",
ProviderKind::Siliconflow => "siliconflow",
ProviderKind::Moonshot => "moonshot",
ProviderKind::Sglang => "sglang",
ProviderKind::Vllm => "vllm",
Expand All @@ -728,7 +731,7 @@ 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,
Expand All @@ -737,6 +740,7 @@ const PROVIDER_LIST: [ProviderKind; 12] = [
ProviderKind::Openrouter,
ProviderKind::Novita,
ProviderKind::Fireworks,
ProviderKind::Siliconflow,
ProviderKind::Moonshot,
ProviderKind::Sglang,
ProviderKind::Vllm,
Expand Down Expand Up @@ -792,6 +796,7 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] {
ProviderKind::Novita => &["NOVITA_API_KEY"],
ProviderKind::NvidiaNim => &["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY", "DEEPSEEK_API_KEY"],
ProviderKind::Fireworks => &["FIREWORKS_API_KEY"],
ProviderKind::Siliconflow => &["SILICONFLOW_API_KEY"],
ProviderKind::Moonshot => &["MOONSHOT_API_KEY", "KIMI_API_KEY"],
ProviderKind::Sglang => &["SGLANG_API_KEY"],
ProviderKind::Vllm => &["VLLM_API_KEY"],
Expand Down Expand Up @@ -1475,13 +1480,14 @@ fn build_tui_command(
| ProviderKind::Openrouter
| ProviderKind::Novita
| ProviderKind::Fireworks
| ProviderKind::Siliconflow
| ProviderKind::Moonshot
| ProviderKind::Sglang
| ProviderKind::Vllm
| 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, Novita, Fireworks, SiliconFlow, Moonshot/Kimi, SGLang, vLLM, and Ollama providers. Remove --provider {} or use `codewhale model ...` for provider registry inspection.",
resolved_runtime.provider.as_str()
);
}
Expand Down Expand Up @@ -1542,6 +1548,9 @@ fn build_tui_command(
if resolved_runtime.provider == ProviderKind::WanjieArk {
cmd.env("WANJIE_ARK_API_KEY", api_key);
}
if resolved_runtime.provider == ProviderKind::Siliconflow {
cmd.env("SILICONFLOW_API_KEY", api_key);
}
cmd.env("DEEPSEEK_API_KEY_SOURCE", "cli");
}
if let Some(base_url) = cli.base_url.as_ref() {
Expand Down Expand Up @@ -2150,6 +2159,18 @@ mod tests {
}))
));

let cli = parse_ok(&["deepseek", "auth", "set", "--provider", "siliconflow"]);
assert!(matches!(
cli.command,
Some(Commands::Auth(AuthArgs {
command: AuthCommand::Set {
provider: ProviderArg::Siliconflow,
api_key: None,
api_key_stdin: false,
}
}))
));

let cli = parse_ok(&["deepseek", "auth", "set", "--provider", "moonshot"]);
assert!(matches!(
cli.command,
Expand Down Expand Up @@ -2886,6 +2907,11 @@ mod tests {
&["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY"],
),
(ProviderKind::Fireworks, "fireworks", &["FIREWORKS_API_KEY"]),
(
ProviderKind::Siliconflow,
"siliconflow",
&["SILICONFLOW_API_KEY"],
),
(ProviderKind::Sglang, "sglang", &["SGLANG_API_KEY"]),
(ProviderKind::Vllm, "vllm", &["VLLM_API_KEY"]),
(ProviderKind::Ollama, "ollama", &["OLLAMA_API_KEY"]),
Expand Down
Loading