diff --git a/src-tauri/src/cli/commands/provider.rs b/src-tauri/src/cli/commands/provider.rs index 06448f0..c4df6da 100644 --- a/src-tauri/src/cli/commands/provider.rs +++ b/src-tauri/src/cli/commands/provider.rs @@ -604,10 +604,7 @@ fn extract_claude_config(settings_config: &serde_json::Value) -> ClaudeConfig { if let Some(env) = env { ClaudeConfig { - api_key: env - .get("ANTHROPIC_AUTH_TOKEN") - .or_else(|| env.get("ANTHROPIC_API_KEY")) - .and_then(|v| v.as_str()) + api_key: crate::services::provider::get_claude_token_from_env(env) .map(|s| mask_api_key(s)), base_url: env .get("ANTHROPIC_BASE_URL") diff --git a/src-tauri/src/cli/commands/provider_input.rs b/src-tauri/src/cli/commands/provider_input.rs index a6e39ce..5e787c8 100644 --- a/src-tauri/src/cli/commands/provider_input.rs +++ b/src-tauri/src/cli/commands/provider_input.rs @@ -324,8 +324,8 @@ fn prompt_claude_config(current: Option<&Value>) -> Result { let api_key = if let Some(current_key) = current .and_then(|v| v.get("env")) - .and_then(|e| e.get("ANTHROPIC_AUTH_TOKEN")) - .and_then(|k| k.as_str()) + .and_then(|v| v.as_object()) + .and_then(crate::services::provider::get_claude_token_from_env) .filter(|s| !s.is_empty()) { // 编辑模式:显示完整 API Key 供编辑 @@ -369,8 +369,9 @@ fn prompt_claude_config(current: Option<&Value>) -> Result { .prompt() .map_err(|e| AppError::Message(texts::input_failed_error(&e.to_string())))?; + let (key, _) = crate::services::provider::claude_auth_env_keys(api_key.trim()); let mut env = serde_json::Map::new(); - env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), json!(api_key.trim())); + env.insert(key.to_string(), json!(api_key.trim())); env.insert("ANTHROPIC_BASE_URL".to_string(), json!(base_url.trim())); if config_models { @@ -754,8 +755,8 @@ pub fn display_provider_summary(provider: &Provider, app_type: &AppType) { println!("\n{}", texts::core_config_label().bright_cyan()); match app_type { AppType::Claude => { - if let Some(env) = provider.settings_config.get("env") { - if let Some(api_key) = env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()) { + if let Some(env) = provider.settings_config.get("env").and_then(|v| v.as_object()) { + if let Some(api_key) = crate::services::provider::get_claude_token_from_env(env) { println!( " {}: {}", texts::api_key_display_label(), diff --git a/src-tauri/src/cli/interactive/provider.rs b/src-tauri/src/cli/interactive/provider.rs index 98eb57e..54bd8fb 100644 --- a/src-tauri/src/cli/interactive/provider.rs +++ b/src-tauri/src/cli/interactive/provider.rs @@ -725,10 +725,7 @@ fn extract_claude_config(settings_config: &serde_json::Value) -> ClaudeConfig { if let Some(env) = env { ClaudeConfig { - api_key: env - .get("ANTHROPIC_AUTH_TOKEN") - .or_else(|| env.get("ANTHROPIC_API_KEY")) - .and_then(|v| v.as_str()) + api_key: crate::services::provider::get_claude_token_from_env(env) .map(|s| mask_api_key(s)), base_url: env .get("ANTHROPIC_BASE_URL") diff --git a/src-tauri/src/cli/tui/form.rs b/src-tauri/src/cli/tui/form.rs index e7662cc..231a9dc 100644 --- a/src-tauri/src/cli/tui/form.rs +++ b/src-tauri/src/cli/tui/form.rs @@ -402,7 +402,7 @@ impl ProviderAddFormState { .get("env") .and_then(|v| v.as_object()) { - if let Some(token) = env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()) { + if let Some(token) = crate::services::provider::get_claude_token_from_env(env) { form.claude_api_key.set(token); } if let Some(url) = env.get("ANTHROPIC_BASE_URL").and_then(|v| v.as_str()) { @@ -883,7 +883,9 @@ impl ProviderAddFormState { let env_obj = env_value .as_object_mut() .expect("env must be a JSON object"); - set_or_remove_trimmed(env_obj, "ANTHROPIC_AUTH_TOKEN", &self.claude_api_key.value); + let (key, old_key) = crate::services::provider::claude_auth_env_keys(&self.claude_api_key.value); + set_or_remove_trimmed(env_obj, key, &self.claude_api_key.value); + env_obj.remove(old_key); set_or_remove_trimmed(env_obj, "ANTHROPIC_BASE_URL", &self.claude_base_url.value); if self.claude_model_config_touched { set_or_remove_trimmed(env_obj, "ANTHROPIC_MODEL", &self.claude_model.value); @@ -2108,7 +2110,7 @@ mod tests { assert_eq!(provider["id"], "p1"); assert_eq!(provider["name"], "Provider One"); assert_eq!( - provider["settingsConfig"]["env"]["ANTHROPIC_AUTH_TOKEN"], + provider["settingsConfig"]["env"]["ANTHROPIC_API_KEY"], "token" ); assert_eq!( @@ -2544,7 +2546,7 @@ requires_openai_auth = true settings["env"]["ANTHROPIC_BASE_URL"], "https://provider.example", "provider field should override common snippet value" ); - assert_eq!(settings["env"]["ANTHROPIC_AUTH_TOKEN"], "sk-provider"); + assert_eq!(settings["env"]["ANTHROPIC_API_KEY"], "sk-provider"); } #[test] @@ -2704,7 +2706,7 @@ requires_openai_auth = true "common env keys should be removed" ); assert_eq!( - env.get("ANTHROPIC_AUTH_TOKEN") + env.get("ANTHROPIC_API_KEY") .and_then(|value| value.as_str()), Some("sk-provider"), "provider-specific env keys should be preserved" diff --git a/src-tauri/src/cli/tui/ui.rs b/src-tauri/src/cli/tui/ui.rs index bbfd3c8..f2d45e9 100644 --- a/src-tauri/src/cli/tui/ui.rs +++ b/src-tauri/src/cli/tui/ui.rs @@ -2717,10 +2717,7 @@ fn render_provider_detail( .get("env") .and_then(|v| v.as_object()) { - let api_key = env - .get("ANTHROPIC_AUTH_TOKEN") - .or_else(|| env.get("ANTHROPIC_API_KEY")) - .and_then(|v| v.as_str()) + let api_key = crate::services::provider::get_claude_token_from_env(env) .map(mask_api_key) .unwrap_or_else(|| texts::tui_na().to_string()); let base_url = env diff --git a/src-tauri/src/deeplink/provider.rs b/src-tauri/src/deeplink/provider.rs index 7951b7e..9ded48e 100644 --- a/src-tauri/src/deeplink/provider.rs +++ b/src-tauri/src/deeplink/provider.rs @@ -219,10 +219,9 @@ fn build_provider_meta(request: &DeepLinkImportRequest) -> Result serde_json::Value { let mut env = serde_json::Map::new(); - env.insert( - "ANTHROPIC_AUTH_TOKEN".to_string(), - json!(request.api_key.clone().unwrap_or_default()), - ); + let token = request.api_key.clone().unwrap_or_default(); + let (key, _) = crate::services::provider::claude_auth_env_keys(&token); + env.insert(key.to_string(), json!(token)); env.insert( "ANTHROPIC_BASE_URL".to_string(), json!(get_primary_endpoint(request)), @@ -358,7 +357,7 @@ fn merge_claude_config( })?; if request.api_key.as_ref().is_none_or(|s| s.is_empty()) { - if let Some(token) = env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()) { + if let Some(token) = crate::services::provider::get_claude_token_from_env(env) { request.api_key = Some(token.to_string()); } } diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index cf9d4e3..58f6565 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -20,6 +20,25 @@ use crate::store::AppState; use gemini_auth::GeminiAuthType; use live::LiveSnapshot; +/// 根据 token 前缀判断 Claude 认证环境变量名。 +/// OAuth token (`sk-ant-oat...`) → `ANTHROPIC_AUTH_TOKEN`(Bearer header), +/// 标准 API key → `ANTHROPIC_API_KEY`(x-api-key header)。 +/// 返回 `(应使用的 key, 应清除的旧 key)`。 +pub fn claude_auth_env_keys(token: &str) -> (&'static str, &'static str) { + if token.trim().starts_with("sk-ant-oat") { + ("ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY") + } else { + ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN") + } +} + +/// 从 env 对象中读取 Claude API key,兼容两种 key 名。 +pub fn get_claude_token_from_env(env: &serde_json::Map) -> Option<&str> { + env.get("ANTHROPIC_AUTH_TOKEN") + .or_else(|| env.get("ANTHROPIC_API_KEY")) + .and_then(|v| v.as_str()) +} + /// 供应商相关业务逻辑 pub struct ProviderService; diff --git a/src-tauri/src/services/provider/usage.rs b/src-tauri/src/services/provider/usage.rs index 72d9470..56204ff 100644 --- a/src-tauri/src/services/provider/usage.rs +++ b/src-tauri/src/services/provider/usage.rs @@ -202,9 +202,7 @@ impl ProviderService { ) })?; - env.get("ANTHROPIC_AUTH_TOKEN") - .or_else(|| env.get("ANTHROPIC_API_KEY")) - .and_then(|v| v.as_str()) + super::get_claude_token_from_env(env) .ok_or_else(|| { AppError::localized( "provider.claude.api_key.missing", diff --git a/src-tauri/tests/deeplink_import.rs b/src-tauri/tests/deeplink_import.rs index b1ac401..bb5d8a8 100644 --- a/src-tauri/tests/deeplink_import.rs +++ b/src-tauri/tests/deeplink_import.rs @@ -39,7 +39,7 @@ fn deeplink_import_claude_provider_persists_to_config() { assert_eq!(provider.website_url.as_deref(), request.homepage.as_deref()); let auth_token = provider .settings_config - .pointer("/env/ANTHROPIC_AUTH_TOKEN") + .pointer("/env/ANTHROPIC_API_KEY") .and_then(|v| v.as_str()); let base_url = provider .settings_config