From d8e833ef1a71836ab4ada3d9bdefd9bfa531a4fd Mon Sep 17 00:00:00 2001 From: iaineng Date: Thu, 4 Sep 2025 23:26:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=B2=98=E6=80=A7=E6=9C=BA=E5=88=B6=E4=B8=8BPro=E8=B4=A6?= =?UTF-8?q?=E6=88=B7=E8=A2=AB=E9=94=99=E8=AF=AF=E8=B0=83=E5=BA=A6=E7=94=A8?= =?UTF-8?q?=E4=BA=8EOpus=E8=AF=B7=E6=B1=82=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 _isAccountAvailable 方法中添加了模型兼容性检查,避免Pro账户被用于Opus请求 - 创建 _isModelSupportedByAccount 统一方法来处理模型兼容性验证 - 支持Claude OAuth账户的订阅类型检查(Pro/Free/Max) - 支持Claude Console账户的supportedModels配置检查 --- src/services/unifiedClaudeScheduler.js | 180 +++++++++++++++---------- 1 file changed, 111 insertions(+), 69 deletions(-) diff --git a/src/services/unifiedClaudeScheduler.js b/src/services/unifiedClaudeScheduler.js index 34e61a81..e144e8c6 100644 --- a/src/services/unifiedClaudeScheduler.js +++ b/src/services/unifiedClaudeScheduler.js @@ -20,6 +20,77 @@ class UnifiedClaudeScheduler { return schedulable !== false && schedulable !== 'false' } + // 🔍 检查账户是否支持请求的模型 + _isModelSupportedByAccount(account, accountType, requestedModel, context = '') { + if (!requestedModel) { + return true // 没有指定模型时,默认支持 + } + + // Claude OAuth 账户的 Opus 模型检查 + if (accountType === 'claude-official') { + if (requestedModel.toLowerCase().includes('opus')) { + if (account.subscriptionInfo) { + try { + const info = + typeof account.subscriptionInfo === 'string' + ? JSON.parse(account.subscriptionInfo) + : account.subscriptionInfo + + // Pro 和 Free 账号不支持 Opus + if (info.hasClaudePro === true && info.hasClaudeMax !== true) { + logger.info( + `🚫 Claude account ${account.name} (Pro) does not support Opus model${context ? ` ${context}` : ''}` + ) + return false + } + if (info.accountType === 'claude_pro' || info.accountType === 'claude_free') { + logger.info( + `🚫 Claude account ${account.name} (${info.accountType}) does not support Opus model${context ? ` ${context}` : ''}` + ) + return false + } + } catch (e) { + // 解析失败,假设为旧数据,默认支持(兼容旧数据为 Max) + logger.debug( + `Account ${account.name} has invalid subscriptionInfo${context ? ` ${context}` : ''}, assuming Max` + ) + } + } + // 没有订阅信息的账号,默认当作支持(兼容旧数据) + } + } + + // Claude Console 账户的模型支持检查 + if (accountType === 'claude-console' && account.supportedModels) { + // 兼容旧格式(数组)和新格式(对象) + if (Array.isArray(account.supportedModels)) { + // 旧格式:数组 + if ( + account.supportedModels.length > 0 && + !account.supportedModels.includes(requestedModel) + ) { + logger.info( + `🚫 Claude Console account ${account.name} does not support model ${requestedModel}${context ? ` ${context}` : ''}` + ) + return false + } + } else if (typeof account.supportedModels === 'object') { + // 新格式:映射表 + if ( + Object.keys(account.supportedModels).length > 0 && + !claudeConsoleAccountService.isModelSupported(account.supportedModels, requestedModel) + ) { + logger.info( + `🚫 Claude Console account ${account.name} does not support model ${requestedModel}${context ? ` ${context}` : ''}` + ) + return false + } + } + } + + return true + } + // 🎯 统一调度Claude账号(官方和Console) async selectAccountForApiKey(apiKeyData, sessionHash = null, requestedModel = null) { try { @@ -102,7 +173,8 @@ class UnifiedClaudeScheduler { // 验证映射的账户是否仍然可用 const isAvailable = await this._isAccountAvailable( mappedAccount.accountId, - mappedAccount.accountType + mappedAccount.accountType, + requestedModel ) if (isAvailable) { logger.info( @@ -269,33 +341,9 @@ class UnifiedClaudeScheduler { ) { // 检查是否可调度 - // 检查模型支持(如果请求的是 Opus 模型) - if (requestedModel && requestedModel.toLowerCase().includes('opus')) { - // 检查账号的订阅信息 - if (account.subscriptionInfo) { - try { - const info = - typeof account.subscriptionInfo === 'string' - ? JSON.parse(account.subscriptionInfo) - : account.subscriptionInfo - - // Pro 和 Free 账号不支持 Opus - if (info.hasClaudePro === true && info.hasClaudeMax !== true) { - logger.info(`🚫 Claude account ${account.name} (Pro) does not support Opus model`) - continue // Claude Pro 不支持 Opus - } - if (info.accountType === 'claude_pro' || info.accountType === 'claude_free') { - logger.info( - `🚫 Claude account ${account.name} (${info.accountType}) does not support Opus model` - ) - continue // 明确标记为 Pro 或 Free 的账号不支持 - } - } catch (e) { - // 解析失败,假设为旧数据,默认支持(兼容旧数据为 Max) - logger.debug(`Account ${account.name} has invalid subscriptionInfo, assuming Max`) - } - } - // 没有订阅信息的账号,默认当作支持(兼容旧数据) + // 检查模型支持 + if (!this._isModelSupportedByAccount(account, 'claude-official', requestedModel)) { + continue } // 检查是否被限流 @@ -330,32 +378,9 @@ class UnifiedClaudeScheduler { ) { // 检查是否可调度 - // 检查模型支持(如果有请求的模型) - if (requestedModel && account.supportedModels) { - // 兼容旧格式(数组)和新格式(对象) - if (Array.isArray(account.supportedModels)) { - // 旧格式:数组 - if ( - account.supportedModels.length > 0 && - !account.supportedModels.includes(requestedModel) - ) { - logger.info( - `🚫 Claude Console account ${account.name} does not support model ${requestedModel}` - ) - continue - } - } else if (typeof account.supportedModels === 'object') { - // 新格式:映射表 - if ( - Object.keys(account.supportedModels).length > 0 && - !claudeConsoleAccountService.isModelSupported(account.supportedModels, requestedModel) - ) { - logger.info( - `🚫 Claude Console account ${account.name} does not support model ${requestedModel}` - ) - continue - } - } + // 检查模型支持 + if (!this._isModelSupportedByAccount(account, 'claude-console', requestedModel)) { + continue } // 检查是否被限流 @@ -439,7 +464,7 @@ class UnifiedClaudeScheduler { } // 🔍 检查账户是否可用 - async _isAccountAvailable(accountId, accountType) { + async _isAccountAvailable(accountId, accountType, requestedModel = null) { try { if (accountType === 'claude-official') { const account = await redis.getClaudeAccount(accountId) @@ -456,6 +481,19 @@ class UnifiedClaudeScheduler { logger.info(`🚫 Account ${accountId} is not schedulable`) return false } + + // 检查模型兼容性 + if ( + !this._isModelSupportedByAccount( + account, + 'claude-official', + requestedModel, + 'in session check' + ) + ) { + return false + } + return !(await claudeAccountService.isAccountRateLimited(accountId)) } else if (accountType === 'claude-console') { const account = await claudeConsoleAccountService.getAccount(accountId) @@ -475,6 +513,19 @@ class UnifiedClaudeScheduler { logger.info(`🚫 Claude Console account ${accountId} is not schedulable`) return false } + + // 检查模型支持 + if ( + !this._isModelSupportedByAccount( + account, + 'claude-console', + requestedModel, + 'in session check' + ) + ) { + return false + } + // 检查是否被限流 if (await claudeConsoleAccountService.isAccountRateLimited(accountId)) { return false @@ -693,7 +744,8 @@ class UnifiedClaudeScheduler { if (memberIds.includes(mappedAccount.accountId)) { const isAvailable = await this._isAccountAvailable( mappedAccount.accountId, - mappedAccount.accountType + mappedAccount.accountType, + requestedModel ) if (isAvailable) { logger.info( @@ -756,19 +808,9 @@ class UnifiedClaudeScheduler { : account.status === 'active' if (isActive && status && this._isSchedulable(account.schedulable)) { - // 检查模型支持(Console账户) - if ( - accountType === 'claude-console' && - requestedModel && - account.supportedModels && - account.supportedModels.length > 0 - ) { - if (!account.supportedModels.includes(requestedModel)) { - logger.info( - `🚫 Account ${account.name} in group does not support model ${requestedModel}` - ) - continue - } + // 检查模型支持 + if (!this._isModelSupportedByAccount(account, accountType, requestedModel, 'in group')) { + continue } // 检查是否被限流 From 8c158d82fae4b25614e6623c0b3e9a0cea198caa Mon Sep 17 00:00:00 2001 From: iaineng Date: Fri, 5 Sep 2025 12:18:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=88=9B=E5=BB=BA?= =?UTF-8?q?Claude=E8=B4=A6=E6=88=B7=E6=97=B6=E7=BC=BA=E5=A4=B1=E7=9A=84use?= =?UTF-8?q?UnifiedUserAgent=E5=AD=97=E6=AE=B5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 /admin/claude-accounts POST 路由中添加 useUnifiedUserAgent 参数解构 - 将 useUnifiedUserAgent 参数传递给 claudeAccountService.createAccount() 方法 - 保持与前端 AccountForm.vue 和服务层 claudeAccountService.js 的一致性 --- src/routes/admin.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index f29a43b9..6cdc3bb8 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1902,7 +1902,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => { priority, groupId, groupIds, - autoStopOnWarning + autoStopOnWarning, + useUnifiedUserAgent } = req.body if (!name) { @@ -1942,7 +1943,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => { accountType: accountType || 'shared', // 默认为共享类型 platform, priority: priority || 50, // 默认优先级为50 - autoStopOnWarning: autoStopOnWarning === true // 默认为false + autoStopOnWarning: autoStopOnWarning === true, // 默认为false + useUnifiedUserAgent: useUnifiedUserAgent === true // 默认为false }) // 如果是分组类型,将账户添加到分组