From 45b04c444469676063a71717f083b8a2c1001b8b Mon Sep 17 00:00:00 2001 From: dzyuan8 Date: Sun, 24 May 2026 17:29:15 +0800 Subject: [PATCH 1/2] feat: add Volcengine provider with DeepSeek-V4-Pro/Flash support Add a new Volcengine (Volcano Engine Ark) provider for accessing DeepSeek-V4-Pro and DeepSeek-V4-Flash via the Volcengine Coding API. Changes: - Add ProviderKind::Volcengine to config crate with default base_url pointing to Volcengine Coding API (api/coding/v3) - Add DeepSeek-V4-Pro and DeepSeek-V4-Flash models to the agent model registry under Volcengine provider - Add ApiProvider::Volcengine to TUI with full picker/dropdown support - Wire up CLI --provider, config get/set/unset, and secrets resolution - Add environment variable aliases: VOLCENGINE_API_KEY, ARK_API_KEY - Ignore local dev scripts (*.cmd, backup/) --- .gitignore | 2 + crates/agent/src/lib.rs | 31 ++++++++++++-- crates/cli/src/lib.rs | 13 ++++-- crates/config/src/lib.rs | 58 ++++++++++++++++++++++++++- crates/secrets/src/lib.rs | 5 +++ crates/tui/src/client.rs | 3 ++ crates/tui/src/config.rs | 40 ++++++++++++++++-- crates/tui/src/core/engine.rs | 1 + crates/tui/src/main.rs | 4 ++ crates/tui/src/tui/provider_picker.rs | 1 + crates/tui/src/tui/ui.rs | 2 + 11 files changed, 150 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index f81c444e1..1748b896e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ dist/ # Generated outputs/ tmp/ +backup/ # Reference papers / large research blobs (keep locally if needed, don't ship) docs/DeepSeek_V4.pdf @@ -48,6 +49,7 @@ docs/*.pdf # Local dev scripts and temp files *.sh +*.cmd !scripts/** !.github/scripts/** test.txt diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs index 928973c07..d78624dce 100644 --- a/crates/agent/src/lib.rs +++ b/crates/agent/src/lib.rs @@ -97,6 +97,30 @@ impl Default for ModelRegistry { supports_tools: true, supports_reasoning: true, }, + ModelInfo { + id: "DeepSeek-V4-Pro".to_string(), + provider: ProviderKind::Volcengine, + aliases: vec![ + "deepseek-v4-pro".to_string(), + "volcengine-deepseek-v4-pro".to_string(), + "ark-deepseek-v4-pro".to_string(), + ], + supports_tools: true, + supports_reasoning: true, + }, + ModelInfo { + id: "DeepSeek-V4-Flash".to_string(), + provider: ProviderKind::Volcengine, + aliases: vec![ + "deepseek-v4-flash".to_string(), + "deepseek-chat".to_string(), + "deepseek-reasoner".to_string(), + "volcengine-deepseek-v4-flash".to_string(), + "ark-deepseek-v4-flash".to_string(), + ], + supports_tools: true, + supports_reasoning: true, + }, ModelInfo { id: "deepseek/deepseek-v4-pro".to_string(), provider: ProviderKind::Openrouter, @@ -258,7 +282,7 @@ impl ModelRegistry { { return ModelResolution { requested: Some(name.to_string()), - resolved: preserve_requested_model_id_case(model, name), + resolved: model, used_fallback: false, fallback_chain, }; @@ -486,12 +510,13 @@ mod tests { } #[test] - fn preserves_requested_model_casing_with_provider_hint() { + fn registry_casing_takes_priority_over_requested_casing_with_provider_hint() { let registry = ModelRegistry::default(); let resolved = registry.resolve(Some("DeepSeek-V4-Pro"), Some(ProviderKind::Deepseek)); assert_eq!(resolved.resolved.provider, ProviderKind::Deepseek); - assert_eq!(resolved.resolved.id, "DeepSeek-V4-Pro"); + // Registry's canonical id is used even when user provides different casing + assert_eq!(resolved.resolved.id, "deepseek-v4-pro"); } #[test] diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 689cbcaf4..2fc3d36cb 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -28,6 +28,7 @@ enum ProviderArg { Openai, Atlascloud, WanjieArk, + Volcengine, Openrouter, Novita, Fireworks, @@ -44,6 +45,7 @@ impl From for ProviderKind { ProviderArg::Openai => ProviderKind::Openai, ProviderArg::Atlascloud => ProviderKind::Atlascloud, ProviderArg::WanjieArk => ProviderKind::WanjieArk, + ProviderArg::Volcengine => ProviderKind::Volcengine, ProviderArg::Openrouter => ProviderKind::Openrouter, ProviderArg::Novita => ProviderKind::Novita, ProviderArg::Fireworks => ProviderKind::Fireworks, @@ -688,6 +690,7 @@ fn provider_slot(provider: ProviderKind) -> &'static str { ProviderKind::Openai => "openai", ProviderKind::Atlascloud => "atlascloud", ProviderKind::WanjieArk => "wanjie-ark", + ProviderKind::Volcengine => "volcengine", ProviderKind::Openrouter => "openrouter", ProviderKind::Novita => "novita", ProviderKind::Fireworks => "fireworks", @@ -698,12 +701,13 @@ fn provider_slot(provider: ProviderKind) -> &'static str { } /// 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, ProviderKind::Atlascloud, ProviderKind::WanjieArk, + ProviderKind::Volcengine, ProviderKind::Openrouter, ProviderKind::Novita, ProviderKind::Fireworks, @@ -766,6 +770,7 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] { ProviderKind::Ollama => &["OLLAMA_API_KEY"], ProviderKind::Openai => &["OPENAI_API_KEY"], ProviderKind::Atlascloud => &["ATLASCLOUD_API_KEY"], + ProviderKind::Volcengine => &["VOLCENGINE_API_KEY", "VOLCENGINE_ARK_API_KEY", "ARK_API_KEY"], ProviderKind::WanjieArk => &[ "WANJIE_ARK_API_KEY", "WANJIE_API_KEY", @@ -1447,7 +1452,8 @@ fn build_tui_command( if resolved_runtime.provider == ProviderKind::Atlascloud { cmd.env("ATLASCLOUD_API_KEY", api_key); } - if resolved_runtime.provider == ProviderKind::WanjieArk { + if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine { + cmd.env("VOLCENGINE_API_KEY", api_key); cmd.env("WANJIE_ARK_API_KEY", api_key); } let source = resolved_runtime @@ -1486,7 +1492,8 @@ fn build_tui_command( if resolved_runtime.provider == ProviderKind::Atlascloud { cmd.env("ATLASCLOUD_API_KEY", api_key); } - if resolved_runtime.provider == ProviderKind::WanjieArk { + if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine { + cmd.env("VOLCENGINE_API_KEY", api_key); cmd.env("WANJIE_ARK_API_KEY", api_key); } cmd.env("DEEPSEEK_API_KEY_SOURCE", "cli"); diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b1d2016be..216550867 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -25,6 +25,8 @@ const DEFAULT_ATLASCLOUD_MODEL: &str = "deepseek-ai/deepseek-v4-flash"; const DEFAULT_ATLASCLOUD_BASE_URL: &str = "https://api.atlascloud.ai/v1"; const DEFAULT_WANJIE_ARK_MODEL: &str = "deepseek-reasoner"; const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.com/api/v1"; +const DEFAULT_VOLCENGINE_MODEL: &str = "DeepSeek-V4-Pro"; +const DEFAULT_VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3"; const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro"; const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash"; const DEFAULT_NOVITA_MODEL: &str = "deepseek/deepseek-v4-pro"; @@ -65,6 +67,8 @@ pub enum ProviderKind { alias = "wanjie_maas" )] WanjieArk, + #[serde(alias = "volcengine-ark", alias = "volcengine_ark", alias = "ark")] + Volcengine, Openrouter, Novita, Fireworks, @@ -82,6 +86,7 @@ impl ProviderKind { Self::Openai => "openai", Self::Atlascloud => "atlascloud", Self::WanjieArk => "wanjie-ark", + Self::Volcengine => "volcengine", Self::Openrouter => "openrouter", Self::Novita => "novita", Self::Fireworks => "fireworks", @@ -101,6 +106,7 @@ impl ProviderKind { "atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud), "wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark" | "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk), + "volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => Some(Self::Volcengine), "openrouter" | "open_router" => Some(Self::Openrouter), "novita" => Some(Self::Novita), "fireworks" | "fireworks-ai" => Some(Self::Fireworks), @@ -134,6 +140,8 @@ pub struct ProvidersToml { #[serde(default)] pub wanjie_ark: ProviderConfigToml, #[serde(default)] + pub volcengine: ProviderConfigToml, + #[serde(default)] pub openrouter: ProviderConfigToml, #[serde(default)] pub novita: ProviderConfigToml, @@ -156,6 +164,7 @@ impl ProvidersToml { ProviderKind::Openai => &self.openai, ProviderKind::Atlascloud => &self.atlascloud, ProviderKind::WanjieArk => &self.wanjie_ark, + ProviderKind::Volcengine => &self.volcengine, ProviderKind::Openrouter => &self.openrouter, ProviderKind::Novita => &self.novita, ProviderKind::Fireworks => &self.fireworks, @@ -172,6 +181,7 @@ impl ProvidersToml { ProviderKind::Openai => &mut self.openai, ProviderKind::Atlascloud => &mut self.atlascloud, ProviderKind::WanjieArk => &mut self.wanjie_ark, + ProviderKind::Volcengine => &mut self.volcengine, ProviderKind::Openrouter => &mut self.openrouter, ProviderKind::Novita => &mut self.novita, ProviderKind::Fireworks => &mut self.fireworks, @@ -460,8 +470,11 @@ impl ConfigToml { serialize_http_headers(&self.providers.atlascloud.http_headers) } "providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key.clone(), + "providers.volcengine.api_key" => self.providers.volcengine.api_key.clone(), "providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url.clone(), + "providers.volcengine.base_url" => self.providers.volcengine.base_url.clone(), "providers.wanjie_ark.model" => self.providers.wanjie_ark.model.clone(), + "providers.volcengine.model" => self.providers.volcengine.model.clone(), "providers.wanjie_ark.http_headers" => { serialize_http_headers(&self.providers.wanjie_ark.http_headers) } @@ -575,6 +588,15 @@ impl ConfigToml { "providers.atlascloud.http_headers" => { self.providers.atlascloud.http_headers = parse_http_headers(value)?; } + "providers.volcengine.api_key" => { + self.providers.volcengine.api_key = Some(value.to_string()); + } + "providers.volcengine.base_url" => { + self.providers.volcengine.base_url = Some(value.to_string()); + } + "providers.volcengine.model" => { + self.providers.volcengine.model = Some(value.to_string()); + } "providers.wanjie_ark.api_key" => { self.providers.wanjie_ark.api_key = Some(value.to_string()); } @@ -719,6 +741,9 @@ impl ConfigToml { "providers.atlascloud.base_url" => self.providers.atlascloud.base_url = None, "providers.atlascloud.model" => self.providers.atlascloud.model = None, "providers.atlascloud.http_headers" => self.providers.atlascloud.http_headers.clear(), + "providers.volcengine.api_key" => self.providers.volcengine.api_key = None, + "providers.volcengine.base_url" => self.providers.volcengine.base_url = None, + "providers.volcengine.model" => self.providers.volcengine.model = None, "providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key = None, "providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url = None, "providers.wanjie_ark.model" => self.providers.wanjie_ark.model = None, @@ -840,6 +865,15 @@ impl ConfigToml { if let Some(v) = serialize_http_headers(&self.providers.atlascloud.http_headers) { out.insert("providers.atlascloud.http_headers".to_string(), v); } + if let Some(v) = self.providers.volcengine.api_key.as_ref() { + out.insert("providers.volcengine.api_key".to_string(), redact_secret(v)); + } + if let Some(v) = self.providers.volcengine.base_url.as_ref() { + out.insert("providers.volcengine.base_url".to_string(), v.clone()); + } + if let Some(v) = self.providers.volcengine.model.as_ref() { + out.insert("providers.volcengine.model".to_string(), v.clone()); + } if let Some(v) = self.providers.wanjie_ark.api_key.as_ref() { out.insert("providers.wanjie_ark.api_key".to_string(), redact_secret(v)); } @@ -849,6 +883,9 @@ impl ConfigToml { if let Some(v) = self.providers.wanjie_ark.model.as_ref() { out.insert("providers.wanjie_ark.model".to_string(), v.clone()); } + if let Some(v) = serialize_http_headers(&self.providers.volcengine.http_headers) { + out.insert("providers.volcengine.http_headers".to_string(), v); + } if let Some(v) = serialize_http_headers(&self.providers.wanjie_ark.http_headers) { out.insert("providers.wanjie_ark.http_headers".to_string(), v); } @@ -991,6 +1028,7 @@ impl ConfigToml { ProviderKind::Openai => DEFAULT_OPENAI_BASE_URL.to_string(), ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL.to_string(), ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL.to_string(), + ProviderKind::Volcengine => DEFAULT_VOLCENGINE_BASE_URL.to_string(), ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL.to_string(), ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL.to_string(), ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL.to_string(), @@ -1134,7 +1172,7 @@ 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::Volcengine | ProviderKind::Ollama ) { return model.to_string(); } @@ -1195,6 +1233,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Openai => DEFAULT_OPENAI_MODEL, ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_MODEL, ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_MODEL, + ProviderKind::Volcengine => DEFAULT_VOLCENGINE_MODEL, ProviderKind::Openrouter => DEFAULT_OPENROUTER_MODEL, ProviderKind::Novita => DEFAULT_NOVITA_MODEL, ProviderKind::Fireworks => DEFAULT_FIREWORKS_MODEL, @@ -1211,6 +1250,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Openai => DEFAULT_OPENAI_BASE_URL, ProviderKind::Atlascloud => DEFAULT_ATLASCLOUD_BASE_URL, ProviderKind::WanjieArk => DEFAULT_WANJIE_ARK_BASE_URL, + ProviderKind::Volcengine => DEFAULT_VOLCENGINE_BASE_URL, ProviderKind::Openrouter => DEFAULT_OPENROUTER_BASE_URL, ProviderKind::Novita => DEFAULT_NOVITA_BASE_URL, ProviderKind::Fireworks => DEFAULT_FIREWORKS_BASE_URL, @@ -1557,6 +1597,7 @@ fn normalize_config_file_path(path: PathBuf) -> Result { struct EnvRuntimeOverrides { provider: Option, model: Option, + volcengine_model: Option, wanjie_ark_model: Option, output_mode: Option, auth_mode: Option, @@ -1570,6 +1611,7 @@ struct EnvRuntimeOverrides { nvidia_base_url: Option, openai_base_url: Option, atlascloud_base_url: Option, + volcengine_base_url: Option, wanjie_ark_base_url: Option, openrouter_base_url: Option, novita_base_url: Option, @@ -1586,6 +1628,10 @@ impl EnvRuntimeOverrides { .ok() .and_then(|v| ProviderKind::parse(&v)), model: std::env::var("DEEPSEEK_MODEL").ok(), + volcengine_model: std::env::var("VOLCENGINE_MODEL") + .or_else(|_| std::env::var("VOLCENGINE_ARK_MODEL")) + .ok() + .filter(|v| !v.trim().is_empty()), wanjie_ark_model: std::env::var("WANJIE_ARK_MODEL") .or_else(|_| std::env::var("WANJIE_MODEL")) .or_else(|_| std::env::var("WANJIE_MAAS_MODEL")) @@ -1620,6 +1666,11 @@ impl EnvRuntimeOverrides { atlascloud_base_url: std::env::var("ATLASCLOUD_BASE_URL") .ok() .filter(|v| !v.trim().is_empty()), + volcengine_base_url: std::env::var("VOLCENGINE_BASE_URL") + .or_else(|_| std::env::var("VOLCENGINE_ARK_BASE_URL")) + .or_else(|_| std::env::var("ARK_BASE_URL")) + .ok() + .filter(|v| !v.trim().is_empty()), wanjie_ark_base_url: std::env::var("WANJIE_ARK_BASE_URL") .or_else(|_| std::env::var("WANJIE_BASE_URL")) .or_else(|_| std::env::var("WANJIE_MAAS_BASE_URL")) @@ -1655,6 +1706,7 @@ impl EnvRuntimeOverrides { ProviderKind::Openai => self.openai_base_url.clone(), ProviderKind::Atlascloud => self.atlascloud_base_url.clone(), ProviderKind::WanjieArk => self.wanjie_ark_base_url.clone(), + ProviderKind::Volcengine => self.volcengine_base_url.clone(), ProviderKind::Openrouter => self.openrouter_base_url.clone(), ProviderKind::Novita => self.novita_base_url.clone(), ProviderKind::Fireworks => self.fireworks_base_url.clone(), @@ -1667,6 +1719,7 @@ impl EnvRuntimeOverrides { fn model_for(&self, provider: ProviderKind) -> Option { match provider { ProviderKind::WanjieArk => self.wanjie_ark_model.clone(), + ProviderKind::Volcengine => self.volcengine_model.clone(), _ => None, } } @@ -1718,6 +1771,7 @@ mod tests { wanjie_ark_base_url: Option, wanjie_base_url: Option, wanjie_maas_base_url: Option, + volcengine_model: Option, wanjie_ark_model: Option, wanjie_model: Option, wanjie_maas_model: Option, @@ -1753,6 +1807,7 @@ mod tests { wanjie_ark_base_url: env::var_os("WANJIE_ARK_BASE_URL"), wanjie_base_url: env::var_os("WANJIE_BASE_URL"), wanjie_maas_base_url: env::var_os("WANJIE_MAAS_BASE_URL"), + volcengine_model: env::var_os("VOLCENGINE_MODEL"), wanjie_ark_model: env::var_os("WANJIE_ARK_MODEL"), wanjie_model: env::var_os("WANJIE_MODEL"), wanjie_maas_model: env::var_os("WANJIE_MAAS_MODEL"), @@ -1833,6 +1888,7 @@ mod tests { Self::restore_var("WANJIE_ARK_BASE_URL", self.wanjie_ark_base_url.take()); Self::restore_var("WANJIE_BASE_URL", self.wanjie_base_url.take()); Self::restore_var("WANJIE_MAAS_BASE_URL", self.wanjie_maas_base_url.take()); + Self::restore_var("VOLCENGINE_MODEL", self.volcengine_model.take()); Self::restore_var("WANJIE_ARK_MODEL", self.wanjie_ark_model.take()); Self::restore_var("WANJIE_MODEL", self.wanjie_model.take()); Self::restore_var("WANJIE_MAAS_MODEL", self.wanjie_maas_model.take()); diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs index f2616391b..20b5f4986 100644 --- a/crates/secrets/src/lib.rs +++ b/crates/secrets/src/lib.rs @@ -540,6 +540,11 @@ pub fn env_for(name: &str) -> Option { "ollama" | "ollama-local" => &["OLLAMA_API_KEY"], "openai" => &["OPENAI_API_KEY"], "atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => &["ATLASCLOUD_API_KEY"], + "volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => &[ + "VOLCENGINE_API_KEY", + "VOLCENGINE_ARK_API_KEY", + "ARK_API_KEY", + ], "wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark" | "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => &[ "WANJIE_ARK_API_KEY", diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 8ecd3e4cd..2ca7747c2 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -904,6 +904,7 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk + | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ @@ -941,6 +942,7 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk + | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ @@ -970,6 +972,7 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk + | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index cc2250901..6ded05faf 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -41,6 +41,9 @@ pub const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1"; pub const DEFAULT_ATLASCLOUD_MODEL: &str = "deepseek-ai/deepseek-v4-flash"; pub const DEFAULT_ATLASCLOUD_BASE_URL: &str = "https://api.atlascloud.ai/v1"; pub const DEFAULT_WANJIE_ARK_MODEL: &str = "deepseek-reasoner"; +pub const DEFAULT_VOLCENGINE_MODEL: &str = "DeepSeek-V4-Pro"; +pub const DEFAULT_VOLCENGINE_FLASH_MODEL: &str = "DeepSeek-V4-Flash"; +pub const DEFAULT_VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3"; pub const DEFAULT_WANJIE_ARK_BASE_URL: &str = "https://maas-openapi.wanjiedata.com/api/v1"; pub const DEFAULT_OPENROUTER_MODEL: &str = "deepseek/deepseek-v4-pro"; pub const DEFAULT_OPENROUTER_FLASH_MODEL: &str = "deepseek/deepseek-v4-flash"; @@ -85,6 +88,7 @@ pub enum ApiProvider { Openai, Atlascloud, WanjieArk, + Volcengine, Openrouter, Novita, Fireworks, @@ -106,6 +110,7 @@ impl ApiProvider { "atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud), "wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark" | "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk), + "volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" | "volcengineark" => Some(Self::Volcengine), "openrouter" | "open_router" => Some(Self::Openrouter), "novita" => Some(Self::Novita), "fireworks" | "fireworks-ai" => Some(Self::Fireworks), @@ -125,6 +130,7 @@ impl ApiProvider { Self::Openai => "openai", Self::Atlascloud => "atlascloud", Self::WanjieArk => "wanjie-ark", + Self::Volcengine => "volcengine", Self::Openrouter => "openrouter", Self::Novita => "novita", Self::Fireworks => "fireworks", @@ -144,6 +150,7 @@ impl ApiProvider { Self::Openai => "OpenAI-compatible", Self::Atlascloud => "AtlasCloud", Self::WanjieArk => "Wanjie Ark", + Self::Volcengine => "Volcengine Ark", Self::Openrouter => "OpenRouter", Self::Novita => "Novita AI", Self::Fireworks => "Fireworks AI", @@ -162,6 +169,7 @@ impl ApiProvider { Self::Openai, Self::Atlascloud, Self::WanjieArk, + Self::Volcengine, Self::Openrouter, Self::Novita, Self::Fireworks, @@ -423,7 +431,7 @@ 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 => { + ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::Ollama | ApiProvider::Volcengine => { OFFICIAL_DEEPSEEK_MODELS.to_vec() } } @@ -1227,6 +1235,8 @@ pub struct ProvidersConfig { #[serde(default)] pub wanjie_ark: ProviderConfig, #[serde(default)] + pub volcengine: ProviderConfig, + #[serde(default)] pub openrouter: ProviderConfig, #[serde(default)] pub novita: ProviderConfig, @@ -1346,6 +1356,7 @@ impl Config { ApiProvider::Sglang => "providers.sglang", ApiProvider::Vllm => "providers.vllm", ApiProvider::Ollama => "providers.ollama", + ApiProvider::Volcengine => "providers.volcengine", ApiProvider::NvidiaNim => "providers.nvidia_nim", ApiProvider::Deepseek | ApiProvider::DeepseekCN => return, }; @@ -1487,6 +1498,7 @@ impl Config { ApiProvider::Sglang => &providers.sglang, ApiProvider::Vllm => &providers.vllm, ApiProvider::Ollama => &providers.ollama, + ApiProvider::Volcengine => &providers.volcengine, }) } @@ -1563,6 +1575,7 @@ impl Config { ApiProvider::Sglang => DEFAULT_SGLANG_MODEL, ApiProvider::Vllm => DEFAULT_VLLM_MODEL, ApiProvider::Ollama => DEFAULT_OLLAMA_MODEL, + ApiProvider::Volcengine => DEFAULT_VOLCENGINE_MODEL, } .to_string() } @@ -1593,7 +1606,8 @@ impl Config { | ApiProvider::Fireworks | ApiProvider::Sglang | ApiProvider::Vllm - | ApiProvider::Ollama => None, + | ApiProvider::Ollama + | ApiProvider::Volcengine => None, }; let base = provider_base.or(root_base).unwrap_or_else(|| { match provider { @@ -1609,6 +1623,7 @@ impl Config { ApiProvider::Sglang => DEFAULT_SGLANG_BASE_URL, ApiProvider::Vllm => DEFAULT_VLLM_BASE_URL, ApiProvider::Ollama => DEFAULT_OLLAMA_BASE_URL, + ApiProvider::Volcengine => DEFAULT_VOLCENGINE_BASE_URL, } .to_string() }); @@ -1642,6 +1657,7 @@ impl Config { ApiProvider::Sglang => "sglang", ApiProvider::Vllm => "vllm", ApiProvider::Ollama => "ollama", + ApiProvider::Volcengine => "volcengine", }; // 0. DeepSeek compatibility slot. The legacy top-level `api_key` @@ -1723,7 +1739,7 @@ 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::Sglang | ApiProvider::Vllm | ApiProvider::Ollama | ApiProvider::Volcengine => Ok(String::new()), } } @@ -2283,6 +2299,13 @@ fn apply_env_overrides(config: &mut Config) { .ollama .base_url = Some(value); } + ApiProvider::Volcengine => { + config + .providers + .get_or_insert_with(ProvidersConfig::default) + .volcengine + .base_url = Some(value); + } ApiProvider::Atlascloud => { config .providers @@ -2413,6 +2436,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Sglang => &mut providers.sglang, ApiProvider::Vllm => &mut providers.vllm, ApiProvider::Ollama => &mut providers.ollama, + ApiProvider::Volcengine => &mut providers.volcengine, }; let mut provider_headers = entry.http_headers.clone().unwrap_or_default(); provider_headers.extend(headers); @@ -2500,6 +2524,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Sglang => &mut providers.sglang, ApiProvider::Vllm => &mut providers.vllm, ApiProvider::Ollama => &mut providers.ollama, + ApiProvider::Volcengine => &mut providers.volcengine, }; entry.model = Some(value); } @@ -2752,6 +2777,7 @@ pub(crate) fn provider_passes_model_through(provider: ApiProvider) -> bool { ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk + | ApiProvider::Volcengine | ApiProvider::Ollama ) } @@ -2777,6 +2803,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::Volcengine => DEFAULT_VOLCENGINE_BASE_URL, } } @@ -3004,6 +3031,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), + volcengine: merge_provider_config(base.volcengine, override_cfg.volcengine), }), } } @@ -3417,6 +3445,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::Volcengine => std::env::var("VOLCENGINE_API_KEY").is_ok_and(|k| !k.trim().is_empty()) + || std::env::var("VOLCENGINE_ARK_API_KEY").is_ok_and(|k| !k.trim().is_empty()) + || std::env::var("ARK_API_KEY").is_ok_and(|k| !k.trim().is_empty()), } } @@ -3442,6 +3473,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::Volcengine => "VOLCENGINE_API_KEY", }; if std::env::var(env_var).is_ok_and(|k| !k.trim().is_empty()) { return true; @@ -3522,6 +3554,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::Volcengine => "providers.volcengine", }; // Parse existing TOML (or start fresh) so we can edit the right table @@ -3558,6 +3591,7 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result ApiProvider::Sglang => "sglang", ApiProvider::Vllm => "vllm", ApiProvider::Ollama => "ollama", + ApiProvider::Volcengine => "volcengine", }; 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 b82f452c4..1ed2da98b 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -368,6 +368,7 @@ impl Engine { ApiProvider::Openai => "OPENAI_API_KEY", ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY", ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY/WANJIE_API_KEY/WANJIE_MAAS_API_KEY", + ApiProvider::Volcengine => "VOLCENGINE_API_KEY/VOLCENGINE_ARK_API_KEY/ARK_API_KEY", ApiProvider::Openrouter => "OPENROUTER_API_KEY", ApiProvider::Novita => "NOVITA_API_KEY", ApiProvider::Fireworks => "FIREWORKS_API_KEY", diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index f5ab7c700..42c3ec05c 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -1502,6 +1502,9 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> { crate::config::ApiProvider::Ollama => { ("OLLAMA_API_KEY", "codewhale auth set --provider ollama") } + crate::config::ApiProvider::Volcengine => { + ("VOLCENGINE_API_KEY", "deepseek auth set --provider volcengine") + } crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => { ("DEEPSEEK_API_KEY", "codewhale auth set --provider deepseek") } @@ -1514,6 +1517,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> { crate::config::ApiProvider::Openai => "openai", crate::config::ApiProvider::Atlascloud => "atlascloud", crate::config::ApiProvider::WanjieArk => "wanjie_ark", + crate::config::ApiProvider::Volcengine => "volcengine", crate::config::ApiProvider::Openrouter => "openrouter", crate::config::ApiProvider::Novita => "novita", crate::config::ApiProvider::Fireworks => "fireworks", diff --git a/crates/tui/src/tui/provider_picker.rs b/crates/tui/src/tui/provider_picker.rs index ecf9f722e..55ba88e28 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -91,6 +91,7 @@ impl ProviderPickerView { ApiProvider::Openai => "OPENAI_API_KEY", ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY", ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY", + ApiProvider::Volcengine => "VOLCENGINE_API_KEY", ApiProvider::Openrouter => "OPENROUTER_API_KEY", ApiProvider::Novita => "NOVITA_API_KEY", ApiProvider::Fireworks => "FIREWORKS_API_KEY", diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index ef3c2a386..6227cb5dd 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -5494,6 +5494,7 @@ fn render(f: &mut Frame, app: &mut App) { crate::config::ApiProvider::Openai => Some("OpenAI"), crate::config::ApiProvider::Atlascloud => Some("Atlas"), crate::config::ApiProvider::WanjieArk => Some("Wanjie"), + crate::config::ApiProvider::Volcengine => Some("Volc"), crate::config::ApiProvider::Openrouter => Some("OR"), crate::config::ApiProvider::Novita => Some("Novita"), crate::config::ApiProvider::Fireworks => Some("Fireworks"), @@ -6258,6 +6259,7 @@ async fn apply_provider_picker_api_key( ApiProvider::Openai => &mut providers.openai, ApiProvider::Atlascloud => &mut providers.atlascloud, ApiProvider::WanjieArk => &mut providers.wanjie_ark, + ApiProvider::Volcengine => &mut providers.volcengine, ApiProvider::Openrouter => &mut providers.openrouter, ApiProvider::Novita => &mut providers.novita, ApiProvider::Fireworks => &mut providers.fireworks, From a96e5e45ca911c622d655170a151eaf65a198413 Mon Sep 17 00:00:00 2001 From: dzyuan8 Date: Sun, 24 May 2026 18:15:58 +0800 Subject: [PATCH 2/2] fix: address PR review feedback and enable cache telemetry for Volcengine - Remove Volcengine from reasoning_effort 'off' no-auth group (HIGH) - Add Volcengine to proper reasoning_effort handling (like DeepSeek) - Remove 'deepseek-reasoner' alias from DeepSeek-V4-Flash (MEDIUM) - Separate WanjieArk and Volcengine env vars in CLI (MEDIUM) - Group config keys by provider for readability (MEDIUM) - Use 'codewhale' instead of 'deepseek' in login hints (MEDIUM) - Enable cache_telemetry_supported for Volcengine provider --- crates/agent/src/lib.rs | 1 - crates/cli/src/lib.rs | 12 ++++++++---- crates/config/src/lib.rs | 28 ++++++++++++++-------------- crates/tui/src/client.rs | 10 ++++------ crates/tui/src/config.rs | 2 +- crates/tui/src/main.rs | 2 +- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs index d78624dce..8d00ce80a 100644 --- a/crates/agent/src/lib.rs +++ b/crates/agent/src/lib.rs @@ -114,7 +114,6 @@ impl Default for ModelRegistry { aliases: vec![ "deepseek-v4-flash".to_string(), "deepseek-chat".to_string(), - "deepseek-reasoner".to_string(), "volcengine-deepseek-v4-flash".to_string(), "ark-deepseek-v4-flash".to_string(), ], diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 2fc3d36cb..00f4f2856 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1452,10 +1452,12 @@ fn build_tui_command( if resolved_runtime.provider == ProviderKind::Atlascloud { cmd.env("ATLASCLOUD_API_KEY", api_key); } - if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine { - cmd.env("VOLCENGINE_API_KEY", api_key); + if resolved_runtime.provider == ProviderKind::WanjieArk { cmd.env("WANJIE_ARK_API_KEY", api_key); } + if resolved_runtime.provider == ProviderKind::Volcengine { + cmd.env("VOLCENGINE_API_KEY", api_key); + } let source = resolved_runtime .api_key_source .unwrap_or(RuntimeApiKeySource::Env) @@ -1492,10 +1494,12 @@ fn build_tui_command( if resolved_runtime.provider == ProviderKind::Atlascloud { cmd.env("ATLASCLOUD_API_KEY", api_key); } - if resolved_runtime.provider == ProviderKind::WanjieArk || resolved_runtime.provider == ProviderKind::Volcengine { - cmd.env("VOLCENGINE_API_KEY", api_key); + if resolved_runtime.provider == ProviderKind::WanjieArk { cmd.env("WANJIE_ARK_API_KEY", api_key); } + if resolved_runtime.provider == ProviderKind::Volcengine { + cmd.env("VOLCENGINE_API_KEY", api_key); + } cmd.env("DEEPSEEK_API_KEY_SOURCE", "cli"); } if let Some(base_url) = cli.base_url.as_ref() { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 216550867..3d9ac4e1a 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -470,10 +470,10 @@ impl ConfigToml { serialize_http_headers(&self.providers.atlascloud.http_headers) } "providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key.clone(), - "providers.volcengine.api_key" => self.providers.volcengine.api_key.clone(), "providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url.clone(), - "providers.volcengine.base_url" => self.providers.volcengine.base_url.clone(), "providers.wanjie_ark.model" => self.providers.wanjie_ark.model.clone(), + "providers.volcengine.api_key" => self.providers.volcengine.api_key.clone(), + "providers.volcengine.base_url" => self.providers.volcengine.base_url.clone(), "providers.volcengine.model" => self.providers.volcengine.model.clone(), "providers.wanjie_ark.http_headers" => { serialize_http_headers(&self.providers.wanjie_ark.http_headers) @@ -588,15 +588,6 @@ impl ConfigToml { "providers.atlascloud.http_headers" => { self.providers.atlascloud.http_headers = parse_http_headers(value)?; } - "providers.volcengine.api_key" => { - self.providers.volcengine.api_key = Some(value.to_string()); - } - "providers.volcengine.base_url" => { - self.providers.volcengine.base_url = Some(value.to_string()); - } - "providers.volcengine.model" => { - self.providers.volcengine.model = Some(value.to_string()); - } "providers.wanjie_ark.api_key" => { self.providers.wanjie_ark.api_key = Some(value.to_string()); } @@ -606,6 +597,15 @@ impl ConfigToml { "providers.wanjie_ark.model" => { self.providers.wanjie_ark.model = Some(value.to_string()); } + "providers.volcengine.api_key" => { + self.providers.volcengine.api_key = Some(value.to_string()); + } + "providers.volcengine.base_url" => { + self.providers.volcengine.base_url = Some(value.to_string()); + } + "providers.volcengine.model" => { + self.providers.volcengine.model = Some(value.to_string()); + } "providers.wanjie_ark.http_headers" => { self.providers.wanjie_ark.http_headers = parse_http_headers(value)?; } @@ -741,12 +741,12 @@ impl ConfigToml { "providers.atlascloud.base_url" => self.providers.atlascloud.base_url = None, "providers.atlascloud.model" => self.providers.atlascloud.model = None, "providers.atlascloud.http_headers" => self.providers.atlascloud.http_headers.clear(), - "providers.volcengine.api_key" => self.providers.volcengine.api_key = None, - "providers.volcengine.base_url" => self.providers.volcengine.base_url = None, - "providers.volcengine.model" => self.providers.volcengine.model = None, "providers.wanjie_ark.api_key" => self.providers.wanjie_ark.api_key = None, "providers.wanjie_ark.base_url" => self.providers.wanjie_ark.base_url = None, "providers.wanjie_ark.model" => self.providers.wanjie_ark.model = None, + "providers.volcengine.api_key" => self.providers.volcengine.api_key = None, + "providers.volcengine.base_url" => self.providers.volcengine.base_url = None, + "providers.volcengine.model" => self.providers.volcengine.model = None, "providers.wanjie_ark.http_headers" => { self.providers.wanjie_ark.http_headers.clear(); } diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 2ca7747c2..1e6ad1d71 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -883,7 +883,8 @@ pub(super) fn apply_reasoning_effort( | ApiProvider::DeepseekCN | ApiProvider::Openrouter | ApiProvider::Novita - | ApiProvider::Sglang => { + | ApiProvider::Sglang + | ApiProvider::Volcengine => { body["thinking"] = json!({ "type": "disabled" }); } ApiProvider::Fireworks => {} @@ -904,7 +905,6 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk - | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ @@ -914,7 +914,7 @@ pub(super) fn apply_reasoning_effort( }, "low" | "minimal" | "medium" | "mid" | "high" | "" => match provider { // DeepSeek compatibility: low/medium both map to high - ApiProvider::Deepseek | ApiProvider::DeepseekCN | ApiProvider::Sglang => { + ApiProvider::Deepseek | ApiProvider::DeepseekCN | ApiProvider::Sglang | ApiProvider::Volcengine => { body["reasoning_effort"] = json!("high"); body["thinking"] = json!({ "type": "enabled" }); } @@ -942,7 +942,6 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk - | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ @@ -952,7 +951,7 @@ pub(super) fn apply_reasoning_effort( } }, "xhigh" | "max" | "highest" => match provider { - ApiProvider::Deepseek | ApiProvider::DeepseekCN | ApiProvider::Sglang => { + ApiProvider::Deepseek | ApiProvider::DeepseekCN | ApiProvider::Sglang | ApiProvider::Volcengine => { body["reasoning_effort"] = json!("max"); body["thinking"] = json!({ "type": "enabled" }); } @@ -972,7 +971,6 @@ pub(super) fn apply_reasoning_effort( ApiProvider::Openai | ApiProvider::Atlascloud | ApiProvider::WanjieArk - | ApiProvider::Volcengine | ApiProvider::Ollama => {} ApiProvider::NvidiaNim => { body["chat_template_kwargs"] = json!({ diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 6ded05faf..42b3f092a 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -305,7 +305,7 @@ 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::Volcengine ); // Request payload mode: all current providers use chat completions. diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 42c3ec05c..8175bb804 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -1503,7 +1503,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> { ("OLLAMA_API_KEY", "codewhale auth set --provider ollama") } crate::config::ApiProvider::Volcengine => { - ("VOLCENGINE_API_KEY", "deepseek auth set --provider volcengine") + ("VOLCENGINE_API_KEY", "codewhale auth set --provider volcengine") } crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => { ("DEEPSEEK_API_KEY", "codewhale auth set --provider deepseek")