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, + }, + }; +}