fix(proxy): respect existing token field when syncing Claude config

- Add support for ANTHROPIC_API_KEY in Claude auth extraction
- Only update existing token fields during sync, avoid adding fields
  that weren't originally configured by the user
- Add tests for both scenarios
This commit is contained in:
Jason
2025-12-20 11:04:07 +08:00
parent 3e8f84481d
commit 8ecb41d25e
2 changed files with 198 additions and 21 deletions

View File

@@ -87,6 +87,14 @@ impl ClaudeAdapter {
log::debug!("[Claude] 使用 ANTHROPIC_AUTH_TOKEN"); log::debug!("[Claude] 使用 ANTHROPIC_AUTH_TOKEN");
return Some(key.to_string()); return Some(key.to_string());
} }
if let Some(key) = env
.get("ANTHROPIC_API_KEY")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
{
log::debug!("[Claude] 使用 ANTHROPIC_API_KEY");
return Some(key.to_string());
}
// OpenRouter key // OpenRouter key
if let Some(key) = env if let Some(key) = env
.get("OPENROUTER_API_KEY") .get("OPENROUTER_API_KEY")
@@ -284,6 +292,21 @@ mod tests {
assert_eq!(auth.strategy, AuthStrategy::Anthropic); assert_eq!(auth.strategy, AuthStrategy::Anthropic);
} }
#[test]
fn test_extract_auth_anthropic_api_key() {
let adapter = ClaudeAdapter::new();
let provider = create_provider(json!({
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"ANTHROPIC_API_KEY": "sk-ant-test-key"
}
}));
let auth = adapter.extract_auth(&provider).unwrap();
assert_eq!(auth.api_key, "sk-ant-test-key");
assert_eq!(auth.strategy, AuthStrategy::Anthropic);
}
#[test] #[test]
fn test_extract_auth_openrouter() { fn test_extract_auth_openrouter() {
let adapter = ClaudeAdapter::new(); let adapter = ClaudeAdapter::new();

View File

@@ -312,34 +312,35 @@ impl ProxyService {
match env_obj { match env_obj {
Some(obj) => { Some(obj) => {
obj.insert(token_key.to_string(), json!(token));
// ANTHROPIC_AUTH_TOKEN 与 ANTHROPIC_API_KEY 视为同义字段,保持一致
if token_key == "ANTHROPIC_AUTH_TOKEN" if token_key == "ANTHROPIC_AUTH_TOKEN"
|| token_key == "ANTHROPIC_API_KEY" || token_key == "ANTHROPIC_API_KEY"
{ {
obj.insert( let mut updated = false;
"ANTHROPIC_AUTH_TOKEN".to_string(), if obj.contains_key("ANTHROPIC_AUTH_TOKEN") {
json!(token), obj.insert(
); "ANTHROPIC_AUTH_TOKEN".to_string(),
obj.insert( json!(token),
"ANTHROPIC_API_KEY".to_string(), );
json!(token), updated = true;
); }
if obj.contains_key("ANTHROPIC_API_KEY") {
obj.insert(
"ANTHROPIC_API_KEY".to_string(),
json!(token),
);
updated = true;
}
if !updated {
obj.insert(token_key.to_string(), json!(token));
}
} else {
obj.insert(token_key.to_string(), json!(token));
} }
} }
None => { None => {
// 至少写入一份可用的 Token // 至少写入一份可用的 Token
provider.settings_config["env"] = json!({ provider.settings_config["env"] =
token_key: token json!({ token_key: token });
});
if token_key == "ANTHROPIC_AUTH_TOKEN"
|| token_key == "ANTHROPIC_API_KEY"
{
provider.settings_config["env"]
["ANTHROPIC_AUTH_TOKEN"] = json!(token);
provider.settings_config["env"]["ANTHROPIC_API_KEY"] =
json!(token);
}
} }
} }
@@ -1575,6 +1576,47 @@ impl ProxyService {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use serial_test::serial;
use std::env;
use tempfile::TempDir;
struct TempHome {
#[allow(dead_code)]
dir: TempDir,
original_home: Option<String>,
original_userprofile: Option<String>,
}
impl TempHome {
fn new() -> Self {
let dir = TempDir::new().expect("failed to create temp home");
let original_home = env::var("HOME").ok();
let original_userprofile = env::var("USERPROFILE").ok();
env::set_var("HOME", dir.path());
env::set_var("USERPROFILE", dir.path());
Self {
dir,
original_home,
original_userprofile,
}
}
}
impl Drop for TempHome {
fn drop(&mut self) {
match &self.original_home {
Some(value) => env::set_var("HOME", value),
None => env::remove_var("HOME"),
}
match &self.original_userprofile {
Some(value) => env::set_var("USERPROFILE", value),
None => env::remove_var("USERPROFILE"),
}
}
}
#[test] #[test]
fn update_toml_base_url_updates_active_model_provider_base_url() { fn update_toml_base_url_updates_active_model_provider_base_url() {
@@ -1637,4 +1679,116 @@ model = "gpt-5.1-codex"
assert_eq!(base_url, new_url); assert_eq!(base_url, new_url);
} }
#[tokio::test]
#[serial]
async fn sync_claude_token_does_not_add_anthropic_api_key() {
let _home = TempHome::new();
crate::settings::reload_settings().expect("reload settings");
let db = Arc::new(Database::memory().expect("init db"));
let service = ProxyService::new(db.clone());
let provider = Provider::with_id(
"p1".to_string(),
"P1".to_string(),
json!({
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"ANTHROPIC_AUTH_TOKEN": "stale"
}
}),
None,
);
db.save_provider("claude", &provider)
.expect("save provider");
db.set_current_provider("claude", "p1")
.expect("set current provider");
let live_config = json!({
"env": {
"ANTHROPIC_AUTH_TOKEN": "fresh"
}
});
service
.sync_live_config_to_provider(&AppType::Claude, &live_config)
.await
.expect("sync");
let updated = db
.get_provider_by_id("p1", "claude")
.expect("get provider")
.expect("provider exists");
let env = updated
.settings_config
.get("env")
.and_then(|v| v.as_object())
.expect("env object");
assert_eq!(
env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()),
Some("fresh")
);
assert!(
!env.contains_key("ANTHROPIC_API_KEY"),
"should not add ANTHROPIC_API_KEY when absent"
);
}
#[tokio::test]
#[serial]
async fn sync_claude_token_respects_existing_api_key_field() {
let _home = TempHome::new();
crate::settings::reload_settings().expect("reload settings");
let db = Arc::new(Database::memory().expect("init db"));
let service = ProxyService::new(db.clone());
let provider = Provider::with_id(
"p1".to_string(),
"P1".to_string(),
json!({
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"ANTHROPIC_API_KEY": "stale"
}
}),
None,
);
db.save_provider("claude", &provider)
.expect("save provider");
db.set_current_provider("claude", "p1")
.expect("set current provider");
let live_config = json!({
"env": {
"ANTHROPIC_AUTH_TOKEN": "fresh"
}
});
service
.sync_live_config_to_provider(&AppType::Claude, &live_config)
.await
.expect("sync");
let updated = db
.get_provider_by_id("p1", "claude")
.expect("get provider")
.expect("provider exists");
let env = updated
.settings_config
.get("env")
.and_then(|v| v.as_object())
.expect("env object");
assert_eq!(
env.get("ANTHROPIC_API_KEY").and_then(|v| v.as_str()),
Some("fresh")
);
assert!(
!env.contains_key("ANTHROPIC_AUTH_TOKEN"),
"should not add ANTHROPIC_AUTH_TOKEN when absent"
);
}
} }