(
res: Response,
cursorReq: CursorChatRequest,
body: OpenAIChatRequest,
anthropicReq: AnthropicRequest,
log: RequestLogger,
)
| 758 | // ==================== 流式处理(OpenAI SSE 格式) ==================== |
| 759 | |
| 760 | async function handleOpenAIStream( |
| 761 | res: Response, |
| 762 | cursorReq: CursorChatRequest, |
| 763 | body: OpenAIChatRequest, |
| 764 | anthropicReq: AnthropicRequest, |
| 765 | log: RequestLogger, |
| 766 | ): Promise<void> { |
| 767 | res.writeHead(200, { |
| 768 | 'Content-Type': 'text/event-stream', |
| 769 | 'Cache-Control': 'no-cache', |
| 770 | 'Connection': 'keep-alive', |
| 771 | 'X-Accel-Buffering': 'no', |
| 772 | }); |
| 773 | |
| 774 | const id = chatId(); |
| 775 | const created = Math.floor(Date.now() / 1000); |
| 776 | const model = body.model; |
| 777 | const hasTools = (body.tools?.length ?? 0) > 0; |
| 778 | |
| 779 | // 发送 role delta |
| 780 | writeOpenAISSE(res, { |
| 781 | id, object: 'chat.completion.chunk', created, model, |
| 782 | choices: [{ |
| 783 | index: 0, |
| 784 | delta: { role: 'assistant', content: '' }, |
| 785 | finish_reason: null, |
| 786 | }], |
| 787 | }); |
| 788 | |
| 789 | let fullResponse = ''; |
| 790 | let sentText = ''; |
| 791 | let activeCursorReq = cursorReq; |
| 792 | let retryCount = 0; |
| 793 | |
| 794 | // 统一缓冲模式:先缓冲全部响应,再检测拒绝和处理 |
| 795 | const executeStream = async (onTextDelta?: (delta: string) => void) => { |
| 796 | fullResponse = ''; |
| 797 | await sendCursorRequest(activeCursorReq, (event: CursorSSEEvent) => { |
| 798 | if (event.type !== 'text-delta' || !event.delta) return; |
| 799 | fullResponse += event.delta; |
| 800 | onTextDelta?.(event.delta); |
| 801 | }); |
| 802 | }; |
| 803 | |
| 804 | try { |
| 805 | if (!hasTools && (!body.response_format || body.response_format.type === 'text')) { |
| 806 | await handleOpenAIIncrementalTextStream(res, cursorReq, body, anthropicReq, { id, created, model }, log); |
| 807 | return; |
| 808 | } |
| 809 | |
| 810 | // ★ 混合流式:文本增量 + 工具缓冲(与 Anthropic handler 同一设计) |
| 811 | const thinkingEnabled = anthropicReq.thinking?.type === 'enabled'; |
| 812 | const hybridStreamer = createIncrementalTextStreamer({ |
| 813 | warmupChars: 300, // ★ 与拒绝检测窗口对齐 |
| 814 | transform: sanitizeResponse, |
| 815 | isBlockedPrefix: (text) => isRefusal(text.substring(0, 300)), |
| 816 | }); |
| 817 | let toolMarkerDetected = false; |
no test coverage detected