diff --git a/src/routes/openaiClaudeRoutes.js b/src/routes/openaiClaudeRoutes.js index 9b570e3c..94c807b2 100644 --- a/src/routes/openaiClaudeRoutes.js +++ b/src/routes/openaiClaudeRoutes.js @@ -221,12 +221,12 @@ async function handleChatCompletion(req, res, apiKeyData) { } }); - // 使用转换后的响应流 + // 使用转换后的响应流 (使用 OAuth-only beta header,不传递客户端 headers) await claudeRelayService.relayStreamRequestWithUsageCapture( claudeRequest, apiKeyData, res, - req.headers, + {}, (usage) => { // 记录使用统计 if (usage && usage.input_tokens !== undefined && usage.output_tokens !== undefined) { @@ -251,20 +251,22 @@ async function handleChatCompletion(req, res, apiKeyData) { // 流转换器 (chunk) => { return openaiToClaude.convertStreamChunk(chunk, req.body.model); - } + }, + { betaHeader: 'oauth-2025-04-20' } ); } else { // 非流式请求 logger.info(`📄 Processing OpenAI non-stream request for model: ${req.body.model}`); - // 发送请求到 Claude + // 发送请求到 Claude (使用 OAuth-only beta header,不传递客户端 headers) const claudeResponse = await claudeRelayService.relayRequest( claudeRequest, apiKeyData, req, res, - req.headers + {}, + { betaHeader: 'oauth-2025-04-20' } ); // 解析 Claude 响应 diff --git a/src/services/claudeRelayService.js b/src/services/claudeRelayService.js index 75cf7d79..6be1a20f 100644 --- a/src/services/claudeRelayService.js +++ b/src/services/claudeRelayService.js @@ -18,7 +18,7 @@ class ClaudeRelayService { } // 🚀 转发请求到Claude API - async relayRequest(requestBody, apiKeyData, clientRequest, clientResponse, clientHeaders) { + async relayRequest(requestBody, apiKeyData, clientRequest, clientResponse, clientHeaders, options = {}) { let upstreamRequest = null; try { @@ -89,7 +89,8 @@ class ClaudeRelayService { accessToken, proxyAgent, clientHeaders, - (req) => { upstreamRequest = req; } + (req) => { upstreamRequest = req; }, + options ); // 移除监听器(请求成功完成) @@ -325,7 +326,7 @@ class ClaudeRelayService { } // 🔗 发送请求到Claude API - async _makeClaudeRequest(body, accessToken, proxyAgent, clientHeaders, onRequest) { + async _makeClaudeRequest(body, accessToken, proxyAgent, clientHeaders, onRequest, requestOptions = {}) { return new Promise((resolve, reject) => { const url = new URL(this.claudeApiUrl); @@ -352,8 +353,10 @@ class ClaudeRelayService { options.headers['User-Agent'] = 'claude-cli/1.0.53 (external, cli)'; } - if (this.betaHeader) { - options.headers['anthropic-beta'] = this.betaHeader; + // 使用自定义的 betaHeader 或默认值 + const betaHeader = requestOptions?.betaHeader !== undefined ? requestOptions.betaHeader : this.betaHeader; + if (betaHeader) { + options.headers['anthropic-beta'] = betaHeader; } const req = https.request(options, (res) => { @@ -445,7 +448,7 @@ class ClaudeRelayService { } // 🌊 处理流式响应(带usage数据捕获) - async relayStreamRequestWithUsageCapture(requestBody, apiKeyData, responseStream, clientHeaders, usageCallback, streamTransformer = null) { + async relayStreamRequestWithUsageCapture(requestBody, apiKeyData, responseStream, clientHeaders, usageCallback, streamTransformer = null, options = {}) { try { // 调试日志:查看API Key数据(流式请求) logger.info('🔍 [Stream] API Key data received:', { @@ -495,7 +498,7 @@ class ClaudeRelayService { const proxyAgent = await this._getProxyAgent(accountId); // 发送流式请求并捕获usage数据 - return await this._makeClaudeStreamRequestWithUsageCapture(processedBody, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash, streamTransformer); + return await this._makeClaudeStreamRequestWithUsageCapture(processedBody, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash, streamTransformer, options); } catch (error) { logger.error('❌ Claude stream relay with usage capture failed:', error); throw error; @@ -503,7 +506,7 @@ class ClaudeRelayService { } // 🌊 发送流式请求到Claude API(带usage数据捕获) - async _makeClaudeStreamRequestWithUsageCapture(body, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash, streamTransformer = null) { + async _makeClaudeStreamRequestWithUsageCapture(body, accessToken, proxyAgent, clientHeaders, responseStream, usageCallback, accountId, sessionHash, streamTransformer = null, requestOptions = {}) { return new Promise((resolve, reject) => { const url = new URL(this.claudeApiUrl); @@ -530,8 +533,10 @@ class ClaudeRelayService { options.headers['User-Agent'] = 'claude-cli/1.0.53 (external, cli)'; } - if (this.betaHeader) { - options.headers['anthropic-beta'] = this.betaHeader; + // 使用自定义的 betaHeader 或默认值 + const betaHeader = requestOptions?.betaHeader !== undefined ? requestOptions.betaHeader : this.betaHeader; + if (betaHeader) { + options.headers['anthropic-beta'] = betaHeader; } const req = https.request(options, (res) => { @@ -736,7 +741,7 @@ class ClaudeRelayService { } // 🌊 发送流式请求到Claude API - async _makeClaudeStreamRequest(body, accessToken, proxyAgent, clientHeaders, responseStream) { + async _makeClaudeStreamRequest(body, accessToken, proxyAgent, clientHeaders, responseStream, requestOptions = {}) { return new Promise((resolve, reject) => { const url = new URL(this.claudeApiUrl); @@ -763,8 +768,10 @@ class ClaudeRelayService { options.headers['User-Agent'] = 'claude-cli/1.0.53 (external, cli)'; } - if (this.betaHeader) { - options.headers['anthropic-beta'] = this.betaHeader; + // 使用自定义的 betaHeader 或默认值 + const betaHeader = requestOptions?.betaHeader !== undefined ? requestOptions.betaHeader : this.betaHeader; + if (betaHeader) { + options.headers['anthropic-beta'] = betaHeader; } const req = https.request(options, (res) => { diff --git a/src/services/openaiToClaude.js b/src/services/openaiToClaude.js index ded54774..e296acf8 100644 --- a/src/services/openaiToClaude.js +++ b/src/services/openaiToClaude.js @@ -358,10 +358,29 @@ class OpenAIToClaudeConverter { 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') { + // 开始工具调用 + baseChunk.choices[0].delta.tool_calls = [{ + index: event.index || 0, + id: event.content_block.id, + type: 'function', + function: { + name: event.content_block.name, + arguments: '' + } + }]; } } else if (event.type === 'content_block_delta' && event.delta) { if (event.delta.type === 'text_delta') { baseChunk.choices[0].delta.content = event.delta.text || ''; + } else if (event.delta.type === 'input_json_delta') { + // 工具调用参数的增量更新 + baseChunk.choices[0].delta.tool_calls = [{ + index: event.index || 0, + function: { + arguments: event.delta.partial_json || '' + } + }]; } } else if (event.type === 'message_delta' && event.delta) { if (event.delta.stop_reason) {