refactor(migration): dedupe by (name + raw key) without hashing\n\n- Compare API key strings directly for Claude/Codex during migration\n- Remove sha2/hex deps and hashing helpers\n- Keep O(N^2) matching acceptable for small provider sets

This commit is contained in:
Jason
2025-09-04 23:00:16 +08:00
parent 33753c72cd
commit 30a441d9ec

View File

@@ -36,6 +36,24 @@ fn next_unique_id(existing: &HashSet<String>, base: &str) -> String {
format!("{}-dup", base) format!("{}-dup", base)
} }
fn extract_claude_api_key(value: &Value) -> Option<String> {
value
.get("env")
.and_then(|env| env.get("ANTHROPIC_AUTH_TOKEN"))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
fn extract_codex_api_key(value: &Value) -> Option<String> {
value
.get("auth")
.and_then(|auth| auth.get("OPENAI_API_KEY").or_else(|| auth.get("openai_api_key")))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
// 去重策略name + 原始 key 直接比较(不做哈希)
fn scan_claude_copies() -> Vec<(String, PathBuf, Value)> { fn scan_claude_copies() -> Vec<(String, PathBuf, Value)> {
let mut items = Vec::new(); let mut items = Vec::new();
let dir = get_claude_config_dir(); let dir = get_claude_config_dir();
@@ -153,21 +171,27 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
} }
}; };
// 合并Claude优先 live然后副本 // 合并Claude优先 live然后副本 - 去重键: name + apiKey直接比较
config.ensure_app(&AppType::Claude); config.ensure_app(&AppType::Claude);
let manager = config.get_manager_mut(&AppType::Claude).unwrap(); let manager = config.get_manager_mut(&AppType::Claude).unwrap();
let mut ids: HashSet<String> = manager.providers.keys().cloned().collect(); let mut ids: HashSet<String> = manager.providers.keys().cloned().collect();
let mut live_claude_id: Option<String> = None; let mut live_claude_id: Option<String> = None;
if let Some((name, value)) = &live_claude { if let Some((name, value)) = &live_claude {
if let Some((id, prov)) = manager let cand_key = extract_claude_api_key(value);
let exist_id = manager
.providers .providers
.iter_mut() .iter()
.find(|(_, p)| p.name == *name) .find_map(|(id, p)| {
{ let pk = extract_claude_api_key(&p.settings_config);
log::info!("覆盖 Claude 供应商 '{}' 来自 live settings.json", name); if p.name == *name && pk == cand_key { Some(id.clone()) } else { None }
});
if let Some(exist_id) = exist_id {
if let Some(prov) = manager.providers.get_mut(&exist_id) {
log::info!("合并到已存在 Claude 供应商 '{}' (by name+key)", name);
prov.settings_config = value.clone(); prov.settings_config = value.clone();
live_claude_id = Some(id.clone()); live_claude_id = Some(exist_id);
}
} else { } else {
let id = next_unique_id(&ids, name); let id = next_unique_id(&ids, name);
ids.insert(id.clone()); ids.insert(id.clone());
@@ -182,20 +206,24 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
} }
} }
for (name, path, value) in claude_items.iter() { for (name, path, value) in claude_items.iter() {
if let Some((id, prov)) = manager let cand_key = extract_claude_api_key(value);
let exist_id = manager
.providers .providers
.iter_mut() .iter()
.find(|(_, p)| p.name == *name) .find_map(|(id, p)| {
{ let pk = extract_claude_api_key(&p.settings_config);
// 重名:覆盖为副本内容 if p.name == *name && pk == cand_key { Some(id.clone()) } else { None }
log::info!("覆盖 Claude 供应商 '{}' 来自 {}", name, path.display()); });
if let Some(exist_id) = exist_id {
if let Some(prov) = manager.providers.get_mut(&exist_id) {
log::info!("覆盖 Claude 供应商 '{}' 来自 {} (by name+key)", name, path.display());
prov.settings_config = value.clone(); prov.settings_config = value.clone();
}
} else { } else {
// 新增
let id = next_unique_id(&ids, name); let id = next_unique_id(&ids, name);
ids.insert(id.clone()); ids.insert(id.clone());
let provider = crate::provider::Provider::with_id( let provider = crate::provider::Provider::with_id(
id, id.clone(),
name.clone(), name.clone(),
value.clone(), value.clone(),
None, None,
@@ -241,21 +269,27 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
} }
}; };
// 合并Codex优先 live然后副本 // 合并Codex优先 live然后副本 - 去重键: name + OPENAI_API_KEY直接比较
config.ensure_app(&AppType::Codex); config.ensure_app(&AppType::Codex);
let manager = config.get_manager_mut(&AppType::Codex).unwrap(); let manager = config.get_manager_mut(&AppType::Codex).unwrap();
let mut ids: HashSet<String> = manager.providers.keys().cloned().collect(); let mut ids: HashSet<String> = manager.providers.keys().cloned().collect();
let mut live_codex_id: Option<String> = None; let mut live_codex_id: Option<String> = None;
if let Some((name, value)) = &live_codex { if let Some((name, value)) = &live_codex {
if let Some((id, prov)) = manager let cand_key = extract_codex_api_key(value);
let exist_id = manager
.providers .providers
.iter_mut() .iter()
.find(|(_, p)| p.name == *name) .find_map(|(id, p)| {
{ let pk = extract_codex_api_key(&p.settings_config);
log::info!("覆盖 Codex 供应商 '{}' 来自 live auth/config", name); if p.name == *name && pk == cand_key { Some(id.clone()) } else { None }
});
if let Some(exist_id) = exist_id {
if let Some(prov) = manager.providers.get_mut(&exist_id) {
log::info!("合并到已存在 Codex 供应商 '{}' (by name+key)", name);
prov.settings_config = value.clone(); prov.settings_config = value.clone();
live_codex_id = Some(id.clone()); live_codex_id = Some(exist_id);
}
} else { } else {
let id = next_unique_id(&ids, name); let id = next_unique_id(&ids, name);
ids.insert(id.clone()); ids.insert(id.clone());
@@ -270,18 +304,24 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
} }
} }
for (name, authp, cfgp, value) in codex_items.iter() { for (name, authp, cfgp, value) in codex_items.iter() {
if let Some((_id, prov)) = manager let cand_key = extract_codex_api_key(value);
let exist_id = manager
.providers .providers
.iter_mut() .iter()
.find(|(_, p)| p.name == *name) .find_map(|(id, p)| {
{ let pk = extract_codex_api_key(&p.settings_config);
log::info!("覆盖 Codex 供应商 '{}' 来自 {:?} / {:?}", name, authp, cfgp); if p.name == *name && pk == cand_key { Some(id.clone()) } else { None }
});
if let Some(exist_id) = exist_id {
if let Some(prov) = manager.providers.get_mut(&exist_id) {
log::info!("覆盖 Codex 供应商 '{}' 来自 {:?}/{:?} (by name+key)", name, authp, cfgp);
prov.settings_config = value.clone(); prov.settings_config = value.clone();
}
} else { } else {
let id = next_unique_id(&ids, name); let id = next_unique_id(&ids, name);
ids.insert(id.clone()); ids.insert(id.clone());
let provider = crate::provider::Provider::with_id( let provider = crate::provider::Provider::with_id(
id, id.clone(),
name.clone(), name.clone(),
value.clone(), value.clone(),
None, None,