From 50eb4538ca49c103bf83be15f43b27a5715a29d5 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 2 Nov 2025 23:10:21 +0800 Subject: [PATCH] feat: support ANTHROPIC_API_KEY field and add AiHubMix provider Add compatibility for providers that use ANTHROPIC_API_KEY instead of ANTHROPIC_AUTH_TOKEN, enabling support for AiHubMix and similar services. Changes: - Backend: Add fallback to ANTHROPIC_API_KEY in credential extraction - migration.rs: Support API_KEY during config migration - services/provider.rs: Extract credentials from either field - Frontend: Smart API key field detection and management - getApiKeyFromConfig: Read from either field (AUTH_TOKEN priority) - setApiKeyInConfig: Write to existing field, preserve user choice - hasApiKeyField: Detect presence of either field - Add AiHubMix provider preset with dual endpoint candidates - Define apiKeyField metadata for provider presets (documentation) Technical Details: - Uses or_else() for zero-cost field fallback in Rust - Runtime field detection ensures correct field name preservation - Maintains backward compatibility with existing configurations - Priority: ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY --- src-tauri/src/migration.rs | 5 ++++- src-tauri/src/services/provider.rs | 1 + src/config/claudeProviderPresets.ts | 26 ++++++++++++++++++++++++++ src/utils/providerConfigUtils.ts | 26 ++++++++++++++++++-------- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/migration.rs b/src-tauri/src/migration.rs index f253e2e..cadf600 100644 --- a/src-tauri/src/migration.rs +++ b/src-tauri/src/migration.rs @@ -40,7 +40,10 @@ fn next_unique_id(existing: &HashSet, base: &str) -> String { fn extract_claude_api_key(value: &Value) -> Option { value .get("env") - .and_then(|env| env.get("ANTHROPIC_AUTH_TOKEN")) + .and_then(|env| { + env.get("ANTHROPIC_AUTH_TOKEN") + .or_else(|| env.get("ANTHROPIC_API_KEY")) + }) .and_then(|v| v.as_str()) .map(|s| s.to_string()) } diff --git a/src-tauri/src/services/provider.rs b/src-tauri/src/services/provider.rs index f7849f5..625f9f4 100644 --- a/src-tauri/src/services/provider.rs +++ b/src-tauri/src/services/provider.rs @@ -1032,6 +1032,7 @@ impl ProviderService { let api_key = env .get("ANTHROPIC_AUTH_TOKEN") + .or_else(|| env.get("ANTHROPIC_API_KEY")) .and_then(|v| v.as_str()) .ok_or_else(|| { AppError::localized( diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index 678b953..eee6963 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -30,6 +30,8 @@ export interface ProviderPreset { settingsConfig: object; isOfficial?: boolean; // 标识是否为官方预设 category?: ProviderCategory; // 新增:分类 + // 新增:指定该预设所使用的 API Key 字段名(默认 ANTHROPIC_AUTH_TOKEN) + apiKeyField?: "ANTHROPIC_AUTH_TOKEN" | "ANTHROPIC_API_KEY"; // 新增:模板变量定义,用于动态替换配置中的值 templateValues?: Record; // editorValue 存储编辑器中的实时输入值 // 新增:请求地址候选列表(用于地址管理/测速) @@ -53,6 +55,30 @@ export const providerPresets: ProviderPreset[] = [ textColor: "#FFFFFF", }, }, + { + name: "AiHubMix", + websiteUrl: "https://aihubmix.com", + apiKeyUrl: "https://aihubmix.com", + // 说明:该供应商使用 ANTHROPIC_API_KEY(而非 ANTHROPIC_AUTH_TOKEN) + apiKeyField: "ANTHROPIC_API_KEY", + settingsConfig: { + env: { + ANTHROPIC_BASE_URL: "https://aihubmix.com", + ANTHROPIC_API_KEY: "", + // 可选的模型默认值(留空表示使用系统默认) + // ANTHROPIC_MODEL: "", + // ANTHROPIC_DEFAULT_HAIKU_MODEL: "", + // ANTHROPIC_DEFAULT_SONNET_MODEL: "", + // ANTHROPIC_DEFAULT_OPUS_MODEL: "", + }, + }, + // 请求地址候选(用于地址管理/测速),用户可自行选择/覆盖 + endpointCandidates: [ + "https://aihubmix.com", + "https://api.aihubmix.com", + ], + category: "cn_official", + }, { name: "DeepSeek", websiteUrl: "https://platform.deepseek.com", diff --git a/src/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts index 56d53de..f577c87 100644 --- a/src/utils/providerConfigUtils.ts +++ b/src/utils/providerConfigUtils.ts @@ -164,12 +164,14 @@ export const hasCommonConfigSnippet = ( } }; -// 读取配置中的 API Key(env.ANTHROPIC_AUTH_TOKEN) +// 读取配置中的 API Key(优先 ANTHROPIC_AUTH_TOKEN,其次 ANTHROPIC_API_KEY) export const getApiKeyFromConfig = (jsonString: string): string => { try { const config = JSON.parse(jsonString); - const key = config?.env?.ANTHROPIC_AUTH_TOKEN; - return typeof key === "string" ? key : ""; + const token = config?.env?.ANTHROPIC_AUTH_TOKEN; + const apiKey = config?.env?.ANTHROPIC_API_KEY; + const value = typeof token === "string" ? token : typeof apiKey === "string" ? apiKey : ""; + return value; } catch (err) { return ""; } @@ -224,9 +226,10 @@ export const applyTemplateValues = ( export const hasApiKeyField = (jsonString: string): boolean => { try { const config = JSON.parse(jsonString); - return Object.prototype.hasOwnProperty.call( - config?.env ?? {}, - "ANTHROPIC_AUTH_TOKEN", + const env = config?.env ?? {}; + return ( + Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_AUTH_TOKEN") || + Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_API_KEY") ); } catch (err) { return false; @@ -246,10 +249,17 @@ export const setApiKeyInConfig = ( if (!createIfMissing) return jsonString; config.env = {}; } - if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) { + const env = config.env as Record; + // 优先写入已存在的字段;若两者均不存在且允许创建,则默认创建 AUTH_TOKEN 字段 + if ("ANTHROPIC_AUTH_TOKEN" in env) { + env.ANTHROPIC_AUTH_TOKEN = apiKey; + } else if ("ANTHROPIC_API_KEY" in env) { + env.ANTHROPIC_API_KEY = apiKey; + } else if (createIfMissing) { + env.ANTHROPIC_AUTH_TOKEN = apiKey; + } else { return jsonString; } - config.env.ANTHROPIC_AUTH_TOKEN = apiKey; return JSON.stringify(config, null, 2); } catch (err) { return jsonString;