From 52321f0f3af8f83fb75534b35de0390ed7f2b36c Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Wed, 8 Apr 2026 03:16:50 +0200 Subject: [PATCH] Solved error with not tracked response.completed event where does't get all the deltas --- src/converters/strategies/CodexConverter.js | 13 ++-- src/providers/openai/codex-core.js | 75 ++++++++++++++++++--- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/converters/strategies/CodexConverter.js b/src/converters/strategies/CodexConverter.js index ddc1666..1da0b36 100644 --- a/src/converters/strategies/CodexConverter.js +++ b/src/converters/strategies/CodexConverter.js @@ -226,12 +226,17 @@ export class CodexConverter extends BaseConverter { if (data.response_format || data.text?.verbosity) { const textObj = {}; if (data.response_format) { - textObj.format = this.convertResponseFormat(data.response_format); + const converted = this.convertResponseFormat(data.response_format); + if (converted) { + textObj.format = converted; + } } if (data.text?.verbosity) { textObj.verbosity = data.text.verbosity; } - codexRequest.text = textObj; + if (Object.keys(textObj).length > 0) { + codexRequest.text = textObj; + } } // 在 input 开头注入特殊指令(如果配置允许) @@ -531,9 +536,7 @@ export class CodexConverter extends BaseConverter { schema: responseFormat.json_schema?.schema || {} }; } else if (responseFormat.type === 'json_object') { - return { - type: 'json_object' - }; + return null; } return responseFormat; } diff --git a/src/providers/openai/codex-core.js b/src/providers/openai/codex-core.js index 909414f..6147767 100644 --- a/src/providers/openai/codex-core.js +++ b/src/providers/openai/codex-core.js @@ -596,9 +596,13 @@ export class CodexApiService { parseNonStreamResponse(data) { // 确保 data 是字符串 const responseText = typeof data === 'string' ? data : String(data); - - // 从 SSE 流中提取 response.completed 事件 + + // 从 SSE 流中提取所有事件,累积 output const lines = responseText.split('\n'); + const outputItems = new Map(); // id -> output item + const textDeltas = new Map(); // item_id -> accumulated text + let completedEvent = null; + for (const line of lines) { if (line.startsWith('data: ')) { const jsonData = line.slice(6).trim(); @@ -607,8 +611,26 @@ export class CodexApiService { } try { const parsed = JSON.parse(jsonData); - if (parsed.type === 'response.completed') { - return parsed; + switch (parsed.type) { + case 'response.output_item.added': + if (parsed.item) { + outputItems.set(parsed.item.id, parsed.item); + } + break; + case 'response.output_text.delta': + if (parsed.item_id && parsed.delta) { + const existing = textDeltas.get(parsed.item_id) || ''; + textDeltas.set(parsed.item_id, existing + parsed.delta); + } + break; + case 'response.output_text.done': + if (parsed.item_id && parsed.text) { + textDeltas.set(parsed.item_id, parsed.text); + } + break; + case 'response.completed': + completedEvent = parsed; + break; } } catch (e) { // 继续解析下一行 @@ -616,10 +638,47 @@ export class CodexApiService { } } } - - // 如果没有找到 response.completed,抛出错误 - logger.error('[Codex] No completed response found in Codex response'); - throw new Error('stream error: stream disconnected before completion: stream closed before response.completed'); + + if (!completedEvent) { + logger.error('[Codex] No completed response found in Codex response'); + throw new Error('stream error: stream disconnected before completion: stream closed before response.completed'); + } + + // 用累积的 delta 文本填充 output items 中缺失的内容 + if (completedEvent.response && textDeltas.size > 0) { + const output = completedEvent.response.output || []; + for (const item of output) { + if (item.type === 'message' && item.role === 'assistant') { + const accumulatedText = textDeltas.get(item.id); + if (accumulatedText !== undefined) { + // content 为空或不含 output_text,直接注入 + if (!item.content || item.content.length === 0) { + item.content = [{ type: 'output_text', text: accumulatedText }]; + } else { + item.content = item.content.map(c => { + if (c.type === 'output_text' && !c.text) { + return { ...c, text: accumulatedText }; + } + return c; + }); + } + } + } + } + // 如果 output 完全为空,从累积事件重建 + if (output.length === 0 && outputItems.size > 0) { + for (const [id, item] of outputItems) { + const accumulatedText = textDeltas.get(id); + if (accumulatedText !== undefined && item.type === 'message') { + item.content = [{ type: 'output_text', text: accumulatedText }]; + } + output.push(item); + } + completedEvent.response.output = output; + } + } + + return completedEvent; } /**