Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src-tauri/src/cli/commands/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 6 additions & 5 deletions src-tauri/src/cli/commands/provider_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ fn prompt_claude_config(current: Option<&Value>) -> Result<Value, AppError> {

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 供编辑
Expand Down Expand Up @@ -369,8 +369,9 @@ fn prompt_claude_config(current: Option<&Value>) -> Result<Value, AppError> {
.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 {
Expand Down Expand Up @@ -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(),
Expand Down
5 changes: 1 addition & 4 deletions src-tauri/src/cli/interactive/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 7 additions & 5 deletions src-tauri/src/cli/tui/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 1 addition & 4 deletions src-tauri/src/cli/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions src-tauri/src/deeplink/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,9 @@ fn build_provider_meta(request: &DeepLinkImportRequest) -> Result<Option<Provide

fn build_claude_settings(request: &DeepLinkImportRequest) -> 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)),
Expand Down Expand Up @@ -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());
}
}
Expand Down
19 changes: 19 additions & 0 deletions src-tauri/src/services/provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Value>) -> Option<&str> {
env.get("ANTHROPIC_AUTH_TOKEN")
.or_else(|| env.get("ANTHROPIC_API_KEY"))
.and_then(|v| v.as_str())
}

/// 供应商相关业务逻辑
pub struct ProviderService;

Expand Down
4 changes: 1 addition & 3 deletions src-tauri/src/services/provider/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tests/deeplink_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down