diff --git a/src/routes/openaiGeminiRoutes.js b/src/routes/openaiGeminiRoutes.js index 7a48975c..7599b98c 100644 --- a/src/routes/openaiGeminiRoutes.js +++ b/src/routes/openaiGeminiRoutes.js @@ -27,6 +27,7 @@ function checkPermissions(apiKeyData, requiredPermission = 'gemini') { router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { const startTime = Date.now(); let abortController = null; + let account = null; // Declare account outside try block for error handling try { const apiKeyData = req.apiKey; @@ -41,16 +42,42 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { } }); } + // 处理请求体结构 - 支持多种格式 + let requestBody = req.body; + + // 如果请求体被包装在 body 字段中,解包它 + if (req.body.body && typeof req.body.body === 'object') { + requestBody = req.body.body; + } + + // 从 URL 路径中提取模型信息(如果存在) + let urlModel = null; + const urlPath = req.body?.config?.url || req.originalUrl || req.url; + const modelMatch = urlPath.match(/\/([^\/]+):(?:stream)?[Gg]enerateContent/); + if (modelMatch) { + urlModel = modelMatch[1]; + logger.debug(`Extracted model from URL: ${urlModel}`); + } // 提取请求参数 const { - messages, - model = 'gemini-2.0-flash-exp', + messages: requestMessages, + contents, + model: bodyModel = 'gemini-2.0-flash-exp', temperature = 0.7, max_tokens = 4096, stream = false - } = req.body; - + } = requestBody; + + // 优先使用 URL 中的模型,其次是请求体中的模型 + const model = urlModel || bodyModel; + + // 支持两种格式: OpenAI 的 messages 或 Gemini 的 contents + let messages = requestMessages; + if (contents && Array.isArray(contents)) { + messages = contents; + } + // 验证必需参数 if (!messages || !Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ @@ -79,7 +106,7 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { const sessionHash = generateSessionHash(req); // 选择可用的 Gemini 账户 - const account = await geminiAccountService.selectAvailableAccount( + account = await geminiAccountService.selectAvailableAccount( apiKeyData.id, sessionHash ); @@ -153,8 +180,8 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { // 处理速率限制 if (error.status === 429) { - if (req.apiKey && req.account) { - await geminiAccountService.setAccountRateLimited(req.account.id, true); + if (req.apiKey && account) { + await geminiAccountService.setAccountRateLimited(account.id, true); } } diff --git a/src/utils/logger.js b/src/utils/logger.js index e905e90a..87765dad 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -5,6 +5,49 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); +// 安全的 JSON 序列化函数,处理循环引用 +const safeStringify = (obj, maxDepth = 3) => { + const seen = new WeakSet(); + + const replacer = (key, value, depth = 0) => { + if (depth > maxDepth) return '[Max Depth Reached]'; + + if (value !== null && typeof value === 'object') { + if (seen.has(value)) { + return '[Circular Reference]'; + } + seen.add(value); + + // 过滤掉常见的循环引用对象 + if (value.constructor) { + const constructorName = value.constructor.name; + if (['Socket', 'TLSSocket', 'HTTPParser', 'IncomingMessage', 'ServerResponse'].includes(constructorName)) { + return `[${constructorName} Object]`; + } + } + + // 递归处理对象属性 + if (Array.isArray(value)) { + return value.map((item, index) => replacer(index, item, depth + 1)); + } else { + const result = {}; + for (const [k, v] of Object.entries(value)) { + result[k] = replacer(k, v, depth + 1); + } + return result; + } + } + + return value; + }; + + try { + return JSON.stringify(replacer('', obj)); + } catch (error) { + return JSON.stringify({ error: 'Failed to serialize object', message: error.message }); + } +}; + // 📝 增强的日志格式 const createLogFormat = (colorize = false) => { const formats = [ @@ -31,7 +74,7 @@ const createLogFormat = (colorize = false) => { // 添加元数据 if (metadata && Object.keys(metadata).length > 0) { - logMessage += ` | ${JSON.stringify(metadata)}`; + logMessage += ` | ${safeStringify(metadata)}`; } // 添加其他属性 @@ -42,7 +85,7 @@ const createLogFormat = (colorize = false) => { delete additionalData.stack; if (Object.keys(additionalData).length > 0) { - logMessage += ` | ${JSON.stringify(additionalData)}`; + logMessage += ` | ${safeStringify(additionalData)}`; } return stack ? `${logMessage}\n${stack}` : logMessage;