From f33cf38b4255cc674d027b776e9424302c661afa Mon Sep 17 00:00:00 2001 From: hex2077 Date: Wed, 6 Aug 2025 23:01:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(api-server):=20=E4=BF=AE=E5=A4=8DOPTIONS?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E7=9A=84CORS=E5=A4=B4=E9=83=A8=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(convert): 添加OpenAI流式响应停止块生成函数 refactor(common): 优化流式请求处理逻辑,支持更多协议转换 --- src/api-server.js | 12 +++++++++--- src/common.js | 11 ++++++++--- src/convert.js | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/api-server.js b/src/api-server.js index e17276a..40d3b3e 100644 --- a/src/api-server.js +++ b/src/api-server.js @@ -411,9 +411,15 @@ function createRequestHandler(config) { const apiService = await getApiService(currentConfig); const method = req.method; if (method === 'OPTIONS') { - res.writeHead(200, { 'Content-Type': 'application/json' }); - console.log("OPTIONS REQUEST SUCCESS"); - return res.end("OPTIONS REQUEST SUCCESS"); + // 设置 CORS 头部,允许所有来源和方法 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-goog-api-key'); + + // OPTIONS 请求通常返回 204 No Content + res.writeHead(204); + res.end(); + return; } // Health check endpoint - no authentication required diff --git a/src/common.js b/src/common.js index fa16303..6182e95 100644 --- a/src/common.js +++ b/src/common.js @@ -2,7 +2,7 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import * as http from 'http'; // Add http for IncomingMessage and ServerResponse types import { ApiServiceAdapter } from './adapter.js'; // Import ApiServiceAdapter -import { convertData } from './convert.js'; +import { convertData, getOpenAIStreamChunkStop } from './convert.js'; import { ProviderStrategyFactory } from './provider-strategies.js'; export const API_ACTIONS = { @@ -207,7 +207,8 @@ export async function handleStreamRequest(res, service, model, requestBody, from // The service returns a stream in its native format (toProvider). const nativeStream = await service.generateContentStream(model, requestBody); const needsConversion = getProtocolPrefix(fromProvider) !== getProtocolPrefix(toProvider); - const isClaude = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.CLAUDE; + const addEvent = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.CLAUDE; + const openStop = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.OPENAI; try { for await (const nativeChunk of nativeStream) { @@ -225,7 +226,7 @@ export async function handleStreamRequest(res, service, model, requestBody, from continue; } - if (isClaude) { + if (addEvent) { res.write(`event: ${chunkToSend.type}\n`); // console.log(`event: ${chunkToSend.type}\n`); } @@ -233,6 +234,10 @@ export async function handleStreamRequest(res, service, model, requestBody, from res.write(`data: ${JSON.stringify(chunkToSend)}\n\n`); // console.log(`data: ${JSON.stringify(chunkToSend)}\n`); } + if (openStop && needsConversion) { + res.write(`data: ${JSON.stringify(getOpenAIStreamChunkStop(model))}\n\n`); + // console.log(`data: ${JSON.stringify(getOpenAIStreamChunkStop(model))}\n`); + } } catch (error) { console.error('\n[Server] Error during stream processing:', error.stack); diff --git a/src/convert.js b/src/convert.js index b08508d..fc8c4f3 100644 --- a/src/convert.js +++ b/src/convert.js @@ -407,10 +407,18 @@ export function toOpenAIStreamChunkFromClaude(claudeChunk, model) { object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: model, + system_fingerprint: "", choices: [{ index: 0, - delta: { content: claudeChunk }, - finish_reason: null, + delta: { + content: claudeChunk, + reasoning_content: "" + }, + finish_reason: !claudeChunk ? 'stop' : null, + message: { + content: claudeChunk, + reasoning_content: "" + } }], usage:{ prompt_tokens: 0, @@ -420,6 +428,32 @@ export function toOpenAIStreamChunkFromClaude(claudeChunk, model) { }; } +export function getOpenAIStreamChunkStop(model) { + return { + id: `chatcmpl-${uuidv4()}`, // uuidv4 needs to be imported or handled + object: "chat.completion.chunk", + created: Math.floor(Date.now() / 1000), + model: model, + system_fingerprint: "", + choices: [{ + index: 0, + delta: { + content: "", + reasoning_content: "" + }, + finish_reason: 'stop', + message: { + content: "", + reasoning_content: "" + } + }], + usage:{ + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }; +}