From 89f9f48576f828ee9d37fbdad3567f28803c80b8 Mon Sep 17 00:00:00 2001 From: KevinLiao Date: Wed, 30 Jul 2025 08:56:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E6=88=90=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7=E9=80=BB=E8=BE=91=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/api.js | 15 +++- src/services/claudeConsoleAccountService.js | 48 +++++++++--- src/services/claudeConsoleRelayService.js | 87 +++++++-------------- src/services/unifiedClaudeScheduler.js | 23 ++++-- 4 files changed, 96 insertions(+), 77 deletions(-) diff --git a/src/routes/api.js b/src/routes/api.js index 74d975b7..a9184db0 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -62,8 +62,9 @@ async function handleMessagesRequest(req, res) { // 生成会话哈希用于sticky会话 const sessionHash = sessionHelper.generateSessionHash(req.body); - // 使用统一调度选择账号 - const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(req.apiKey, sessionHash); + // 使用统一调度选择账号(传递请求的模型) + const requestedModel = req.body.model; + const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(req.apiKey, sessionHash, requestedModel); // 根据账号类型选择对应的转发服务并调用 if (accountType === 'claude-official') { @@ -152,16 +153,22 @@ async function handleMessagesRequest(req, res) { // 生成会话哈希用于sticky会话 const sessionHash = sessionHelper.generateSessionHash(req.body); - // 使用统一调度选择账号 - const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(req.apiKey, sessionHash); + // 使用统一调度选择账号(传递请求的模型) + const requestedModel = req.body.model; + const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(req.apiKey, sessionHash, requestedModel); // 根据账号类型选择对应的转发服务 let response; + logger.debug(`[DEBUG] Request query params: ${JSON.stringify(req.query)}`); + logger.debug(`[DEBUG] Request URL: ${req.url}`); + logger.debug(`[DEBUG] Request path: ${req.path}`); + if (accountType === 'claude-official') { // 官方Claude账号使用原有的转发服务 response = await claudeRelayService.relayRequest(req.body, req.apiKey, req, res, req.headers); } else { // Claude Console账号使用Console转发服务 + logger.debug(`[DEBUG] Calling claudeConsoleRelayService.relayRequest with accountId: ${accountId}`); response = await claudeConsoleRelayService.relayRequest(req.body, req.apiKey, req, res, req.headers, accountId); } diff --git a/src/services/claudeConsoleAccountService.js b/src/services/claudeConsoleAccountService.js index 8f25d82f..afaca6ae 100644 --- a/src/services/claudeConsoleAccountService.js +++ b/src/services/claudeConsoleAccountService.js @@ -45,7 +45,7 @@ class ClaudeConsoleAccountService { platform: 'claude-console', name, description, - apiUrl: this._encryptSensitiveData(apiUrl), + apiUrl: apiUrl, apiKey: this._encryptSensitiveData(apiKey), priority: priority.toString(), supportedModels: JSON.stringify(supportedModels), @@ -64,6 +64,9 @@ class ClaudeConsoleAccountService { }; const client = redis.getClientSafe(); + logger.debug(`[DEBUG] Saving account data to Redis with key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`); + logger.debug(`[DEBUG] Account data to save: ${JSON.stringify(accountData, null, 2)}`); + await client.hset( `${this.ACCOUNT_KEY_PREFIX}${accountId}`, accountData @@ -111,7 +114,7 @@ class ClaudeConsoleAccountService { platform: accountData.platform, name: accountData.name, description: accountData.description, - apiUrl: this._maskApiUrl(this._decryptSensitiveData(accountData.apiUrl)), + apiUrl: accountData.apiUrl, priority: parseInt(accountData.priority) || 50, supportedModels: JSON.parse(accountData.supportedModels || '[]'), userAgent: accountData.userAgent, @@ -138,18 +141,28 @@ class ClaudeConsoleAccountService { // 🔍 获取单个账户(内部使用,包含敏感信息) async getAccount(accountId) { const client = redis.getClientSafe(); + logger.debug(`[DEBUG] Getting account data for ID: ${accountId}`); const accountData = await client.hgetall(`${this.ACCOUNT_KEY_PREFIX}${accountId}`); if (!accountData || Object.keys(accountData).length === 0) { + logger.debug(`[DEBUG] No account data found for ID: ${accountId}`); return null; } - // 解密敏感字段 - accountData.apiUrl = this._decryptSensitiveData(accountData.apiUrl); - accountData.apiKey = this._decryptSensitiveData(accountData.apiKey); + logger.debug(`[DEBUG] Raw account data keys: ${Object.keys(accountData).join(', ')}`); + logger.debug(`[DEBUG] Raw supportedModels value: ${accountData.supportedModels}`); + + // 解密敏感字段(只解密apiKey,apiUrl不加密) + const decryptedKey = this._decryptSensitiveData(accountData.apiKey); + logger.debug(`[DEBUG] URL exists: ${!!accountData.apiUrl}, Decrypted key exists: ${!!decryptedKey}`); + + accountData.apiKey = decryptedKey; // 解析JSON字段 - accountData.supportedModels = JSON.parse(accountData.supportedModels || '[]'); + const parsedModels = JSON.parse(accountData.supportedModels || '[]'); + logger.debug(`[DEBUG] Parsed supportedModels: ${JSON.stringify(parsedModels)}`); + + accountData.supportedModels = parsedModels; accountData.priority = parseInt(accountData.priority) || 50; accountData.rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60; accountData.isActive = accountData.isActive === 'true'; @@ -158,6 +171,8 @@ class ClaudeConsoleAccountService { accountData.proxy = JSON.parse(accountData.proxy); } + logger.debug(`[DEBUG] Final account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`); + return accountData; } @@ -173,12 +188,24 @@ class ClaudeConsoleAccountService { const updatedData = {}; // 处理各个字段的更新 + logger.debug(`[DEBUG] Update request received with fields: ${Object.keys(updates).join(', ')}`); + logger.debug(`[DEBUG] Updates content: ${JSON.stringify(updates, null, 2)}`); + if (updates.name !== undefined) updatedData.name = updates.name; if (updates.description !== undefined) updatedData.description = updates.description; - if (updates.apiUrl !== undefined) updatedData.apiUrl = this._encryptSensitiveData(updates.apiUrl); - if (updates.apiKey !== undefined) updatedData.apiKey = this._encryptSensitiveData(updates.apiKey); + if (updates.apiUrl !== undefined) { + logger.debug(`[DEBUG] Updating apiUrl from frontend: ${updates.apiUrl}`); + updatedData.apiUrl = updates.apiUrl; + } + if (updates.apiKey !== undefined) { + logger.debug(`[DEBUG] Updating apiKey (length: ${updates.apiKey?.length})`); + updatedData.apiKey = this._encryptSensitiveData(updates.apiKey); + } if (updates.priority !== undefined) updatedData.priority = updates.priority.toString(); - if (updates.supportedModels !== undefined) updatedData.supportedModels = JSON.stringify(updates.supportedModels); + if (updates.supportedModels !== undefined) { + logger.debug(`[DEBUG] Updating supportedModels: ${JSON.stringify(updates.supportedModels)}`); + updatedData.supportedModels = JSON.stringify(updates.supportedModels); + } if (updates.userAgent !== undefined) updatedData.userAgent = updates.userAgent; if (updates.rateLimitDuration !== undefined) updatedData.rateLimitDuration = updates.rateLimitDuration.toString(); if (updates.proxy !== undefined) updatedData.proxy = updates.proxy ? JSON.stringify(updates.proxy) : ''; @@ -197,6 +224,9 @@ class ClaudeConsoleAccountService { updatedData.updatedAt = new Date().toISOString(); + logger.debug(`[DEBUG] Final updatedData to save: ${JSON.stringify(updatedData, null, 2)}`); + logger.debug(`[DEBUG] Updating Redis key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`); + await client.hset( `${this.ACCOUNT_KEY_PREFIX}${accountId}`, updatedData diff --git a/src/services/claudeConsoleRelayService.js b/src/services/claudeConsoleRelayService.js index 8754d349..71455151 100644 --- a/src/services/claudeConsoleRelayService.js +++ b/src/services/claudeConsoleRelayService.js @@ -21,28 +21,11 @@ class ClaudeConsoleRelayService { logger.info(`📤 Processing Claude Console API request for key: ${apiKeyData.name || apiKeyData.id}, account: ${account.name} (${accountId})`); logger.debug(`🌐 Account API URL: ${account.apiUrl}`); + logger.debug(`🔍 Account supportedModels: ${JSON.stringify(account.supportedModels)}`); + logger.debug(`🔑 Account has apiKey: ${!!account.apiKey}`); + logger.debug(`📝 Request model: ${requestBody.model}`); - // 检查模型支持 - if (account.supportedModels && account.supportedModels.length > 0) { - const requestedModel = requestBody.model; - if (requestedModel && !account.supportedModels.includes(requestedModel)) { - logger.warn(`🚫 Model not supported by Claude Console account ${account.name}: ${requestedModel}`); - - // 标记账户为blocked - await claudeConsoleAccountService.blockAccount(accountId, `Model ${requestedModel} not supported`); - - return { - statusCode: 400, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - error: { - type: 'invalid_request_error', - message: `Model ${requestedModel} is not supported by this account` - } - }) - }; - } - } + // 模型兼容性检查已经在调度器中完成,这里不需要再检查 // 创建代理agent const proxyAgent = claudeConsoleAccountService._createProxyAgent(account.proxy); @@ -73,6 +56,12 @@ class ClaudeConsoleRelayService { : `${cleanUrl}/v1/messages`; logger.debug(`🎯 Final API endpoint: ${apiEndpoint}`); + logger.debug(`[DEBUG] Options passed to relayRequest: ${JSON.stringify(options)}`); + logger.debug(`[DEBUG] Client headers received: ${JSON.stringify(clientHeaders)}`); + + // 过滤客户端请求头 + const filteredHeaders = this._filterClientHeaders(clientHeaders); + logger.debug(`[DEBUG] Filtered client headers: ${JSON.stringify(filteredHeaders)}`); // 准备请求配置 const requestConfig = { @@ -84,7 +73,7 @@ class ClaudeConsoleRelayService { 'x-api-key': account.apiKey, 'anthropic-version': '2023-06-01', 'User-Agent': account.userAgent || this.defaultUserAgent, - ...this._filterClientHeaders(clientHeaders) + ...filteredHeaders }, httpsAgent: proxyAgent, timeout: config.proxy.timeout || 60000, @@ -92,12 +81,18 @@ class ClaudeConsoleRelayService { validateStatus: () => true // 接受所有状态码 }; + logger.debug(`[DEBUG] Initial headers before beta: ${JSON.stringify(requestConfig.headers, null, 2)}`); + // 添加beta header如果需要 if (options.betaHeader) { + logger.debug(`[DEBUG] Adding beta header: ${options.betaHeader}`); requestConfig.headers['anthropic-beta'] = options.betaHeader; + } else { + logger.debug(`[DEBUG] No beta header to add`); } // 发送请求 + logger.debug(`📤 Sending request to Claude Console API with headers:`, JSON.stringify(requestConfig.headers, null, 2)); const response = await axios(requestConfig); // 移除监听器(请求成功完成) @@ -109,6 +104,10 @@ class ClaudeConsoleRelayService { } logger.debug(`🔗 Claude Console API response: ${response.status}`); + logger.debug(`[DEBUG] Response headers: ${JSON.stringify(response.headers)}`); + logger.debug(`[DEBUG] Response data type: ${typeof response.data}`); + logger.debug(`[DEBUG] Response data length: ${response.data ? (typeof response.data === 'string' ? response.data.length : JSON.stringify(response.data).length) : 0}`); + logger.debug(`[DEBUG] Response data preview: ${typeof response.data === 'string' ? response.data.substring(0, 200) : JSON.stringify(response.data).substring(0, 200)}`); // 检查是否为限流错误 if (response.status === 429) { @@ -125,10 +124,13 @@ class ClaudeConsoleRelayService { // 更新最后使用时间 await this._updateLastUsedTime(accountId); + const responseBody = typeof response.data === 'string' ? response.data : JSON.stringify(response.data); + logger.debug(`[DEBUG] Final response body to return: ${responseBody}`); + return { statusCode: response.status, headers: response.headers, - body: typeof response.data === 'string' ? response.data : JSON.stringify(response.data), + body: responseBody, accountId }; @@ -141,13 +143,7 @@ class ClaudeConsoleRelayService { logger.error('❌ Claude Console Claude relay request failed:', error.message); - // 检查是否是模型不支持导致的错误 - if (error.response && error.response.data && error.response.data.error) { - const errorMessage = error.response.data.error.message || ''; - if (errorMessage.includes('model') && errorMessage.includes('not supported')) { - await claudeConsoleAccountService.blockAccount(accountId, errorMessage); - } - } + // 不再因为模型不支持而block账号 throw error; } @@ -165,28 +161,7 @@ class ClaudeConsoleRelayService { logger.info(`📡 Processing streaming Claude Console API request for key: ${apiKeyData.name || apiKeyData.id}, account: ${account.name} (${accountId})`); logger.debug(`🌐 Account API URL: ${account.apiUrl}`); - // 检查模型支持 - if (account.supportedModels && account.supportedModels.length > 0) { - const requestedModel = requestBody.model; - if (requestedModel && !account.supportedModels.includes(requestedModel)) { - logger.warn(`🚫 Model not supported by Claude Console account ${account.name}: ${requestedModel}`); - - // 标记账户为blocked - await claudeConsoleAccountService.blockAccount(accountId, `Model ${requestedModel} not supported`); - - // 对于流式响应,需要写入错误并结束流 - const errorResponse = JSON.stringify({ - error: { - type: 'invalid_request_error', - message: `Model ${requestedModel} is not supported by this account` - } - }); - - responseStream.writeHead(400, { 'Content-Type': 'application/json' }); - responseStream.end(errorResponse); - return; - } - } + // 模型兼容性检查已经在调度器中完成,这里不需要再检查 // 创建代理agent const proxyAgent = claudeConsoleAccountService._createProxyAgent(account.proxy); @@ -355,13 +330,7 @@ class ClaudeConsoleRelayService { } } - // 检查错误 - if (data.type === 'error' && data.error) { - const errorMessage = data.error.message || ''; - if (errorMessage.includes('model') && errorMessage.includes('not supported')) { - claudeConsoleAccountService.blockAccount(accountId, errorMessage); - } - } + // 不再因为模型不支持而block账号 } catch (e) { // 忽略解析错误 } diff --git a/src/services/unifiedClaudeScheduler.js b/src/services/unifiedClaudeScheduler.js index af92acd3..17bd2f45 100644 --- a/src/services/unifiedClaudeScheduler.js +++ b/src/services/unifiedClaudeScheduler.js @@ -9,7 +9,7 @@ class UnifiedClaudeScheduler { } // 🎯 统一调度Claude账号(官方和Console) - async selectAccountForApiKey(apiKeyData, sessionHash = null) { + async selectAccountForApiKey(apiKeyData, sessionHash = null, requestedModel = null) { try { // 如果API Key绑定了专属账户,优先使用 if (apiKeyData.claudeAccountId) { @@ -41,11 +41,16 @@ class UnifiedClaudeScheduler { } } - // 获取所有可用账户 - const availableAccounts = await this._getAllAvailableAccounts(apiKeyData); + // 获取所有可用账户(传递请求的模型进行过滤) + const availableAccounts = await this._getAllAvailableAccounts(apiKeyData, requestedModel); if (availableAccounts.length === 0) { - throw new Error('No available Claude accounts (neither official nor console)'); + // 提供更详细的错误信息 + if (requestedModel) { + throw new Error(`No available Claude accounts support the requested model: ${requestedModel}`); + } else { + throw new Error('No available Claude accounts (neither official nor console)'); + } } // 按优先级和最后使用时间排序 @@ -73,7 +78,7 @@ class UnifiedClaudeScheduler { } // 📋 获取所有可用账户(合并官方和Console) - async _getAllAvailableAccounts(apiKeyData) { + async _getAllAvailableAccounts(apiKeyData, requestedModel = null) { const availableAccounts = []; // 如果API Key绑定了专属Claude账户,优先返回 @@ -130,6 +135,14 @@ class UnifiedClaudeScheduler { account.status === 'active' && account.accountType === 'shared') { + // 检查模型支持(如果有请求的模型) + if (requestedModel && account.supportedModels && account.supportedModels.length > 0) { + if (!account.supportedModels.includes(requestedModel)) { + logger.info(`🚫 Claude Console account ${account.name} does not support model ${requestedModel}`); + continue; + } + } + // 检查是否被限流 const isRateLimited = await claudeConsoleAccountService.isAccountRateLimited(account.id); if (!isRateLimited) {