diff --git a/src/routes/geminiRoutes.js b/src/routes/geminiRoutes.js index c5fd850c..45e707e1 100644 --- a/src/routes/geminiRoutes.js +++ b/src/routes/geminiRoutes.js @@ -137,7 +137,7 @@ router.post('/messages', authenticateApiKey, async (req, res) => { // 处理速率限制 if (error.status === 429) { - if (apiKeyData && req.account) { + if (req.apiKey && req.account) { await geminiAccountService.setAccountRateLimited(req.account.id, true); } } diff --git a/src/routes/openaiClaudeRoutes.js b/src/routes/openaiClaudeRoutes.js index 4e004e5d..b72c1257 100644 --- a/src/routes/openaiClaudeRoutes.js +++ b/src/routes/openaiClaudeRoutes.js @@ -265,9 +265,13 @@ async function handleChatCompletion(req, res, apiKeyData) { } }, // 流转换器 - (chunk) => { - return openaiToClaude.convertStreamChunk(chunk, req.body.model); - }, + (() => { + // 为每个请求创建独立的会话ID + const sessionId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`; + return (chunk) => { + return openaiToClaude.convertStreamChunk(chunk, req.body.model, sessionId); + }; + })(), { betaHeader: 'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14' } ); diff --git a/src/routes/openaiGeminiRoutes.js b/src/routes/openaiGeminiRoutes.js index 64e10b4f..7a48975c 100644 --- a/src/routes/openaiGeminiRoutes.js +++ b/src/routes/openaiGeminiRoutes.js @@ -48,13 +48,7 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { model = 'gemini-2.0-flash-exp', temperature = 0.7, max_tokens = 4096, - stream = false, - n = 1, - stop = null, - presence_penalty = 0, - frequency_penalty = 0, - logit_bias = null, - user = null + stream = false } = req.body; // 验证必需参数 @@ -159,7 +153,7 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { // 处理速率限制 if (error.status === 429) { - if (apiKeyData && req.account) { + if (req.apiKey && req.account) { await geminiAccountService.setAccountRateLimited(req.account.id, true); } } diff --git a/src/services/claudeCodeHeadersService.js b/src/services/claudeCodeHeadersService.js index 4c00f0ec..5b370723 100644 --- a/src/services/claudeCodeHeadersService.js +++ b/src/services/claudeCodeHeadersService.js @@ -118,7 +118,7 @@ class ClaudeCodeHeadersService { // 获取当前存储的 headers const key = `claude_code_headers:${accountId}`; - const currentData = await redis.get(key); + const currentData = await redis.getClient().get(key); if (currentData) { const current = JSON.parse(currentData); @@ -137,7 +137,7 @@ class ClaudeCodeHeadersService { updatedAt: new Date().toISOString() }; - await redis.setex(key, 86400 * 7, JSON.stringify(data)); // 7天过期 + await redis.getClient().setex(key, 86400 * 7, JSON.stringify(data)); // 7天过期 logger.info(`✅ Stored Claude Code headers for account ${accountId}, version: ${version}`); @@ -152,7 +152,7 @@ class ClaudeCodeHeadersService { async getAccountHeaders(accountId) { try { const key = `claude_code_headers:${accountId}`; - const data = await redis.get(key); + const data = await redis.getClient().get(key); if (data) { const parsed = JSON.parse(data); @@ -176,7 +176,7 @@ class ClaudeCodeHeadersService { async clearAccountHeaders(accountId) { try { const key = `claude_code_headers:${accountId}`; - await redis.del(key); + await redis.getClient().del(key); logger.info(`🗑️ Cleared Claude Code headers for account ${accountId}`); } catch (error) { logger.error(`❌ Failed to clear Claude Code headers for account ${accountId}:`, error); @@ -189,12 +189,12 @@ class ClaudeCodeHeadersService { async getAllAccountHeaders() { try { const pattern = 'claude_code_headers:*'; - const keys = await redis.keys(pattern); + const keys = await redis.getClient().keys(pattern); const results = {}; for (const key of keys) { const accountId = key.replace('claude_code_headers:', ''); - const data = await redis.get(key); + const data = await redis.getClient().get(key); if (data) { results[accountId] = JSON.parse(data); } diff --git a/src/services/openaiToClaude.js b/src/services/openaiToClaude.js index a3e33d23..9e505d6e 100644 --- a/src/services/openaiToClaude.js +++ b/src/services/openaiToClaude.js @@ -99,14 +99,16 @@ class OpenAIToClaudeConverter { * 转换流式响应的单个数据块 * @param {String} chunk - Claude SSE 数据块 * @param {String} requestModel - 原始请求的模型名 + * @param {String} sessionId - 会话ID * @returns {String} OpenAI 格式的 SSE 数据块 */ - convertStreamChunk(chunk, requestModel) { + convertStreamChunk(chunk, requestModel, sessionId) { if (!chunk || chunk.trim() === '') return ''; // 解析 SSE 数据 const lines = chunk.split('\n'); let convertedChunks = []; + let hasMessageStop = false; for (const line of lines) { if (line.startsWith('data: ')) { @@ -118,18 +120,27 @@ class OpenAIToClaudeConverter { try { const claudeEvent = JSON.parse(data); - const openaiChunk = this._convertStreamEvent(claudeEvent, requestModel); + + // 检查是否是 message_stop 事件 + if (claudeEvent.type === 'message_stop') { + hasMessageStop = true; + } + + const openaiChunk = this._convertStreamEvent(claudeEvent, requestModel, sessionId); if (openaiChunk) { convertedChunks.push(`data: ${JSON.stringify(openaiChunk)}\n\n`); } } catch (e) { - // 如果不是 JSON,原样传递 - convertedChunks.push(line + '\n'); + // 跳过无法解析的数据,不传递非JSON格式的行 + continue; } - } else if (line.startsWith('event:') || line === '') { - // 保留事件类型行和空行 - convertedChunks.push(line + '\n'); } + // 忽略 event: 行和空行,OpenAI 格式不包含这些 + } + + // 如果收到 message_stop 事件,添加 [DONE] 标记 + if (hasMessageStop) { + convertedChunks.push('data: [DONE]\n\n'); } return convertedChunks.join(''); @@ -331,10 +342,10 @@ class OpenAIToClaudeConverter { /** * 转换流式事件 */ - _convertStreamEvent(event, requestModel) { + _convertStreamEvent(event, requestModel, sessionId) { const timestamp = Math.floor(Date.now() / 1000); const baseChunk = { - id: `chatcmpl-${this._generateId()}`, + id: sessionId, object: 'chat.completion.chunk', created: timestamp, model: requestModel || 'gpt-4', @@ -346,7 +357,11 @@ class OpenAIToClaudeConverter { }; // 根据事件类型处理 - if (event.type === 'content_block_start' && event.content_block) { + if (event.type === 'message_start') { + // 处理消息开始事件,发送角色信息 + baseChunk.choices[0].delta.role = 'assistant'; + return baseChunk; + } else if (event.type === 'content_block_start' && event.content_block) { if (event.content_block.type === 'text') { baseChunk.choices[0].delta.content = event.content_block.text || ''; } else if (event.content_block.type === 'tool_use') { @@ -381,7 +396,11 @@ class OpenAIToClaudeConverter { baseChunk.usage = this._convertUsage(event.usage); } } else if (event.type === 'message_stop') { - baseChunk.choices[0].finish_reason = 'stop'; + // message_stop 事件不需要返回 chunk,[DONE] 标记会在 convertStreamChunk 中添加 + return null; + } else { + // 忽略其他类型的事件 + return null; } return baseChunk; diff --git a/src/services/tokenRefreshService.js b/src/services/tokenRefreshService.js index d51151ee..f825062e 100644 --- a/src/services/tokenRefreshService.js +++ b/src/services/tokenRefreshService.js @@ -1,9 +1,6 @@ const redis = require('../models/redis'); const logger = require('../utils/logger'); const { v4: uuidv4 } = require('uuid'); -const { - logRefreshSkipped -} = require('../utils/tokenRefreshLogger'); /** * Token 刷新锁服务 diff --git a/src/utils/tokenRefreshLogger.js b/src/utils/tokenRefreshLogger.js index 17f0a7ed..ad86a090 100644 --- a/src/utils/tokenRefreshLogger.js +++ b/src/utils/tokenRefreshLogger.js @@ -1,7 +1,7 @@ const winston = require('winston'); const path = require('path'); const fs = require('fs'); -const { maskToken, formatTokenRefreshLog } = require('./tokenMask'); +const { maskToken } = require('./tokenMask'); // 确保日志目录存在 const logDir = path.join(process.cwd(), 'logs');