From 3cfa87fafbacc6012116d9e4bbee9fa2327bfbfe Mon Sep 17 00:00:00 2001 From: QiyuanChen Date: Thu, 21 May 2026 14:39:13 +0800 Subject: [PATCH] feat(tui): add SiliconFlow provider Add SiliconFlow (https://siliconflow.com) as a new API provider with OpenAI-compatible /v1/chat/completions endpoint. - Base URL: https://api.siliconflow.com/v1 - Default model: deepseek-ai/DeepSeek-V4-Pro - Supports thinking (reasoning) and cache telemetry - Pass-through model names (no remapping) - Env vars: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL - Provider aliases: siliconflow, sf Co-Authored-By: Claude Opus 4.7 --- config.example.toml | 9 +++- crates/cli/src/lib.rs | 5 +- crates/config/src/lib.rs | 63 +++++++++++++++++++++++- crates/secrets/src/lib.rs | 1 + crates/tui/src/client.rs | 9 ++-- crates/tui/src/client/chat.rs | 1 + crates/tui/src/config.rs | 70 +++++++++++++++++++++++++-- crates/tui/src/core/engine.rs | 1 + crates/tui/src/main.rs | 5 ++ crates/tui/src/tui/provider_picker.rs | 4 +- crates/tui/src/tui/ui.rs | 2 + 11 files changed, 158 insertions(+), 12 deletions(-) diff --git a/config.example.toml b/config.example.toml index a8b25e06e..df212489b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -17,7 +17,7 @@ # 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 | sglang | vllm | ollama | siliconflow 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) @@ -168,6 +168,7 @@ max_subagents = 10 # optional (1-20) # 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 +# SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL # DeepSeek Platform (https://platform.deepseek.com) [providers.deepseek] @@ -227,6 +228,12 @@ max_subagents = 10 # optional (1-20) # base_url = "http://localhost:11434/v1" # model = "deepseek-coder:1.3b" # or any local Ollama tag +# SiliconFlow (https://siliconflow.com) +[providers.siliconflow] +# api_key = "YOUR_SILICONFLOW_API_KEY" +# base_url = "https://api.siliconflow.com/v1" +# model = "deepseek-ai/DeepSeek-V4-Pro" + # ───────────────────────────────────────────────────────────────────────────────── # Web Search Provider # ───────────────────────────────────────────────────────────────────────────────── diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 8b64dc5ab..32e7cf09b 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -694,11 +694,12 @@ fn provider_slot(provider: ProviderKind) -> &'static str { ProviderKind::Sglang => "sglang", ProviderKind::Vllm => "vllm", ProviderKind::Ollama => "ollama", + ProviderKind::SiliconFlow => "siliconflow", } } /// Provider order used by the `auth list` and `auth status` outputs. -const PROVIDER_LIST: [ProviderKind; 11] = [ +const PROVIDER_LIST: [ProviderKind; 12] = [ ProviderKind::Deepseek, ProviderKind::NvidiaNim, ProviderKind::Openai, @@ -710,6 +711,7 @@ const PROVIDER_LIST: [ProviderKind; 11] = [ ProviderKind::Sglang, ProviderKind::Vllm, ProviderKind::Ollama, + ProviderKind::SiliconFlow, ]; #[cfg(test)] @@ -764,6 +766,7 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] { ProviderKind::Sglang => &["SGLANG_API_KEY"], ProviderKind::Vllm => &["VLLM_API_KEY"], ProviderKind::Ollama => &["OLLAMA_API_KEY"], + ProviderKind::SiliconFlow => &["SILICONFLOW_API_KEY"], ProviderKind::Openai => &["OPENAI_API_KEY"], ProviderKind::Atlascloud => &["ATLASCLOUD_API_KEY"], ProviderKind::WanjieArk => &[ diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 81ca4221f..79866e2b9 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -41,6 +41,8 @@ const DEFAULT_VLLM_FLASH_MODEL: &str = "deepseek-ai/DeepSeek-V4-Flash"; const DEFAULT_VLLM_BASE_URL: &str = "http://localhost:8000/v1"; const DEFAULT_OLLAMA_MODEL: &str = "deepseek-coder:1.3b"; const DEFAULT_OLLAMA_BASE_URL: &str = "http://localhost:11434/v1"; +const DEFAULT_SILICONFLOW_MODEL: &str = "deepseek-ai/DeepSeek-V4-Pro"; +const DEFAULT_SILICONFLOW_BASE_URL: &str = "https://api.siliconflow.com/v1"; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "kebab-case")] @@ -71,6 +73,7 @@ pub enum ProviderKind { Sglang, Vllm, Ollama, + SiliconFlow, } impl ProviderKind { @@ -88,6 +91,7 @@ impl ProviderKind { Self::Sglang => "sglang", Self::Vllm => "vllm", Self::Ollama => "ollama", + Self::SiliconFlow => "siliconflow", } } @@ -107,6 +111,7 @@ impl ProviderKind { "sglang" | "sg-lang" => Some(Self::Sglang), "vllm" | "v-llm" => Some(Self::Vllm), "ollama" | "ollama-local" => Some(Self::Ollama), + "siliconflow" | "sf" => Some(Self::SiliconFlow), _ => None, } } @@ -145,6 +150,8 @@ pub struct ProvidersToml { pub vllm: ProviderConfigToml, #[serde(default)] pub ollama: ProviderConfigToml, + #[serde(default)] + pub siliconflow: ProviderConfigToml, } impl ProvidersToml { @@ -162,6 +169,7 @@ impl ProvidersToml { ProviderKind::Sglang => &self.sglang, ProviderKind::Vllm => &self.vllm, ProviderKind::Ollama => &self.ollama, + ProviderKind::SiliconFlow => &self.siliconflow, } } @@ -178,6 +186,7 @@ impl ProvidersToml { ProviderKind::Sglang => &mut self.sglang, ProviderKind::Vllm => &mut self.vllm, ProviderKind::Ollama => &mut self.ollama, + ProviderKind::SiliconFlow => &mut self.siliconflow, } } } @@ -400,6 +409,10 @@ impl ConfigToml { merge_provider_config(&mut self.providers.sglang, &project.providers.sglang); merge_provider_config(&mut self.providers.vllm, &project.providers.vllm); merge_provider_config(&mut self.providers.ollama, &project.providers.ollama); + merge_provider_config( + &mut self.providers.siliconflow, + &project.providers.siliconflow, + ); if project.network.is_some() { self.network = project.network; @@ -501,6 +514,12 @@ impl ConfigToml { "providers.ollama.http_headers" => { serialize_http_headers(&self.providers.ollama.http_headers) } + "providers.siliconflow.api_key" => self.providers.siliconflow.api_key.clone(), + "providers.siliconflow.base_url" => self.providers.siliconflow.base_url.clone(), + "providers.siliconflow.model" => self.providers.siliconflow.model.clone(), + "providers.siliconflow.http_headers" => { + serialize_http_headers(&self.providers.siliconflow.http_headers) + } _ => self.extras.get(key).map(toml::Value::to_string), } } @@ -671,6 +690,18 @@ impl ConfigToml { "providers.ollama.http_headers" => { self.providers.ollama.http_headers = parse_http_headers(value)?; } + "providers.siliconflow.api_key" => { + self.providers.siliconflow.api_key = Some(value.to_string()); + } + "providers.siliconflow.base_url" => { + self.providers.siliconflow.base_url = Some(value.to_string()); + } + "providers.siliconflow.model" => { + self.providers.siliconflow.model = Some(value.to_string()); + } + "providers.siliconflow.http_headers" => { + self.providers.siliconflow.http_headers = parse_http_headers(value)?; + } _ => { self.extras .insert(key.to_string(), toml::Value::String(value.to_string())); @@ -753,6 +784,10 @@ impl ConfigToml { "providers.ollama.base_url" => self.providers.ollama.base_url = None, "providers.ollama.model" => self.providers.ollama.model = None, "providers.ollama.http_headers" => self.providers.ollama.http_headers.clear(), + "providers.siliconflow.api_key" => self.providers.siliconflow.api_key = None, + "providers.siliconflow.base_url" => self.providers.siliconflow.base_url = None, + "providers.siliconflow.model" => self.providers.siliconflow.model = None, + "providers.siliconflow.http_headers" => self.providers.siliconflow.http_headers.clear(), _ => { self.extras.remove(key); } @@ -936,6 +971,21 @@ impl ConfigToml { if let Some(v) = serialize_http_headers(&self.providers.ollama.http_headers) { out.insert("providers.ollama.http_headers".to_string(), v); } + if let Some(v) = self.providers.siliconflow.api_key.as_ref() { + out.insert( + "providers.siliconflow.api_key".to_string(), + redact_secret(v), + ); + } + if let Some(v) = self.providers.siliconflow.base_url.as_ref() { + out.insert("providers.siliconflow.base_url".to_string(), v.clone()); + } + if let Some(v) = self.providers.siliconflow.model.as_ref() { + out.insert("providers.siliconflow.model".to_string(), v.clone()); + } + if let Some(v) = serialize_http_headers(&self.providers.siliconflow.http_headers) { + out.insert("providers.siliconflow.http_headers".to_string(), v); + } for (k, v) in &self.extras { out.insert(k.clone(), v.to_string()); @@ -997,6 +1047,7 @@ impl ConfigToml { ProviderKind::Sglang => DEFAULT_SGLANG_BASE_URL.to_string(), ProviderKind::Vllm => DEFAULT_VLLM_BASE_URL.to_string(), ProviderKind::Ollama => DEFAULT_OLLAMA_BASE_URL.to_string(), + ProviderKind::SiliconFlow => DEFAULT_SILICONFLOW_BASE_URL.to_string(), }); let auth_mode = cli .auth_mode @@ -1134,7 +1185,10 @@ pub fn load_project_config(workspace: &Path) -> Option { fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String { if matches!( provider, - ProviderKind::Atlascloud | ProviderKind::WanjieArk | ProviderKind::Ollama + ProviderKind::Atlascloud + | ProviderKind::WanjieArk + | ProviderKind::Ollama + | ProviderKind::SiliconFlow ) { return model.to_string(); } @@ -1201,6 +1255,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Sglang => DEFAULT_SGLANG_MODEL, ProviderKind::Vllm => DEFAULT_VLLM_MODEL, ProviderKind::Ollama => DEFAULT_OLLAMA_MODEL, + ProviderKind::SiliconFlow => DEFAULT_SILICONFLOW_MODEL, } } @@ -1217,6 +1272,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Sglang => DEFAULT_SGLANG_BASE_URL, ProviderKind::Vllm => DEFAULT_VLLM_BASE_URL, ProviderKind::Ollama => DEFAULT_OLLAMA_BASE_URL, + ProviderKind::SiliconFlow => DEFAULT_SILICONFLOW_BASE_URL, } } @@ -1577,6 +1633,7 @@ struct EnvRuntimeOverrides { sglang_base_url: Option, vllm_base_url: Option, ollama_base_url: Option, + siliconflow_base_url: Option, } impl EnvRuntimeOverrides { @@ -1643,6 +1700,9 @@ impl EnvRuntimeOverrides { ollama_base_url: std::env::var("OLLAMA_BASE_URL") .ok() .filter(|v| !v.trim().is_empty()), + siliconflow_base_url: std::env::var("SILICONFLOW_BASE_URL") + .ok() + .filter(|v| !v.trim().is_empty()), } } @@ -1661,6 +1721,7 @@ impl EnvRuntimeOverrides { ProviderKind::Sglang => self.sglang_base_url.clone(), ProviderKind::Vllm => self.vllm_base_url.clone(), ProviderKind::Ollama => self.ollama_base_url.clone(), + ProviderKind::SiliconFlow => self.siliconflow_base_url.clone(), } } diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs index f2616391b..e3849cc89 100644 --- a/crates/secrets/src/lib.rs +++ b/crates/secrets/src/lib.rs @@ -546,6 +546,7 @@ pub fn env_for(name: &str) -> Option { "WANJIE_API_KEY", "WANJIE_MAAS_API_KEY", ], + "siliconflow" | "sf" => &["SILICONFLOW_API_KEY"], _ => return None, }; for var in candidates { diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 20755beae..e26da818f 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -887,7 +887,8 @@ pub(super) fn apply_reasoning_effort( | ApiProvider::DeepseekCN | ApiProvider::Openrouter | ApiProvider::Novita - | ApiProvider::Sglang => { + | ApiProvider::Sglang + | ApiProvider::SiliconFlow => { body["thinking"] = json!({ "type": "disabled" }); } ApiProvider::Fireworks => {} @@ -920,7 +921,8 @@ pub(super) fn apply_reasoning_effort( | ApiProvider::DeepseekCN | ApiProvider::Openrouter | ApiProvider::Novita - | ApiProvider::Sglang => { + | ApiProvider::Sglang + | ApiProvider::SiliconFlow => { body["reasoning_effort"] = json!("high"); body["thinking"] = json!({ "type": "enabled" }); } @@ -949,7 +951,8 @@ pub(super) fn apply_reasoning_effort( | ApiProvider::DeepseekCN | ApiProvider::Openrouter | ApiProvider::Novita - | ApiProvider::Sglang => { + | ApiProvider::Sglang + | ApiProvider::SiliconFlow => { body["reasoning_effort"] = json!("max"); body["thinking"] = json!({ "type": "enabled" }); } diff --git a/crates/tui/src/client/chat.rs b/crates/tui/src/client/chat.rs index e3a10d6cd..12635d02a 100644 --- a/crates/tui/src/client/chat.rs +++ b/crates/tui/src/client/chat.rs @@ -1669,6 +1669,7 @@ fn provider_accepts_reasoning_content(provider: ApiProvider) -> bool { | ApiProvider::Novita | ApiProvider::Fireworks | ApiProvider::Sglang + | ApiProvider::SiliconFlow ) } diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 04bc18e8c..ce4e448d1 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -58,6 +58,8 @@ pub const DEFAULT_VLLM_FLASH_MODEL: &str = "deepseek-ai/DeepSeek-V4-Flash"; pub const DEFAULT_VLLM_BASE_URL: &str = "http://localhost:8000/v1"; pub const DEFAULT_OLLAMA_MODEL: &str = "deepseek-coder:1.3b"; pub const DEFAULT_OLLAMA_BASE_URL: &str = "http://localhost:11434/v1"; +pub const DEFAULT_SILICONFLOW_MODEL: &str = "deepseek-ai/DeepSeek-V4-Pro"; +pub const DEFAULT_SILICONFLOW_BASE_URL: &str = "https://api.siliconflow.com/v1"; /// Legacy `deepseek-cn` provider alias. /// /// DeepSeek's official API host is the same worldwide. Keep this alias for @@ -91,6 +93,7 @@ pub enum ApiProvider { Sglang, Vllm, Ollama, + SiliconFlow, } impl ApiProvider { @@ -112,6 +115,7 @@ impl ApiProvider { "sglang" | "sg-lang" => Some(Self::Sglang), "vllm" | "v-llm" => Some(Self::Vllm), "ollama" | "ollama-local" => Some(Self::Ollama), + "siliconflow" | "sf" => Some(Self::SiliconFlow), _ => None, } } @@ -131,6 +135,7 @@ impl ApiProvider { Self::Sglang => "sglang", Self::Vllm => "vllm", Self::Ollama => "ollama", + Self::SiliconFlow => "siliconflow", } } @@ -150,6 +155,7 @@ impl ApiProvider { Self::Sglang => "SGLang", Self::Vllm => "vLLM", Self::Ollama => "Ollama", + Self::SiliconFlow => "SiliconFlow", } } @@ -168,6 +174,7 @@ impl ApiProvider { Self::Sglang, Self::Vllm, Self::Ollama, + Self::SiliconFlow, ] } } @@ -297,7 +304,10 @@ pub fn provider_capability(provider: ApiProvider, resolved_model: &str) -> Provi // Cache telemetry: returned only by DeepSeek-native and NVIDIA NIM endpoints. let cache_telemetry_supported = matches!( provider, - ApiProvider::Deepseek | ApiProvider::DeepseekCN | ApiProvider::NvidiaNim + ApiProvider::Deepseek + | ApiProvider::DeepseekCN + | ApiProvider::NvidiaNim + | ApiProvider::SiliconFlow ); // Request payload mode: all current providers use chat completions. @@ -423,9 +433,10 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati ApiProvider::WanjieArk => vec![DEFAULT_WANJIE_ARK_MODEL], ApiProvider::Sglang => vec![DEFAULT_SGLANG_MODEL, DEFAULT_SGLANG_FLASH_MODEL], ApiProvider::Vllm => vec![DEFAULT_VLLM_MODEL, DEFAULT_VLLM_FLASH_MODEL], - ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::Ollama => { - OFFICIAL_DEEPSEEK_MODELS.to_vec() - } + ApiProvider::Openai + | ApiProvider::Atlascloud + | ApiProvider::Ollama + | ApiProvider::SiliconFlow => OFFICIAL_DEEPSEEK_MODELS.to_vec(), } } @@ -1238,6 +1249,8 @@ pub struct ProvidersConfig { pub vllm: ProviderConfig, #[serde(default)] pub ollama: ProviderConfig, + #[serde(default)] + pub siliconflow: ProviderConfig, } #[derive(Debug, Clone, Deserialize, Default)] @@ -1346,6 +1359,7 @@ impl Config { ApiProvider::Sglang => "providers.sglang", ApiProvider::Vllm => "providers.vllm", ApiProvider::Ollama => "providers.ollama", + ApiProvider::SiliconFlow => "providers.siliconflow", ApiProvider::NvidiaNim => "providers.nvidia_nim", ApiProvider::Deepseek | ApiProvider::DeepseekCN => return, }; @@ -1487,6 +1501,7 @@ impl Config { ApiProvider::Sglang => &providers.sglang, ApiProvider::Vllm => &providers.vllm, ApiProvider::Ollama => &providers.ollama, + ApiProvider::SiliconFlow => &providers.siliconflow, }) } @@ -1563,6 +1578,7 @@ impl Config { ApiProvider::Sglang => DEFAULT_SGLANG_MODEL, ApiProvider::Vllm => DEFAULT_VLLM_MODEL, ApiProvider::Ollama => DEFAULT_OLLAMA_MODEL, + ApiProvider::SiliconFlow => DEFAULT_SILICONFLOW_MODEL, } .to_string() } @@ -1593,7 +1609,8 @@ impl Config { | ApiProvider::Fireworks | ApiProvider::Sglang | ApiProvider::Vllm - | ApiProvider::Ollama => None, + | ApiProvider::Ollama + | ApiProvider::SiliconFlow => None, }; let base = provider_base.or(root_base).unwrap_or_else(|| { match provider { @@ -1609,6 +1626,7 @@ impl Config { ApiProvider::Sglang => DEFAULT_SGLANG_BASE_URL, ApiProvider::Vllm => DEFAULT_VLLM_BASE_URL, ApiProvider::Ollama => DEFAULT_OLLAMA_BASE_URL, + ApiProvider::SiliconFlow => DEFAULT_SILICONFLOW_BASE_URL, } .to_string() }); @@ -1642,6 +1660,7 @@ impl Config { ApiProvider::Sglang => "sglang", ApiProvider::Vllm => "vllm", ApiProvider::Ollama => "ollama", + ApiProvider::SiliconFlow => "siliconflow", }; // 0. DeepSeek compatibility slot. The legacy top-level `api_key` @@ -1724,6 +1743,10 @@ impl Config { // Self-hosted deployments commonly run without auth on localhost. // Return an empty key and let the client omit the Authorization header. ApiProvider::Sglang | ApiProvider::Vllm | ApiProvider::Ollama => Ok(String::new()), + ApiProvider::SiliconFlow => anyhow::bail!( + "SiliconFlow API key not found. Run 'deepseek auth set --provider siliconflow', \ + set SILICONFLOW_API_KEY, or add [providers.siliconflow] api_key in ~/.deepseek/config.toml." + ), } } @@ -2291,6 +2314,13 @@ fn apply_env_overrides(config: &mut Config) { .atlascloud .base_url = Some(value); } + ApiProvider::SiliconFlow => { + config + .providers + .get_or_insert_with(ProvidersConfig::default) + .siliconflow + .base_url = Some(value); + } } } if matches!(config.api_provider(), ApiProvider::NvidiaNim) @@ -2414,6 +2444,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Sglang => &mut providers.sglang, ApiProvider::Vllm => &mut providers.vllm, ApiProvider::Ollama => &mut providers.ollama, + ApiProvider::SiliconFlow => &mut providers.siliconflow, }; let mut provider_headers = entry.http_headers.clone().unwrap_or_default(); provider_headers.extend(headers); @@ -2429,6 +2460,16 @@ fn apply_env_overrides(config: &mut Config) { .ollama .base_url = Some(value); } + if matches!(config.api_provider(), ApiProvider::SiliconFlow) + && let Ok(value) = std::env::var("SILICONFLOW_BASE_URL") + && !value.trim().is_empty() + { + config + .providers + .get_or_insert_with(ProvidersConfig::default) + .siliconflow + .base_url = Some(value); + } if matches!(config.api_provider(), ApiProvider::Sglang) && let Ok(value) = std::env::var("SGLANG_MODEL") { @@ -2444,6 +2485,15 @@ fn apply_env_overrides(config: &mut Config) { { config.default_text_model = Some(value); } + if matches!(config.api_provider(), ApiProvider::SiliconFlow) + && let Ok(value) = std::env::var("SILICONFLOW_MODEL") + { + config + .providers + .get_or_insert_with(ProvidersConfig::default) + .siliconflow + .model = Some(value); + } if matches!(config.api_provider(), ApiProvider::Openai) && let Ok(value) = std::env::var("OPENAI_MODEL") { @@ -2501,6 +2551,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Sglang => &mut providers.sglang, ApiProvider::Vllm => &mut providers.vllm, ApiProvider::Ollama => &mut providers.ollama, + ApiProvider::SiliconFlow => &mut providers.siliconflow, }; entry.model = Some(value); } @@ -2754,6 +2805,7 @@ pub(crate) fn provider_passes_model_through(provider: ApiProvider) -> bool { | ApiProvider::Atlascloud | ApiProvider::WanjieArk | ApiProvider::Ollama + | ApiProvider::SiliconFlow ) } @@ -2778,6 +2830,7 @@ fn default_base_url_for_provider(provider: ApiProvider) -> &'static str { ApiProvider::Sglang => DEFAULT_SGLANG_BASE_URL, ApiProvider::Vllm => DEFAULT_VLLM_BASE_URL, ApiProvider::Ollama => DEFAULT_OLLAMA_BASE_URL, + ApiProvider::SiliconFlow => DEFAULT_SILICONFLOW_BASE_URL, } } @@ -3009,6 +3062,7 @@ fn merge_providers( sglang: merge_provider_config(base.sglang, override_cfg.sglang), vllm: merge_provider_config(base.vllm, override_cfg.vllm), ollama: merge_provider_config(base.ollama, override_cfg.ollama), + siliconflow: merge_provider_config(base.siliconflow, override_cfg.siliconflow), }), } } @@ -3423,6 +3477,9 @@ pub fn active_provider_has_env_api_key(config: &Config) -> bool { ApiProvider::Sglang => std::env::var("SGLANG_API_KEY").is_ok_and(|k| !k.trim().is_empty()), ApiProvider::Vllm => std::env::var("VLLM_API_KEY").is_ok_and(|k| !k.trim().is_empty()), ApiProvider::Ollama => std::env::var("OLLAMA_API_KEY").is_ok_and(|k| !k.trim().is_empty()), + ApiProvider::SiliconFlow => { + std::env::var("SILICONFLOW_API_KEY").is_ok_and(|k| !k.trim().is_empty()) + } } } @@ -3448,6 +3505,7 @@ pub fn has_api_key_for(config: &Config, provider: ApiProvider) -> bool { ApiProvider::Sglang => "SGLANG_API_KEY", ApiProvider::Vllm => "VLLM_API_KEY", ApiProvider::Ollama => "OLLAMA_API_KEY", + ApiProvider::SiliconFlow => "SILICONFLOW_API_KEY", }; if std::env::var(env_var).is_ok_and(|k| !k.trim().is_empty()) { return true; @@ -3528,6 +3586,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result ApiProvider::Sglang => "providers.sglang", ApiProvider::Vllm => "providers.vllm", ApiProvider::Ollama => "providers.ollama", + ApiProvider::SiliconFlow => "providers.siliconflow", }; // Parse existing TOML (or start fresh) so we can edit the right table @@ -3564,6 +3623,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result ApiProvider::Sglang => "sglang", ApiProvider::Vllm => "vllm", ApiProvider::Ollama => "ollama", + ApiProvider::SiliconFlow => "siliconflow", }; let entry = providers .entry(key_inside.to_string()) diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index 4332df6ee..5d45b4253 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -374,6 +374,7 @@ impl Engine { ApiProvider::Sglang => "SGLANG_API_KEY", ApiProvider::Vllm => "VLLM_API_KEY", ApiProvider::Ollama => "OLLAMA_API_KEY", + ApiProvider::SiliconFlow => "SILICONFLOW_API_KEY", }; Some(format!( diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 75bfbda9d..23a57bc50 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -1500,6 +1500,10 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> { crate::config::ApiProvider::Ollama => { ("OLLAMA_API_KEY", "deepseek auth set --provider ollama") } + crate::config::ApiProvider::SiliconFlow => ( + "SILICONFLOW_API_KEY", + "deepseek auth set --provider siliconflow --api-key \"...\"", + ), crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => { ("DEEPSEEK_API_KEY", "deepseek auth set --provider deepseek") } @@ -1518,6 +1522,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> { crate::config::ApiProvider::Sglang => "sglang", crate::config::ApiProvider::Vllm => "vllm", crate::config::ApiProvider::Ollama => "ollama", + crate::config::ApiProvider::SiliconFlow => "siliconflow", crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => "deepseek", } diff --git a/crates/tui/src/tui/provider_picker.rs b/crates/tui/src/tui/provider_picker.rs index ecf9f722e..09fc24c92 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -97,6 +97,7 @@ impl ProviderPickerView { ApiProvider::Sglang => "SGLANG_API_KEY", ApiProvider::Vllm => "VLLM_API_KEY", ApiProvider::Ollama => "OLLAMA_API_KEY", + ApiProvider::SiliconFlow => "SILICONFLOW_API_KEY", } } @@ -402,7 +403,8 @@ mod tests { "Fireworks AI", "SGLang", "vLLM", - "Ollama" + "Ollama", + "SiliconFlow" ] ); } diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 3cf028a40..3d9501869 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -5437,6 +5437,7 @@ fn render(f: &mut Frame, app: &mut App) { crate::config::ApiProvider::Sglang => Some("SGLang"), crate::config::ApiProvider::Vllm => Some("vLLM"), crate::config::ApiProvider::Ollama => Some("Ollama"), + crate::config::ApiProvider::SiliconFlow => Some("SiliconFlow"), }; let status_indicator_started_at = if app.low_motion { None @@ -6201,6 +6202,7 @@ async fn apply_provider_picker_api_key( ApiProvider::Sglang => &mut providers.sglang, ApiProvider::Vllm => &mut providers.vllm, ApiProvider::Ollama => &mut providers.ollama, + ApiProvider::SiliconFlow => &mut providers.siliconflow, }; entry.api_key = Some(api_key); }