* Responses API 流式处理 * * ★ 与 Chat Completions 流式的核心区别: * 1. 使用 event: 前缀的 SSE 事件(不是 data-only) * 2. 必须发送 response.completed 事件,否则 Codex 报 "stream closed before response.completed" * 3. 工具调用用 function_call 类型的 output item 表示
(
res: Response,
cursorReq: CursorChatRequest,
body: Record<string, unknown>,
anthropicReq: AnthropicRequest,
log: RequestLogger,
)
| 1528 | * 3. 工具调用用 function_call 类型的 output item 表示 |
| 1529 | */ |
| 1530 | async function handleResponsesStream( |
| 1531 | res: Response, |
| 1532 | cursorReq: CursorChatRequest, |
| 1533 | body: Record<string, unknown>, |
| 1534 | anthropicReq: AnthropicRequest, |
| 1535 | log: RequestLogger, |
| 1536 | ): Promise<void> { |
| 1537 | res.writeHead(200, { |
| 1538 | 'Content-Type': 'text/event-stream', |
| 1539 | 'Cache-Control': 'no-cache', |
| 1540 | 'Connection': 'keep-alive', |
| 1541 | 'X-Accel-Buffering': 'no', |
| 1542 | }); |
| 1543 | |
| 1544 | const respId = responsesId(); |
| 1545 | const model = (body.model as string) || 'gpt-4'; |
| 1546 | const hasTools = (anthropicReq.tools?.length ?? 0) > 0; |
| 1547 | let toolCallsDetected = 0; |
| 1548 | |
| 1549 | // 缓冲完整响应再处理(复用 Chat Completions 的逻辑) |
| 1550 | let fullResponse = ''; |
| 1551 | let activeCursorReq = cursorReq; |
| 1552 | let retryCount = 0; |
| 1553 | |
| 1554 | // ★ 流式保活:防止网关 504 |
| 1555 | const keepaliveInterval = setInterval(() => { |
| 1556 | try { |
| 1557 | res.write(': keepalive\n\n'); |
| 1558 | if (typeof (res as unknown as { flush: () => void }).flush === 'function') { |
| 1559 | (res as unknown as { flush: () => void }).flush(); |
| 1560 | } |
| 1561 | } catch { /* connection already closed */ } |
| 1562 | }, 15000); |
| 1563 | |
| 1564 | try { |
| 1565 | const executeStream = async () => { |
| 1566 | fullResponse = ''; |
| 1567 | await sendCursorRequest(activeCursorReq, (event: CursorSSEEvent) => { |
| 1568 | if (event.type !== 'text-delta' || !event.delta) return; |
| 1569 | fullResponse += event.delta; |
| 1570 | }); |
| 1571 | }; |
| 1572 | |
| 1573 | await executeStream(); |
| 1574 | |
| 1575 | // Thinking 提取 |
| 1576 | if (hasLeadingThinking(fullResponse)) { |
| 1577 | const { strippedText } = extractThinking(fullResponse); |
| 1578 | fullResponse = strippedText; |
| 1579 | } |
| 1580 | |
| 1581 | // 拒绝检测 + 自动重试 |
| 1582 | const shouldRetryRefusal = () => { |
| 1583 | if (!isRefusal(fullResponse)) return false; |
| 1584 | if (hasTools && hasToolCalls(fullResponse)) return false; |
| 1585 | return true; |
| 1586 | }; |
| 1587 |
no test coverage detected