From c84292235fd5dae5688c2b23cb0124ef8b7019dc Mon Sep 17 00:00:00 2001 From: hongchen1993 Date: Tue, 9 Jun 2026 11:48:59 +0800 Subject: [PATCH] feat(config): prefer dispatcher-provided API key over saved DeepSeek key when source is cli When the CLI dispatcher launches the interactive TUI with an explicit `--api-key` argument (e.g. for a DeepSeek-compatible subscription endpoint), the environment variable `DEEPSEEK_API_KEY` carries the intended key with `DEEPSEEK_API_KEY_SOURCE=cli`. Previously the saved root `api_key` in config.toml always won over this env override for the DeepSeek provider, blocking users from running: codewhale --provider deepseek \ --api-key ark-... --base-url https://... --model auto This change gives the dispatcher-supplied env key priority when the source marker is `cli`, keeping full backward compatibility for normal config-file or keyring paths, and also cleaning up a `***` literal in an unrelated test. --- crates/tui/src/config.rs | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index be3bd836..c0e22c18 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -2518,6 +2518,20 @@ impl Config { // 0. DeepSeek compatibility slot. The legacy top-level `api_key` // belongs to DeepSeek only; provider-specific keys below must win for // NIM/OpenRouter/etc. so a stale DeepSeek key is not sent elsewhere. + // + // However, when the CLI dispatcher forwards an explicit `--api-key` + // through `DEEPSEEK_API_KEY` with the dispatcher source marker, that + // intentional override must win over the saved root key. This is + // essential for DeepSeek-compatible subscription endpoints where the + // user runs something like: + // codewhale --provider deepseek --api-key ark-... --base-url ... --model auto + if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) + && std::env::var("DEEPSEEK_API_KEY_SOURCE").as_deref() == Ok("cli") + && let Some(env_key) = codewhale_secrets::env_for("deepseek") + && !env_key.trim().is_empty() + { + return Ok(env_key); + } if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) && let Some(configured) = self.api_key.as_ref() && !configured.trim().is_empty() @@ -7002,7 +7016,7 @@ action = "session.compact" // Env var path. let env_cfg = Config::default(); unsafe { - std::env::set_var("DEEPSEEK_API_KEY", "sk-test-from-env"); + std::env::set_var("DEEPSEEK_API_KEY", "env-key"); } assert!( has_api_key(&env_cfg), @@ -7014,6 +7028,31 @@ action = "session.compact" Ok(()) } + #[test] + fn deepseek_dispatcher_env_key_overrides_config_key() -> Result<()> { + let _lock = lock_test_env(); + let prev_source = std::env::var_os("DEEPSEEK_API_KEY_SOURCE"); + unsafe { + std::env::set_var("DEEPSEEK_API_KEY", "ark-dispatcher-key"); + std::env::set_var("DEEPSEEK_API_KEY_SOURCE", "cli"); + } + let config = Config { + api_key: Some("saved-deepseek-key".to_string()), + ..Default::default() + }; + + assert_eq!(config.deepseek_api_key()?, "ark-dispatcher-key"); + + unsafe { + std::env::remove_var("DEEPSEEK_API_KEY"); + match prev_source { + Some(value) => std::env::set_var("DEEPSEEK_API_KEY_SOURCE", value), + None => std::env::remove_var("DEEPSEEK_API_KEY_SOURCE"), + } + } + Ok(()) + } + fn config_with_provider_scoped_key(provider: &str, api_key: &str) -> Config { let mut providers = ProvidersConfig::default(); match provider {