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
This commit is contained in:
@@ -40,7 +40,10 @@ fn next_unique_id(existing: &HashSet<String>, base: &str) -> String {
|
|||||||
fn extract_claude_api_key(value: &Value) -> Option<String> {
|
fn extract_claude_api_key(value: &Value) -> Option<String> {
|
||||||
value
|
value
|
||||||
.get("env")
|
.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())
|
.and_then(|v| v.as_str())
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1032,6 +1032,7 @@ impl ProviderService {
|
|||||||
|
|
||||||
let api_key = env
|
let api_key = env
|
||||||
.get("ANTHROPIC_AUTH_TOKEN")
|
.get("ANTHROPIC_AUTH_TOKEN")
|
||||||
|
.or_else(|| env.get("ANTHROPIC_API_KEY"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
AppError::localized(
|
AppError::localized(
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface ProviderPreset {
|
|||||||
settingsConfig: object;
|
settingsConfig: object;
|
||||||
isOfficial?: boolean; // 标识是否为官方预设
|
isOfficial?: boolean; // 标识是否为官方预设
|
||||||
category?: ProviderCategory; // 新增:分类
|
category?: ProviderCategory; // 新增:分类
|
||||||
|
// 新增:指定该预设所使用的 API Key 字段名(默认 ANTHROPIC_AUTH_TOKEN)
|
||||||
|
apiKeyField?: "ANTHROPIC_AUTH_TOKEN" | "ANTHROPIC_API_KEY";
|
||||||
// 新增:模板变量定义,用于动态替换配置中的值
|
// 新增:模板变量定义,用于动态替换配置中的值
|
||||||
templateValues?: Record<string, TemplateValueConfig>; // editorValue 存储编辑器中的实时输入值
|
templateValues?: Record<string, TemplateValueConfig>; // editorValue 存储编辑器中的实时输入值
|
||||||
// 新增:请求地址候选列表(用于地址管理/测速)
|
// 新增:请求地址候选列表(用于地址管理/测速)
|
||||||
@@ -53,6 +55,30 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
textColor: "#FFFFFF",
|
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",
|
name: "DeepSeek",
|
||||||
websiteUrl: "https://platform.deepseek.com",
|
websiteUrl: "https://platform.deepseek.com",
|
||||||
|
|||||||
@@ -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 => {
|
export const getApiKeyFromConfig = (jsonString: string): string => {
|
||||||
try {
|
try {
|
||||||
const config = JSON.parse(jsonString);
|
const config = JSON.parse(jsonString);
|
||||||
const key = config?.env?.ANTHROPIC_AUTH_TOKEN;
|
const token = config?.env?.ANTHROPIC_AUTH_TOKEN;
|
||||||
return typeof key === "string" ? key : "";
|
const apiKey = config?.env?.ANTHROPIC_API_KEY;
|
||||||
|
const value = typeof token === "string" ? token : typeof apiKey === "string" ? apiKey : "";
|
||||||
|
return value;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -224,9 +226,10 @@ export const applyTemplateValues = (
|
|||||||
export const hasApiKeyField = (jsonString: string): boolean => {
|
export const hasApiKeyField = (jsonString: string): boolean => {
|
||||||
try {
|
try {
|
||||||
const config = JSON.parse(jsonString);
|
const config = JSON.parse(jsonString);
|
||||||
return Object.prototype.hasOwnProperty.call(
|
const env = config?.env ?? {};
|
||||||
config?.env ?? {},
|
return (
|
||||||
"ANTHROPIC_AUTH_TOKEN",
|
Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_AUTH_TOKEN") ||
|
||||||
|
Object.prototype.hasOwnProperty.call(env, "ANTHROPIC_API_KEY")
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
@@ -246,10 +249,17 @@ export const setApiKeyInConfig = (
|
|||||||
if (!createIfMissing) return jsonString;
|
if (!createIfMissing) return jsonString;
|
||||||
config.env = {};
|
config.env = {};
|
||||||
}
|
}
|
||||||
if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) {
|
const env = config.env as Record<string, any>;
|
||||||
|
// 优先写入已存在的字段;若两者均不存在且允许创建,则默认创建 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;
|
return jsonString;
|
||||||
}
|
}
|
||||||
config.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
||||||
return JSON.stringify(config, null, 2);
|
return JSON.stringify(config, null, 2);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return jsonString;
|
return jsonString;
|
||||||
|
|||||||
Reference in New Issue
Block a user