fix(oauth): 优化自动关联凭证逻辑以支持单个凭证关联
- 修改 autoLinkProviderConfigs 函数,增加 onlyCurrentCred 选项 - 当 onlyCurrentCred 为 true 时,仅关联当前生成的凭证文件 - 避免批量导入凭证时重复扫描所有配置文件 - 在 OAuth 回调中传递 credPath 参数,确保正确关联新凭证 - 统一 install-and-run 脚本中的包管理器检测逻辑 - 优化 Claude 提供商的 token 计数方法,提高准确性
This commit is contained in:
parent
d6c2bd7919
commit
3a54404e0e
11 changed files with 5014 additions and 118 deletions
|
|
@ -18,9 +18,9 @@ echo.
|
|||
if !FORCE_PULL! equ 1 (
|
||||
echo [更新] 正在从远程仓库拉取最新代码...
|
||||
git --version >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
if %errorlevel% equ 0 (
|
||||
git pull
|
||||
if !errorlevel! neq 0 (
|
||||
if %errorlevel% neq 0 (
|
||||
echo [警告] Git pull 失败,请检查网络或手动处理冲突。
|
||||
) else (
|
||||
echo [成功] 代码已更新。
|
||||
|
|
@ -55,14 +55,22 @@ if not exist "package.json" (
|
|||
|
||||
echo [成功] 找到package.json文件
|
||||
|
||||
echo [安装] 正在安装/更新依赖...
|
||||
:: 检查 pnpm 是否安装
|
||||
where pnpm >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
set PKG_MANAGER=pnpm
|
||||
) else (
|
||||
set PKG_MANAGER=npm
|
||||
)
|
||||
|
||||
echo [安装] 正在使用 !PKG_MANAGER! 安装/更新依赖...
|
||||
echo 这可能需要几分钟时间,请耐心等待...
|
||||
echo 正在执行: npm install...
|
||||
:: 使用npm install并设置超时机制
|
||||
call npm install --timeout=300000
|
||||
if !errorlevel! neq 0 (
|
||||
echo 正在执行: !PKG_MANAGER! install...
|
||||
|
||||
call !PKG_MANAGER! install
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 依赖安装失败
|
||||
echo 请检查网络连接或手动运行 'npm install'
|
||||
echo 请检查网络连接或手动运行 '!PKG_MANAGER! install'
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
|
@ -89,4 +97,4 @@ echo 按 Ctrl+C 停止服务器
|
|||
echo.
|
||||
|
||||
:: 启动服务器
|
||||
node src\core\master.js
|
||||
node src\core\master.js
|
||||
|
|
|
|||
|
|
@ -64,13 +64,21 @@ fi
|
|||
|
||||
echo "[成功] 找到package.json文件"
|
||||
|
||||
echo "[安装] 正在安装/更新依赖..."
|
||||
# 检查 pnpm 是否安装
|
||||
if command -v pnpm > /dev/null 2>&1; then
|
||||
PKG_MANAGER=pnpm
|
||||
else
|
||||
PKG_MANAGER=npm
|
||||
fi
|
||||
|
||||
echo "[安装] 正在使用 $PKG_MANAGER 安装/更新依赖..."
|
||||
echo "这可能需要几分钟时间,请耐心等待..."
|
||||
echo "正在执行: npm install..."
|
||||
npm install
|
||||
echo "正在执行: $PKG_MANAGER install..."
|
||||
|
||||
$PKG_MANAGER install
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[错误] 依赖安装失败"
|
||||
echo "请检查网络连接或运行 'npm install' 手动安装"
|
||||
echo "请检查网络连接或手动运行 '$PKG_MANAGER install'"
|
||||
exit 1
|
||||
fi
|
||||
echo "[成功] 依赖安装/更新完成"
|
||||
|
|
|
|||
4766
pnpm-lock.yaml
Normal file
4766
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -740,7 +740,10 @@ export async function handleCodexOAuth(currentConfig, options = {}) {
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: credentials.relativePath
|
||||
});
|
||||
|
||||
logger.info('[Codex Auth] OAuth flow completed successfully');
|
||||
} catch (error) {
|
||||
|
|
@ -844,7 +847,10 @@ export async function handleCodexOAuthCallback(code, state) {
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: result.relativePath
|
||||
});
|
||||
|
||||
logger.info('[Codex Auth] OAuth callback processed successfully');
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,10 @@ async function createOAuthCallbackServer(config, redirectUri, authClient, credPa
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: relativePath
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(generateResponsePage(true, '您可以关闭此页面'));
|
||||
|
|
|
|||
|
|
@ -375,7 +375,10 @@ function createIFlowCallbackServer(port, redirectUri, expectedState, options = {
|
|||
});
|
||||
|
||||
// 6. 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: relativePath
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(generateResponsePage(true, `授权成功!账户: ${userInfo.email},您可以关闭此页面`));
|
||||
|
|
|
|||
|
|
@ -413,7 +413,10 @@ async function pollKiroBuilderIDToken(clientId, clientSecret, deviceCode, interv
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: path.relative(process.cwd(), credPath)
|
||||
});
|
||||
|
||||
return tokenData;
|
||||
}
|
||||
|
|
@ -594,7 +597,10 @@ function createKiroHttpCallbackServer(port, codeVerifier, expectedState, options
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: path.relative(process.cwd(), credPath)
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(generateResponsePage(true, '授权成功!您可以关闭此页面'));
|
||||
|
|
@ -846,7 +852,7 @@ export async function batchImportKiroRefreshTokens(refreshTokens, region = KIRO_
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
// 自动关联新生成的凭据到 Pools(批量导入时扫描所有)
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
}
|
||||
|
||||
|
|
@ -979,7 +985,7 @@ export async function batchImportKiroRefreshTokensStream(refreshTokens, region =
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
// 自动关联新生成的凭据到 Pools(批量导入时扫描所有)
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
}
|
||||
|
||||
|
|
@ -1101,7 +1107,10 @@ export async function importAwsCredentials(credentials, skipDuplicateCheck = fal
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: relativePath
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -212,7 +212,10 @@ async function pollQwenToken(deviceCode, codeVerifier, interval = 5, expiresIn =
|
|||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
await autoLinkProviderConfigs(CONFIG, {
|
||||
onlyCurrentCred: true,
|
||||
credPath: relativePath
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -711,6 +711,30 @@ async saveCredentialsToFile(filePath, newData) {
|
|||
return String(message.content || message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理内容,将不同格式的内容转换为文本
|
||||
* @param {any} content - 内容对象或数组
|
||||
* @returns {string} 处理后的文本
|
||||
*/
|
||||
processContent(content) {
|
||||
if (!content) return "";
|
||||
if (typeof content === 'string') return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content.map(part => {
|
||||
if (typeof part === 'string') return part;
|
||||
if (part && typeof part === 'object') {
|
||||
if (part.type === 'text') return part.text || "";
|
||||
if (part.type === 'thinking') return part.thinking || part.text || "";
|
||||
if (part.type === 'tool_result') return this.processContent(part.content);
|
||||
if (part.type === 'tool_use' && part.input) return JSON.stringify(part.input);
|
||||
if (part.text) return part.text;
|
||||
}
|
||||
return "";
|
||||
}).join("");
|
||||
}
|
||||
return this.getContentText(content);
|
||||
}
|
||||
|
||||
_normalizeThinkingBudgetTokens(budgetTokens) {
|
||||
let value = Number(budgetTokens);
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
|
|
@ -2366,52 +2390,34 @@ async saveCredentialsToFile(filePath, newData) {
|
|||
* Calculate input tokens from request body using Claude's official tokenizer
|
||||
*/
|
||||
estimateInputTokens(requestBody) {
|
||||
let totalTokens = 0;
|
||||
let allText = "";
|
||||
|
||||
// Count system prompt tokens
|
||||
if (requestBody.system) {
|
||||
const systemText = this.getContentText(requestBody.system);
|
||||
totalTokens += this.countTextTokens(systemText);
|
||||
allText += this.processContent(requestBody.system);
|
||||
}
|
||||
|
||||
// Count thinking prefix tokens if thinking is enabled
|
||||
if (requestBody.thinking?.type === 'enabled') {
|
||||
const budget = this._normalizeThinkingBudgetTokens(requestBody.thinking.budget_tokens);
|
||||
const prefixText = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>`;
|
||||
totalTokens += this.countTextTokens(prefixText);
|
||||
allText += `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>`;
|
||||
}
|
||||
|
||||
// Count all messages tokens
|
||||
if (requestBody.messages && Array.isArray(requestBody.messages)) {
|
||||
for (const message of requestBody.messages) {
|
||||
if (message.content) {
|
||||
if (Array.isArray(message.content)) {
|
||||
for (const part of message.content) {
|
||||
if (part.type === 'text' && part.text) {
|
||||
totalTokens += this.countTextTokens(part.text);
|
||||
} else if (part.type === 'thinking' && part.thinking) {
|
||||
totalTokens += this.countTextTokens(part.thinking);
|
||||
} else if (part.type === 'tool_result') {
|
||||
const resultContent = this.getContentText(part.content);
|
||||
totalTokens += this.countTextTokens(resultContent);
|
||||
} else if (part.type === 'tool_use' && part.input) {
|
||||
totalTokens += this.countTextTokens(JSON.stringify(part.input));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const contentText = this.getContentText(message);
|
||||
totalTokens += this.countTextTokens(contentText);
|
||||
}
|
||||
allText += this.processContent(message.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count tools definitions tokens if present
|
||||
if (requestBody.tools && Array.isArray(requestBody.tools)) {
|
||||
totalTokens += this.countTextTokens(JSON.stringify(requestBody.tools));
|
||||
allText += JSON.stringify(requestBody.tools);
|
||||
}
|
||||
|
||||
return totalTokens;
|
||||
return this.countTextTokens(allText);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2659,45 +2665,37 @@ async saveCredentialsToFile(filePath, newData) {
|
|||
* @returns {Object} { input_tokens: number }
|
||||
*/
|
||||
countTokens(requestBody) {
|
||||
let totalTokens = 0;
|
||||
let allText = "";
|
||||
let extraTokens = 0;
|
||||
|
||||
// Count system prompt tokens
|
||||
if (requestBody.system) {
|
||||
const systemText = this.getContentText(requestBody.system);
|
||||
totalTokens += this.countTextTokens(systemText);
|
||||
allText += this.processContent(requestBody.system);
|
||||
}
|
||||
|
||||
// Count all messages tokens
|
||||
if (requestBody.messages && Array.isArray(requestBody.messages)) {
|
||||
for (const message of requestBody.messages) {
|
||||
if (message.content) {
|
||||
if (typeof message.content === 'string') {
|
||||
totalTokens += this.countTextTokens(message.content);
|
||||
} else if (Array.isArray(message.content)) {
|
||||
if (Array.isArray(message.content)) {
|
||||
for (const block of message.content) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
totalTokens += this.countTextTokens(block.text);
|
||||
} else if (block.type === 'tool_use') {
|
||||
// Count tool use block tokens
|
||||
totalTokens += this.countTextTokens(block.name || '');
|
||||
totalTokens += this.countTextTokens(JSON.stringify(block.input || {}));
|
||||
} else if (block.type === 'tool_result') {
|
||||
// Count tool result block tokens
|
||||
const resultContent = this.getContentText(block.content);
|
||||
totalTokens += this.countTextTokens(resultContent);
|
||||
} else if (block.type === 'image') {
|
||||
if (block.type === 'image') {
|
||||
// Images have a fixed token cost (approximately 1600 tokens for a typical image)
|
||||
// This is an estimation as actual cost depends on image size
|
||||
totalTokens += 1600;
|
||||
extraTokens += 1600;
|
||||
} else if (block.type === 'document') {
|
||||
// Documents - estimate based on content if available
|
||||
if (block.source?.data) {
|
||||
// For base64 encoded documents, estimate tokens
|
||||
const estimatedChars = block.source.data.length * 0.75; // base64 to bytes ratio
|
||||
totalTokens += Math.ceil(estimatedChars / 4);
|
||||
extraTokens += Math.ceil(estimatedChars / 4);
|
||||
}
|
||||
} else {
|
||||
allText += this.processContent([block]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allText += this.processContent(message.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2705,18 +2703,10 @@ async saveCredentialsToFile(filePath, newData) {
|
|||
|
||||
// Count tools definitions tokens if present
|
||||
if (requestBody.tools && Array.isArray(requestBody.tools)) {
|
||||
for (const tool of requestBody.tools) {
|
||||
// Count tool name and description
|
||||
totalTokens += this.countTextTokens(tool.name || '');
|
||||
totalTokens += this.countTextTokens(tool.description || '');
|
||||
// Count input schema
|
||||
if (tool.input_schema) {
|
||||
totalTokens += this.countTextTokens(JSON.stringify(tool.input_schema));
|
||||
}
|
||||
}
|
||||
allText += JSON.stringify(requestBody.tools);
|
||||
}
|
||||
|
||||
return { input_tokens: totalTokens };
|
||||
return { input_tokens: this.countTextTokens(allText) + extraTokens };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ let providerPoolManager = null;
|
|||
/**
|
||||
* 扫描 configs 目录并自动关联未关联的配置文件到对应的提供商
|
||||
* @param {Object} config - 服务器配置对象
|
||||
* @param {Object} options - 可选参数
|
||||
* @param {boolean} options.onlyCurrentCred - 为 true 时,只自动关联当前凭证
|
||||
* @param {string} options.credPath - 当前凭证的路径(当 onlyCurrentCred 为 true 时必需)
|
||||
* @returns {Promise<Object>} 更新后的 providerPools 对象
|
||||
*/
|
||||
export async function autoLinkProviderConfigs(config) {
|
||||
export async function autoLinkProviderConfigs(config, options = {}) {
|
||||
// 确保 providerPools 对象存在
|
||||
if (!config.providerPools) {
|
||||
config.providerPools = {};
|
||||
|
|
@ -31,43 +34,52 @@ export async function autoLinkProviderConfigs(config) {
|
|||
let totalNewProviders = 0;
|
||||
const allNewProviders = {};
|
||||
|
||||
// 遍历所有提供商映射
|
||||
for (const mapping of PROVIDER_MAPPINGS) {
|
||||
const configsPath = path.join(process.cwd(), 'configs', mapping.dirName);
|
||||
const { providerType, credPathKey, defaultCheckModel, displayName, needsProjectId } = mapping;
|
||||
|
||||
// 确保提供商类型数组存在
|
||||
if (!config.providerPools[providerType]) {
|
||||
config.providerPools[providerType] = [];
|
||||
// 如果只关联当前凭证
|
||||
if (options.onlyCurrentCred && options.credPath) {
|
||||
const result = await linkSingleCredential(config, options.credPath);
|
||||
if (result) {
|
||||
totalNewProviders = 1;
|
||||
allNewProviders[result.displayName] = [result.provider];
|
||||
}
|
||||
|
||||
// 检查目录是否存在
|
||||
if (!fs.existsSync(configsPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取已关联的配置文件路径集合
|
||||
const linkedPaths = new Set();
|
||||
for (const provider of config.providerPools[providerType]) {
|
||||
if (provider[credPathKey]) {
|
||||
// 使用公共方法添加路径的所有变体格式
|
||||
addToUsedPaths(linkedPaths, provider[credPathKey]);
|
||||
} else {
|
||||
// 遍历所有提供商映射
|
||||
for (const mapping of PROVIDER_MAPPINGS) {
|
||||
const configsPath = path.join(process.cwd(), 'configs', mapping.dirName);
|
||||
const { providerType, credPathKey, defaultCheckModel, displayName, needsProjectId } = mapping;
|
||||
|
||||
// 确保提供商类型数组存在
|
||||
if (!config.providerPools[providerType]) {
|
||||
config.providerPools[providerType] = [];
|
||||
}
|
||||
|
||||
// 检查目录是否存在
|
||||
if (!fs.existsSync(configsPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取已关联的配置文件路径集合
|
||||
const linkedPaths = new Set();
|
||||
for (const provider of config.providerPools[providerType]) {
|
||||
if (provider[credPathKey]) {
|
||||
// 使用公共方法添加路径的所有变体格式
|
||||
addToUsedPaths(linkedPaths, provider[credPathKey]);
|
||||
}
|
||||
}
|
||||
|
||||
// 递归扫描目录
|
||||
const newProviders = [];
|
||||
await scanProviderDirectory(configsPath, linkedPaths, newProviders, {
|
||||
credPathKey,
|
||||
defaultCheckModel,
|
||||
needsProjectId
|
||||
});
|
||||
|
||||
// 如果有新的配置文件需要关联
|
||||
if (newProviders.length > 0) {
|
||||
config.providerPools[providerType].push(...newProviders);
|
||||
totalNewProviders += newProviders.length;
|
||||
allNewProviders[displayName] = newProviders;
|
||||
}
|
||||
}
|
||||
|
||||
// 递归扫描目录
|
||||
const newProviders = [];
|
||||
await scanProviderDirectory(configsPath, linkedPaths, newProviders, {
|
||||
credPathKey,
|
||||
defaultCheckModel,
|
||||
needsProjectId
|
||||
});
|
||||
|
||||
// 如果有新的配置文件需要关联
|
||||
if (newProviders.length > 0) {
|
||||
config.providerPools[providerType].push(...newProviders);
|
||||
totalNewProviders += newProviders.length;
|
||||
allNewProviders[displayName] = newProviders;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +116,94 @@ export async function autoLinkProviderConfigs(config) {
|
|||
return config.providerPools;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联单个凭证文件到对应的提供商
|
||||
* @param {Object} config - 服务器配置对象
|
||||
* @param {string} credPath - 凭证文件路径(相对或绝对路径)
|
||||
* @returns {Promise<Object|null>} 返回关联结果或 null
|
||||
*/
|
||||
async function linkSingleCredential(config, credPath) {
|
||||
try {
|
||||
// 规范化路径
|
||||
const absolutePath = path.isAbsolute(credPath) ? credPath : path.join(process.cwd(), credPath);
|
||||
const relativePath = path.relative(process.cwd(), absolutePath);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
logger.warn(`[Auto-Link] Credential file not found: ${relativePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
const ext = path.extname(absolutePath).toLowerCase();
|
||||
if (ext !== '.json') {
|
||||
logger.warn(`[Auto-Link] Only JSON files are supported: ${relativePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据文件路径确定提供商类型
|
||||
let matchedMapping = null;
|
||||
for (const mapping of PROVIDER_MAPPINGS) {
|
||||
const configsPath = path.join(process.cwd(), 'configs', mapping.dirName);
|
||||
// 检查文件是否在该提供商的配置目录下
|
||||
if (absolutePath.startsWith(configsPath)) {
|
||||
matchedMapping = mapping;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedMapping) {
|
||||
logger.warn(`[Auto-Link] Could not determine provider type for: ${relativePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { providerType, credPathKey, defaultCheckModel, displayName, needsProjectId } = matchedMapping;
|
||||
|
||||
// 确保提供商类型数组存在
|
||||
if (!config.providerPools[providerType]) {
|
||||
config.providerPools[providerType] = [];
|
||||
}
|
||||
|
||||
// 检查是否已关联
|
||||
const linkedPaths = new Set();
|
||||
for (const provider of config.providerPools[providerType]) {
|
||||
if (provider[credPathKey]) {
|
||||
addToUsedPaths(linkedPaths, provider[credPathKey]);
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = getFileName(absolutePath);
|
||||
const isLinked = isPathUsed(relativePath, fileName, linkedPaths);
|
||||
|
||||
if (isLinked) {
|
||||
logger.info(`[Auto-Link] Credential already linked: ${relativePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建新的提供商配置
|
||||
const newProvider = createProviderConfig({
|
||||
credPathKey,
|
||||
credPath: formatSystemPath(relativePath),
|
||||
defaultCheckModel,
|
||||
needsProjectId
|
||||
});
|
||||
|
||||
// 添加到配置
|
||||
config.providerPools[providerType].push(newProvider);
|
||||
|
||||
logger.info(`[Auto-Link] Successfully linked credential: ${relativePath} to ${displayName}`);
|
||||
|
||||
return {
|
||||
provider: newProvider,
|
||||
displayName,
|
||||
providerType
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`[Auto-Link] Failed to link credential ${credPath}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归扫描提供商配置目录
|
||||
* @param {string} dirPath - 目录路径
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@
|
|||
<label for="configProviderFilter" data-i18n="upload.providerFilter">提供商类型</label>
|
||||
<select id="configProviderFilter" class="form-control">
|
||||
<option value="" data-i18n="upload.providerFilter.all">全部提供商</option>
|
||||
<option value="claude-kiro-oauth" data-i18n="upload.providerFilter.kiro">Kiro OAuth</option>
|
||||
<option value="gemini-cli-oauth" data-i18n="upload.providerFilter.gemini">Gemini OAuth</option>
|
||||
<option value="openai-qwen-oauth" data-i18n="upload.providerFilter.qwen">Qwen OAuth</option>
|
||||
<option value="gemini-antigravity" data-i18n="upload.providerFilter.antigravity">Antigravity</option>
|
||||
<option value="openai-codex-oauth" data-i18n="upload.providerFilter.codex">Codex OAuth</option>
|
||||
<option value="openai-iflow-oauth" data-i18n="upload.providerFilter.iflow">iFlow OAuth</option>
|
||||
<option value="gemini-cli-oauth" data-i18n="upload.providerFilter.gemini">Gemini CLI OAuth</option>
|
||||
<option value="gemini-antigravity" data-i18n="upload.providerFilter.antigravity">Gemini Antigravity</option>
|
||||
<option value="claude-kiro-oauth" data-i18n="upload.providerFilter.kiro">Claude Kiro OAuth</option>
|
||||
<option value="openai-qwen-oauth" data-i18n="upload.providerFilter.qwen">OpenAI Qwen OAuth</option>
|
||||
<option value="openai-iflow" data-i18n="upload.providerFilter.iflow">OpenAI iFlow</option>
|
||||
<option value="openai-codex-oauth" data-i18n="upload.providerFilter.codex">OpenAI Codex OAuth</option>
|
||||
<option value="other" data-i18n="upload.providerFilter.other">其他/未识别</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue