feat: 增强模型支持和修复流处理问题

- 添加新的Qwen模型(coder-model, vision-model)到提供者列表
- 修复OpenAI Responses流结束事件处理,避免下游类型校验错误
- 更新Qwen API端点地址和版本号
- 重构Codex转换器,分离OpenAI和OpenAI Responses的转换逻辑
- 优化工具调用处理,支持嵌套function结构
- 移除健康检查功能,简化API管理初始化
- 修复消息角色转换(developer→assistant)和类型标记
This commit is contained in:
hex2077 2026-02-13 20:47:47 +08:00
parent ce8e8ad855
commit 6ee7e78c90
9 changed files with 338 additions and 362 deletions

View file

@ -29,11 +29,6 @@ export class CodexConverter extends BaseConverter {
* 转换请求
*/
convertRequest(data, targetProtocol) {
if (targetProtocol === MODEL_PROTOCOL_PREFIX.CODEX) {
return this.toCodexRequest(data);
} else if (targetProtocol === MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES) {
return this.toOpenAIResponsesRequest(data);
}
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
@ -57,14 +52,6 @@ export class CodexConverter extends BaseConverter {
}
}
/**
* OpenAI Responses Codex 请求转换 (或处理已经转换好的数据)
*/
toOpenAIResponsesRequest(data) {
// 如果输入已经是 OpenAI Responses 格式,将其转换为 Codex 格式
return this.toCodexRequest(data);
}
/**
* 转换流式响应块
*/
@ -78,15 +65,85 @@ export class CodexConverter extends BaseConverter {
return this.toGeminiStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.CODEX:
return chunk; // Codex to Codex
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换模型列表
*/
convertModelList(data, targetProtocol) {
return data;
}
/**
* OpenAI Responses Codex 请求转换
*/
toOpenAIResponsesToCodexRequest(responsesRequest) {
let codexRequest = { ...responsesRequest };
// 处理 input 字段,如果它是字符串,则转换为消息数组
if (codexRequest.input && typeof codexRequest.input === 'string') {
const inputText = codexRequest.input;
codexRequest.input = [{
type: "message",
role: "user",
content: [{
type: "input_text",
text: inputText
}]
}];
}
// 设置Codex特定的字段
codexRequest.stream = true;
codexRequest.store = false;
codexRequest.parallel_tool_calls = true;
codexRequest.include = ['reasoning.encrypted_content'];
// 删除Codex不支持的字段
delete codexRequest.max_output_tokens;
delete codexRequest.max_completion_tokens;
delete codexRequest.temperature;
delete codexRequest.top_p;
delete codexRequest.service_tier;
delete codexRequest.user;
delete codexRequest.reasoning;
// 添加 reasoning 配置
codexRequest.reasoning = {
"effort": "medium",
"summary": "auto"
};
// 确保 input 数组中的每个项都有 type: "message",并将系统角色转换为开发者角色
if (codexRequest.input && Array.isArray(codexRequest.input)) {
codexRequest.input = codexRequest.input.map(item => {
// 如果没有 type 或者 type 不是 message则添加 type: "message"
if (!item.type || item.type !== 'message') {
item = { type: "message", ...item };
}
// 将系统角色转换为开发者角色
if (item.role === 'system') {
item = { ...item, role: 'developer' };
}
return item;
});
}
return codexRequest;
}
/**
* OpenAI Codex 请求转换
*/
toCodexRequest(data) {
toOpenAIRequestToCodexRequest(data) {
// 构建工具名称映射
this.buildToolNameMap(data.tools || []);
@ -536,6 +593,210 @@ export class CodexConverter extends BaseConverter {
return openaiResponse;
}
/**
* Codex OpenAI Responses 响应转换
*/
toOpenAIResponsesResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const unixTimestamp = response.created_at || Math.floor(Date.now() / 1000);
const output = [];
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
output.push({
id: `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
status: "completed",
content: [{
type: "reasoning",
text: reasoningText
}]
});
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
output.push({
id: `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
status: "completed",
content: [{
type: "output_text",
text: contentText,
annotations: []
}]
});
}
} else if (item.type === 'function_call') {
output.push({
id: item.call_id || `call_${uuidv4().replace(/-/g, '')}`,
type: "function_call",
name: this.getOriginalToolName(item.name),
arguments: typeof item.arguments === 'string' ? item.arguments : JSON.stringify(item.arguments),
status: "completed"
});
}
}
}
return {
id: response.id || `resp_${uuidv4().replace(/-/g, '')}`,
object: "response",
created_at: unixTimestamp,
model: response.model || model,
status: "completed",
output: output,
incomplete_details: response.incomplete_details || null,
usage: {
input_tokens: response.usage?.input_tokens || 0,
output_tokens: response.usage?.output_tokens || 0,
total_tokens: response.usage?.total_tokens || 0,
output_tokens_details: {
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0
}
}
};
}
/**
* Codex Gemini 响应转换
*/
toGeminiResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const parts = [];
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
parts.push({ text: reasoningText, thought: true });
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
parts.push({ text: contentText });
}
} else if (item.type === 'function_call') {
parts.push({
functionCall: {
name: this.getOriginalToolName(item.name),
args: typeof item.arguments === 'string' ? JSON.parse(item.arguments) : item.arguments
}
});
}
}
}
return {
candidates: [{
content: {
role: "model",
parts: parts
},
finishReason: "STOP"
}],
usageMetadata: {
promptTokenCount: response.usage?.input_tokens || 0,
candidatesTokenCount: response.usage?.output_tokens || 0,
totalTokenCount: response.usage?.total_tokens || 0
},
modelVersion: response.model || model,
responseId: response.id
};
}
/**
* Codex Claude 响应转换
*/
toClaudeResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const content = [];
let stopReason = "end_turn";
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
content.push({ type: "thinking", thinking: reasoningText });
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
content.push({ type: "text", text: contentText });
}
} else if (item.type === 'function_call') {
stopReason = "tool_use";
content.push({
type: "tool_use",
id: item.call_id || `call_${uuidv4().replace(/-/g, '')}`,
name: this.getOriginalToolName(item.name),
input: typeof item.arguments === 'string' ? JSON.parse(item.arguments) : item.arguments
});
}
}
}
return {
id: response.id || `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
model: response.model || model,
content: content,
stop_reason: stopReason,
usage: {
input_tokens: response.usage?.input_tokens || 0,
output_tokens: response.usage?.output_tokens || 0
}
};
}
/**
* Codex OpenAI 流式响应块转换
*/
@ -699,222 +960,14 @@ export class CodexConverter extends BaseConverter {
return null;
}
/**
* 转换模型列表
*/
convertModelList(data, targetProtocol) {
return data;
}
/**
* Codex OpenAI Responses 响应转换
*/
toOpenAIResponsesResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const unixTimestamp = response.created_at || Math.floor(Date.now() / 1000);
const output = [];
let hasToolCall = false;
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
output.push({
id: `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
status: "completed",
content: [{
type: "reasoning",
text: reasoningText
}]
});
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
output.push({
id: `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
status: "completed",
content: [{
type: "output_text",
text: contentText,
annotations: []
}]
});
}
} else if (item.type === 'function_call') {
hasToolCall = true;
output.push({
id: item.call_id || `call_${uuidv4().replace(/-/g, '')}`,
type: "function_call",
name: this.getOriginalToolName(item.name),
arguments: typeof item.arguments === 'string' ? item.arguments : JSON.stringify(item.arguments),
status: "completed"
});
}
}
}
return {
id: response.id || `resp_${uuidv4().replace(/-/g, '')}`,
object: "response",
created_at: unixTimestamp,
model: response.model || model,
status: "completed",
output: output,
usage: {
input_tokens: response.usage?.input_tokens || 0,
output_tokens: response.usage?.output_tokens || 0,
total_tokens: response.usage?.total_tokens || 0,
output_tokens_details: {
reasoning_tokens: response.usage?.output_tokens_details?.reasoning_tokens || 0
}
}
};
}
/**
* Codex Gemini 响应转换
*/
toGeminiResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const parts = [];
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
parts.push({ text: reasoningText, thought: true });
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
parts.push({ text: contentText });
}
} else if (item.type === 'function_call') {
parts.push({
functionCall: {
name: this.getOriginalToolName(item.name),
args: typeof item.arguments === 'string' ? JSON.parse(item.arguments) : item.arguments
}
});
}
}
}
return {
candidates: [{
content: {
role: "model",
parts: parts
},
finishReason: "STOP"
}],
usageMetadata: {
promptTokenCount: response.usage?.input_tokens || 0,
candidatesTokenCount: response.usage?.output_tokens || 0,
totalTokenCount: response.usage?.total_tokens || 0
},
modelVersion: response.model || model,
responseId: response.id
};
}
/**
* Codex Claude 响应转换
*/
toClaudeResponse(rawJSON, model) {
const root = typeof rawJSON === 'string' ? JSON.parse(rawJSON) : rawJSON;
if (root.type !== 'response.completed') {
return null;
}
const response = root.response;
const content = [];
let stopReason = "end_turn";
if (response.output && Array.isArray(response.output)) {
for (const item of response.output) {
if (item.type === 'reasoning') {
let reasoningText = '';
if (Array.isArray(item.summary)) {
const summaryItem = item.summary.find(s => s.type === 'summary_text');
if (summaryItem) reasoningText = summaryItem.text;
}
if (reasoningText) {
content.push({ type: "thinking", thinking: reasoningText });
}
} else if (item.type === 'message') {
let contentText = '';
if (Array.isArray(item.content)) {
const contentItem = item.content.find(c => c.type === 'output_text');
if (contentItem) contentText = contentItem.text;
}
if (contentText) {
content.push({ type: "text", text: contentText });
}
} else if (item.type === 'function_call') {
stopReason = "tool_use";
content.push({
type: "tool_use",
id: item.call_id || `call_${uuidv4().replace(/-/g, '')}`,
name: this.getOriginalToolName(item.name),
input: typeof item.arguments === 'string' ? JSON.parse(item.arguments) : item.arguments
});
}
}
}
return {
id: response.id || `msg_${uuidv4().replace(/-/g, '')}`,
type: "message",
role: "assistant",
model: response.model || model,
content: content,
stop_reason: stopReason,
usage: {
input_tokens: response.usage?.input_tokens || 0,
output_tokens: response.usage?.output_tokens || 0
}
};
}
/**
* Codex OpenAI Responses 流式响应转换
*/
toOpenAIResponsesStreamChunk(chunk, model) {
if(true){
return chunk;
}
const type = chunk.type;
const resId = chunk.response?.id || 'default';
@ -1162,10 +1215,4 @@ export class CodexConverter extends BaseConverter {
return null;
}
/**
* 转换模型列表
*/
convertModelList(data, targetProtocol) {
return data;
}
}

View file

@ -1340,7 +1340,7 @@ export class OpenAIConverter extends BaseConverter {
* OpenAI请求 -> Codex请求委托给 CodexConverter
*/
toCodexRequest(openaiRequest) {
return this.codexConverter.toCodexRequest(openaiRequest);
return this.codexConverter.toOpenAIRequestToCodexRequest(openaiRequest);
}
/**

View file

@ -5,6 +5,7 @@
import { v4 as uuidv4 } from 'uuid';
import { BaseConverter } from '../BaseConverter.js';
import { CodexConverter } from './CodexConverter.js';
import { MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import {
extractAndProcessSystemMessages as extractSystemMessages,
@ -31,6 +32,7 @@ import {
export class OpenAIResponsesConverter extends BaseConverter {
constructor() {
super(MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES);
this.codexConverter = new CodexConverter();
}
// =============================================================================
@ -165,9 +167,9 @@ export class OpenAIResponsesConverter extends BaseConverter {
content = item.content;
}
if (content || item.role === 'assistant') {
if (content || (item.role === 'assistant' || item.role === 'developer')) {
openaiRequest.messages.push({
role: item.role,
role: item.role === 'developer' ? 'assistant' : item.role,
content: content
});
}
@ -210,19 +212,31 @@ export class OpenAIResponsesConverter extends BaseConverter {
// 处理工具
if (responsesRequest.tools && Array.isArray(responsesRequest.tools)) {
openaiRequest.tools = responsesRequest.tools.map(tool => {
if (tool.type && tool.type !== 'function') {
return tool;
}
return {
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters || tool.parametersJsonSchema || { type: 'object', properties: {} }
openaiRequest.tools = responsesRequest.tools
.map(tool => {
if (tool.type && tool.type !== 'function') {
return null;
}
};
});
const name = tool.name || (tool.function && tool.function.name);
const description = tool.description || (tool.function && tool.function.description);
const parameters = tool.parameters || (tool.function && tool.function.parameters) || tool.parametersJsonSchema || { type: 'object', properties: {} };
// 如果没有名称,则该工具无效,稍后过滤掉
if (!name) {
return null;
}
return {
type: 'function',
function: {
name: name,
description: description,
parameters: parameters
}
};
})
.filter(tool => tool !== null);
}
if (responsesRequest.tool_choice) {
@ -687,11 +701,13 @@ export class OpenAIResponsesConverter extends BaseConverter {
// 处理工具
if (responsesRequest.tools && Array.isArray(responsesRequest.tools)) {
geminiRequest.tools = [{
functionDeclarations: responsesRequest.tools.map(tool => ({
name: tool.name,
description: tool.description,
parameters: tool.parameters || tool.parametersJsonSchema || { type: 'object', properties: {} }
}))
functionDeclarations: responsesRequest.tools
.filter(tool => !tool.type || tool.type === 'function')
.map(tool => ({
name: tool.name,
description: tool.description,
parameters: tool.parameters || tool.parametersJsonSchema || { type: 'object', properties: {} }
}))
}];
}
@ -780,6 +796,13 @@ export class OpenAIResponsesConverter extends BaseConverter {
return null;
}
/**
* OpenAI Responses Codex 请求转换
*/
toCodexRequest(responsesRequest) {
return this.codexConverter.toOpenAIResponsesToCodexRequest(responsesRequest);
}
// =============================================================================
// 辅助方法
// =============================================================================
@ -850,83 +873,6 @@ export class OpenAIResponsesConverter extends BaseConverter {
};
}
// =============================================================================
// 转换到 Codex 格式
// =============================================================================
/**
* OpenAI Responses Codex 请求转换
*/
toCodexRequest(responsesRequest) {
const codexRequest = {
model: responsesRequest.model,
instructions: responsesRequest.instructions || '',
input: [],
stream: responsesRequest.stream || false,
store: false,
reasoning: {
effort: responsesRequest.reasoning?.effort || 'medium',
summary: 'auto'
},
parallel_tool_calls: responsesRequest.parallel_tool_calls ?? true,
include: ['reasoning.encrypted_content']
};
// 处理 input
if (responsesRequest.input && Array.isArray(responsesRequest.input)) {
for (const item of responsesRequest.input) {
const itemType = item.type || (item.role ? 'message' : '');
if (itemType === 'message') {
const content = [];
if (Array.isArray(item.content)) {
item.content.forEach(c => {
content.push({
type: item.role === 'assistant' ? 'output_text' : 'input_text',
text: c.text
});
});
} else if (typeof item.content === 'string') {
content.push({
type: item.role === 'assistant' ? 'output_text' : 'input_text',
text: item.content
});
}
codexRequest.input.push({
type: 'message',
role: item.role === 'system' ? 'developer' : item.role,
content: content
});
} else if (itemType === 'function_call') {
codexRequest.input.push({
type: 'function_call',
call_id: item.call_id,
name: item.name,
arguments: typeof item.arguments === 'string' ? item.arguments : JSON.stringify(item.arguments)
});
} else if (itemType === 'function_call_output') {
codexRequest.input.push({
type: 'function_call_output',
call_id: item.call_id,
output: item.output
});
}
}
}
// 处理工具
if (responsesRequest.tools) {
codexRequest.tools = responsesRequest.tools.map(tool => ({
type: 'function',
name: tool.name,
description: tool.description,
parameters: tool.parameters || tool.parametersJsonSchema || { type: 'object', properties: {} }
}));
}
return codexRequest;
}
/**
* OpenAI Responses Codex 响应转换 (实际上是 Codex OpenAI Responses)

View file

@ -74,8 +74,8 @@ class CodexResponsesAPIStrategy extends ProviderStrategy {
if (typeof requestBody.input === 'string') {
// Convert to array format to add system message
requestBody.input = [
{ role: 'developer', content: filePromptContent },
{ role: 'user', content: requestBody.input }
{ type: 'message', role: 'developer', content: filePromptContent },
{ type: 'message', role: 'user', content: requestBody.input }
];
} else if (Array.isArray(requestBody.input)) {
// Check if system message already exists
@ -86,11 +86,11 @@ class CodexResponsesAPIStrategy extends ProviderStrategy {
if (systemMessageIndex !== -1) {
requestBody.input[systemMessageIndex].content = filePromptContent;
} else {
requestBody.input.unshift({ role: 'developer', content: filePromptContent });
requestBody.input.unshift({ type: 'message', role: 'developer', content: filePromptContent });
}
} else {
// If input is not defined, initialize with system message
requestBody.input = [{ role: 'developer', content: filePromptContent }];
requestBody.input = [{ type: 'message', role: 'developer', content: filePromptContent }];
}
} else if (requestBody.instructions) {
// If system prompt mode is not append, then replace the instructions

View file

@ -35,7 +35,7 @@ const DEFAULT_LOCK_CONFIG = {
};
const DEFAULT_QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
const DEFAULT_QWEN_BASE_URL = 'https://portal.qwen.ai/v1';
const DEFAULT_QWEN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
const QWEN_OAUTH_CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56';
const QWEN_OAUTH_SCOPE = 'openid profile email model.completion';
const QWEN_OAUTH_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
@ -531,7 +531,7 @@ export class QwenApiService {
const maxRetries = (this.config && this.config.REQUEST_MAX_RETRIES) || 3;
const baseDelay = (this.config && this.config.REQUEST_BASE_DELAY) || 1000;
const version = "0.2.1";
const version = "0.10.1";
const userAgent = `QwenCode/${version} (${process.platform}; ${process.arch})`;
logger.info(`[QwenApiService] User-Agent: ${userAgent}`);

View file

@ -39,7 +39,9 @@ export const PROVIDER_MODELS = {
'openaiResponses-custom': [],
'openai-qwen-oauth': [
'qwen3-coder-plus',
'qwen3-coder-flash'
'qwen3-coder-flash',
'coder-model',
'vision-model'
],
'openai-iflow': [
// iFlow 特有模型

View file

@ -62,30 +62,14 @@ export async function handleAPIRequests(method, path, req, res, currentConfig, a
* @param {Object} services - The initialized services
* @returns {Function} - The heartbeat and token refresh function
*/
export function initializeAPIManagement(services, config = {}) {
export function initializeAPIManagement(services) {
const providerPoolManager = getProviderPoolManager();
const healthCheckInterval = config.HEALTH_CHECK_INTERVAL || 10 * 60 * 1000; // 默认10分钟
return async function heartbeatAndRefreshToken() {
logger.info(`[Heartbeat] Server is running. Current time: ${new Date().toLocaleString()}`, Object.keys(services));
// 定期执行健康检查
if (providerPoolManager) {
try {
logger.info('[HealthCheck] Starting periodic health check...');
await providerPoolManager.performHealthChecks();
const stats = {};
for (const providerType in providerPoolManager.providerStatus) {
const providerStats = providerPoolManager.getProviderStats(providerType);
stats[providerType] = providerStats;
}
logger.info('[HealthCheck] Health check completed. Stats:', JSON.stringify(stats));
} catch (error) {
logger.error('[HealthCheck] Health check failed:', error.message);
}
}
// 循环遍历所有已初始化的服务适配器,并尝试刷新令牌
// if (getProviderPoolManager()) {
// await getProviderPoolManager().performHealthChecks(); // 定期执行健康检查
// }
for (const providerKey in services) {
const serviceAdapter = services[providerKey];
try {

View file

@ -265,7 +265,7 @@ async function startServer() {
initializeUIManagement(CONFIG);
// Initialize API management and get heartbeat function
const heartbeatAndRefreshToken = initializeAPIManagement(services, CONFIG);
const heartbeatAndRefreshToken = initializeAPIManagement(services);
// Create request handler
const requestHandlerInstance = createRequestHandler(CONFIG, getProviderPoolManager());

View file

@ -594,11 +594,8 @@ export async function handleStreamRequest(res, service, model, requestBody, from
hasMessageStop = true;
}
} else if (clientProtocol === MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES) {
if (!hasMessageStop) {
res.write('event: done\n');
res.write('data: {}\n\n');
hasMessageStop = true;
}
// OpenAI Responses 以 response.completed/response.incomplete或 error作为结束事件。
// 连接关闭即表示流结束;不要再追加 `event: done` + `data: {}`否则会触发下游类型校验失败AI_TypeValidationError
} else if (clientProtocol === MODEL_PROTOCOL_PREFIX.CLAUDE) {
if (!hasMessageStop) {
res.write('event: message_stop\n');