From b291214cd223083408769acbe4c635ac3e97ce98 Mon Sep 17 00:00:00 2001 From: Hanmiao Li <894876246@qq.com> Date: Sat, 13 Jun 2026 01:53:11 +0800 Subject: [PATCH] fix(config): add separate siliconflow_cn provider config field with fallback (#2893) (#2895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split SiliconflowCN into its own [providers.siliconflow_cn] TOML section instead of silently ignoring [providers.siliconflow-CN] config. - ProvidersToml / ProvidersConfig: add siliconflow_cn field with serde alias - for_provider / for_provider_mut / provider_config_for: route SiliconflowCN to the new field - resolve_runtime_options_with_secrets: fallback siliconflow_cn → siliconflow for api_key / base_url / model when unset - deepseek_api_key: add config-file fallback for SiliconflowCn - provider_config_key: update metadata to "siliconflow_cn" - save_api_key_for: write SiliconflowCn keys to providers.siliconflow_cn - docs/PROVIDERS.md, config.example.toml, scripts/check-provider-registry.py --- config.example.toml | 7 +++++++ crates/config/src/lib.rs | 26 +++++++++++++++++++++----- crates/config/src/provider.rs | 2 +- crates/tui/src/config.rs | 27 ++++++++++++++++++--------- docs/PROVIDERS.md | 2 +- scripts/check-provider-registry.py | 2 +- 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/config.example.toml b/config.example.toml index 6a0ed0af..139674b9 100644 --- a/config.example.toml +++ b/config.example.toml @@ -378,6 +378,13 @@ max_subagents = 10 # optional (1-20) # base_url = "https://api.siliconflow.com/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash +# SiliconFlow China-hosted DeepSeek V4 (https://siliconflow.cn) +# Falls back to [providers.siliconflow] for api_key / base_url / model when unset. +[providers.siliconflow-CN] +# api_key = "YOUR_SILICONFLOW_API_KEY" +# base_url = "https://api.siliconflow.cn/v1" +# model = "deepseek-ai/DeepSeek-V4-Pro" + # Arcee AI direct OpenAI-compatible endpoint (https://docs.arcee.ai) [providers.arcee] # api_key = "YOUR_ARCEE_API_KEY" diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index be1824bd..2acc34e7 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -296,6 +296,8 @@ pub struct ProvidersToml { pub fireworks: ProviderConfigToml, #[serde(default)] pub siliconflow: ProviderConfigToml, + #[serde(default, alias = "siliconflow-CN", alias = "siliconflow-cn")] + pub siliconflow_cn: ProviderConfigToml, #[serde(default)] pub arcee: ProviderConfigToml, #[serde(default)] @@ -361,7 +363,8 @@ impl ProvidersToml { ProviderKind::XiaomiMimo => &self.xiaomi_mimo, ProviderKind::Novita => &self.novita, ProviderKind::Fireworks => &self.fireworks, - ProviderKind::Siliconflow | ProviderKind::SiliconflowCN => &self.siliconflow, + ProviderKind::Siliconflow => &self.siliconflow, + ProviderKind::SiliconflowCN => &self.siliconflow_cn, ProviderKind::Arcee => &self.arcee, ProviderKind::Moonshot => &self.moonshot, ProviderKind::Sglang => &self.sglang, @@ -386,7 +389,8 @@ impl ProvidersToml { ProviderKind::XiaomiMimo => &mut self.xiaomi_mimo, ProviderKind::Novita => &mut self.novita, ProviderKind::Fireworks => &mut self.fireworks, - ProviderKind::Siliconflow | ProviderKind::SiliconflowCN => &mut self.siliconflow, + ProviderKind::Siliconflow => &mut self.siliconflow, + ProviderKind::SiliconflowCN => &mut self.siliconflow_cn, ProviderKind::Arcee => &mut self.arcee, ProviderKind::Moonshot => &mut self.moonshot, ProviderKind::Sglang => &mut self.sglang, @@ -1956,7 +1960,19 @@ impl ConfigToml { let env = EnvRuntimeOverrides::load(); let provider = cli.provider.or(env.provider).unwrap_or(self.provider); - let provider_cfg = self.providers.for_provider(provider); + let mut provider_cfg = self.providers.for_provider(provider).clone(); + if provider == ProviderKind::SiliconflowCN { + let fb = &self.providers.siliconflow; + if provider_cfg.api_key.is_none() { + provider_cfg.api_key = fb.api_key.clone(); + } + if provider_cfg.base_url.is_none() { + provider_cfg.base_url = fb.base_url.clone(); + } + if provider_cfg.model.is_none() { + provider_cfg.model = fb.model.clone(); + } + } let root_deepseek_api_key = (provider == ProviderKind::Deepseek) .then(|| self.api_key.clone()) .flatten(); @@ -5135,11 +5151,11 @@ unix_socket_path = "/tmp/cw-hooks.sock" provider::resolve_provider("siliconflow-cn").expect("siliconflow-cn alias resolves"); assert_eq!(siliconflow_cn.kind(), ProviderKind::SiliconflowCN); assert_eq!(siliconflow_cn.id(), "siliconflow-CN"); - assert_eq!(siliconflow_cn.provider_config_key(), "siliconflow"); + assert_eq!(siliconflow_cn.provider_config_key(), "siliconflow_cn"); let config = ProvidersToml::default(); let shared_table = config.for_provider(ProviderKind::SiliconflowCN); - assert!(std::ptr::eq( + assert!(!std::ptr::eq( shared_table, config.for_provider(ProviderKind::Siliconflow) )); diff --git a/crates/config/src/provider.rs b/crates/config/src/provider.rs index c9e59414..e8312a27 100644 --- a/crates/config/src/provider.rs +++ b/crates/config/src/provider.rs @@ -223,7 +223,7 @@ provider!( DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL, ["SILICONFLOW_API_KEY"], - "siliconflow" + "siliconflow_cn" ); provider!( Arcee, diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 140c8f80..4a27908f 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -1979,6 +1979,8 @@ pub struct ProvidersConfig { pub fireworks: ProviderConfig, #[serde(default)] pub siliconflow: ProviderConfig, + #[serde(default, alias = "siliconflow-CN", alias = "siliconflow-cn")] + pub siliconflow_cn: ProviderConfig, #[serde(default)] pub arcee: ProviderConfig, #[serde(default)] @@ -2302,7 +2304,8 @@ impl Config { ApiProvider::XiaomiMimo => &providers.xiaomi_mimo, ApiProvider::Novita => &providers.novita, ApiProvider::Fireworks => &providers.fireworks, - ApiProvider::Siliconflow | ApiProvider::SiliconflowCn => &providers.siliconflow, + ApiProvider::Siliconflow => &providers.siliconflow, + ApiProvider::SiliconflowCn => &providers.siliconflow_cn, ApiProvider::Arcee => &providers.arcee, ApiProvider::Moonshot => &providers.moonshot, ApiProvider::Sglang => &providers.sglang, @@ -2329,7 +2332,8 @@ impl Config { ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo, ApiProvider::Novita => &mut providers.novita, ApiProvider::Fireworks => &mut providers.fireworks, - ApiProvider::Siliconflow | ApiProvider::SiliconflowCn => &mut providers.siliconflow, + ApiProvider::Siliconflow => &mut providers.siliconflow, + ApiProvider::SiliconflowCn => &mut providers.siliconflow_cn, ApiProvider::Arcee => &mut providers.arcee, ApiProvider::Moonshot => &mut providers.moonshot, ApiProvider::Sglang => &mut providers.sglang, @@ -3801,7 +3805,8 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo, ApiProvider::Novita => &mut providers.novita, ApiProvider::Fireworks => &mut providers.fireworks, - ApiProvider::Siliconflow | ApiProvider::SiliconflowCn => &mut providers.siliconflow, + ApiProvider::Siliconflow => &mut providers.siliconflow, + ApiProvider::SiliconflowCn => &mut providers.siliconflow_cn, ApiProvider::Arcee => &mut providers.arcee, ApiProvider::Moonshot => &mut providers.moonshot, ApiProvider::Sglang => &mut providers.sglang, @@ -3998,7 +4003,8 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::XiaomiMimo => &mut providers.xiaomi_mimo, ApiProvider::Novita => &mut providers.novita, ApiProvider::Fireworks => &mut providers.fireworks, - ApiProvider::Siliconflow | ApiProvider::SiliconflowCn => &mut providers.siliconflow, + ApiProvider::Siliconflow => &mut providers.siliconflow, + ApiProvider::SiliconflowCn => &mut providers.siliconflow_cn, ApiProvider::Arcee => &mut providers.arcee, ApiProvider::Moonshot => &mut providers.moonshot, ApiProvider::Sglang => &mut providers.sglang, @@ -4749,6 +4755,7 @@ fn merge_providers( novita: merge_provider_config(base.novita, override_cfg.novita), fireworks: merge_provider_config(base.fireworks, override_cfg.fireworks), siliconflow: merge_provider_config(base.siliconflow, override_cfg.siliconflow), + siliconflow_cn: merge_provider_config(base.siliconflow_cn, override_cfg.siliconflow_cn), arcee: merge_provider_config(base.arcee, override_cfg.arcee), moonshot: merge_provider_config(base.moonshot, override_cfg.moonshot), sglang: merge_provider_config(base.sglang, override_cfg.sglang), @@ -5457,7 +5464,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result ApiProvider::Novita => "novita", ApiProvider::Fireworks => "fireworks", ApiProvider::Siliconflow => "siliconflow", - ApiProvider::SiliconflowCn => "siliconflow", + ApiProvider::SiliconflowCn => "siliconflow_cn", ApiProvider::Arcee => "arcee", ApiProvider::Huggingface => "huggingface", ApiProvider::Moonshot => "moonshot", @@ -10473,16 +10480,18 @@ api_key = "moonshot-platform-key" assert_eq!( parsed .get("providers") - .and_then(|p| p.get("siliconflow")) + .and_then(|p| p.get("siliconflow_cn")) .and_then(|t| t.get("api_key")) .and_then(toml::Value::as_str), Some("sf-cn-saved-key") ); - assert!( + assert_eq!( parsed .get("providers") - .and_then(|p| p.get("siliconflow-CN")) - .is_none() + .and_then(|p| p.get("siliconflow")) + .and_then(|t| t.get("api_key")) + .and_then(toml::Value::as_str), + Some("sf-saved-key") ); Ok(()) } diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 15232624..cfaa389c 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -128,7 +128,7 @@ endpoint. | `novita` | `[providers.novita]` | `NOVITA_API_KEY` | `NOVITA_BASE_URL`; default `https://api.novita.ai/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | OpenAI-compatible hosted route for DeepSeek model IDs. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. | | `fireworks` | `[providers.fireworks]` | `FIREWORKS_API_KEY` | `FIREWORKS_BASE_URL`; default `https://api.fireworks.ai/inference/v1` | `accounts/fireworks/models/deepseek-v4-pro` | OpenAI-compatible hosted route. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. | | `siliconflow` | `[providers.siliconflow]` | `SILICONFLOW_API_KEY` | `SILICONFLOW_BASE_URL`; default `https://api.siliconflow.com/v1` | `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` | OpenAI-compatible hosted route. Official docs use the `.com` endpoint. `SILICONFLOW_MODEL` is accepted. Reasoning aliases `deepseek-reasoner` and `deepseek-r1` map to Pro; `deepseek-chat` and `deepseek-v3` map to Flash. | -| `siliconflow-CN` | `[providers.siliconflow]` | `SILICONFLOW_API_KEY` | `SILICONFLOW_BASE_URL`; default `https://api.siliconflow.cn/v1` | Uses the SiliconFlow model set | China regional SiliconFlow route. This intentionally shares `[providers.siliconflow]` and `SILICONFLOW_API_KEY`; do not create `[providers.siliconflow_CN]`. Select it with `provider = "siliconflow-CN"` or `CODEWHALE_PROVIDER=siliconflow-CN`. | +| `siliconflow-CN` | `[providers.siliconflow_cn]` | `SILICONFLOW_API_KEY` | `SILICONFLOW_BASE_URL`; default `https://api.siliconflow.cn/v1` | Uses the SiliconFlow model set | China regional SiliconFlow route. Falls back to `[providers.siliconflow]` for api_key / base_url / model when unset. Select it with `provider = "siliconflow-CN"` or `CODEWHALE_PROVIDER=siliconflow-CN`. | | `arcee` | `[providers.arcee]` | `ARCEE_API_KEY` | `ARCEE_BASE_URL`; default `https://api.arcee.ai/api/v1` | `trinity-large-thinking`, `trinity-large-preview` | Arcee AI direct OpenAI-compatible route, tracked as 256K-context BF16 serving. `ARCEE_MODEL` is accepted. OpenRouter's `arcee-ai/trinity-large-thinking` remains the OpenRouter namespaced model ID; direct Arcee uses the bare `trinity-large-thinking` ID. | | `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. | | `sglang` | `[providers.sglang]` | Optional `SGLANG_API_KEY` | `SGLANG_BASE_URL`; default `http://localhost:30000/v1` | `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` | Self-hosted OpenAI-compatible route. Localhost deployments commonly omit auth. `SGLANG_MODEL` is accepted. | diff --git a/scripts/check-provider-registry.py b/scripts/check-provider-registry.py index 85d7eea6..0caa5a71 100644 --- a/scripts/check-provider-registry.py +++ b/scripts/check-provider-registry.py @@ -28,7 +28,7 @@ PROVIDERS_MD = ROOT / "docs" / "PROVIDERS.md" API_PROVIDER_ONLY_IDS = {"deepseek-cn"} SHARED_PROVIDER_TABLES = { - "siliconflow-CN": "siliconflow", + "siliconflow-CN": "siliconflow_cn", }