From 6ee7e78c90842df88b371d70697e8ead4bfec714 Mon Sep 17 00:00:00 2001 From: hex2077 Date: Fri, 13 Feb 2026 20:47:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=92=8C=E4=BF=AE=E5=A4=8D=E6=B5=81=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加新的Qwen模型(coder-model, vision-model)到提供者列表 - 修复OpenAI Responses流结束事件处理,避免下游类型校验错误 - 更新Qwen API端点地址和版本号 - 重构Codex转换器,分离OpenAI和OpenAI Responses的转换逻辑 - 优化工具调用处理,支持嵌套function结构 - 移除健康检查功能,简化API管理初始化 - 修复消息角色转换(developer→assistant)和类型标记 --- src/converters/strategies/CodexConverter.js | 511 ++++++++++-------- src/converters/strategies/OpenAIConverter.js | 2 +- .../strategies/OpenAIResponsesConverter.js | 138 ++--- .../openai/codex-responses-strategy.js | 8 +- src/providers/openai/qwen-core.js | 4 +- src/providers/provider-models.js | 4 +- src/services/api-manager.js | 24 +- src/services/api-server.js | 2 +- src/utils/common.js | 7 +- 9 files changed, 338 insertions(+), 362 deletions(-) diff --git a/src/converters/strategies/CodexConverter.js b/src/converters/strategies/CodexConverter.js index 7feb441..c0fa990 100644 --- a/src/converters/strategies/CodexConverter.js +++ b/src/converters/strategies/CodexConverter.js @@ -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; - } } diff --git a/src/converters/strategies/OpenAIConverter.js b/src/converters/strategies/OpenAIConverter.js index 9133ed1..cce072a 100644 --- a/src/converters/strategies/OpenAIConverter.js +++ b/src/converters/strategies/OpenAIConverter.js @@ -1340,7 +1340,7 @@ export class OpenAIConverter extends BaseConverter { * OpenAI请求 -> Codex请求(委托给 CodexConverter) */ toCodexRequest(openaiRequest) { - return this.codexConverter.toCodexRequest(openaiRequest); + return this.codexConverter.toOpenAIRequestToCodexRequest(openaiRequest); } /** diff --git a/src/converters/strategies/OpenAIResponsesConverter.js b/src/converters/strategies/OpenAIResponsesConverter.js index 858b9f2..8c75bc6 100644 --- a/src/converters/strategies/OpenAIResponsesConverter.js +++ b/src/converters/strategies/OpenAIResponsesConverter.js @@ -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) diff --git a/src/providers/openai/codex-responses-strategy.js b/src/providers/openai/codex-responses-strategy.js index be61a83..21cacbb 100644 --- a/src/providers/openai/codex-responses-strategy.js +++ b/src/providers/openai/codex-responses-strategy.js @@ -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 diff --git a/src/providers/openai/qwen-core.js b/src/providers/openai/qwen-core.js index a6bf582..d566d0e 100644 --- a/src/providers/openai/qwen-core.js +++ b/src/providers/openai/qwen-core.js @@ -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}`); diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 7964f99..abcee8c 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -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 特有模型 diff --git a/src/services/api-manager.js b/src/services/api-manager.js index dc97f5e..d9cb526 100644 --- a/src/services/api-manager.js +++ b/src/services/api-manager.js @@ -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 { diff --git a/src/services/api-server.js b/src/services/api-server.js index 08d1ead..90543f2 100644 --- a/src/services/api-server.js +++ b/src/services/api-server.js @@ -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()); diff --git a/src/utils/common.js b/src/utils/common.js index f3cb01f..00eaa50 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -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');