(res: Response, cursorReq: CursorChatRequest, body: AnthropicRequest, log: RequestLogger, clientRequestedThinking: boolean = false)
| 1816 | // ==================== 非流式处理 ==================== |
| 1817 | |
| 1818 | async function handleNonStream(res: Response, cursorReq: CursorChatRequest, body: AnthropicRequest, log: RequestLogger, clientRequestedThinking: boolean = false): Promise<void> { |
| 1819 | // ★ 非流式保活:手动设置 chunked 响应,在缓冲期间每 15s 发送空白字符保活 |
| 1820 | // JSON.parse 会忽略前导空白,所以客户端解析不受影响 |
| 1821 | res.writeHead(200, { 'Content-Type': 'application/json' }); |
| 1822 | const keepaliveInterval = setInterval(() => { |
| 1823 | try { |
| 1824 | res.write(' '); |
| 1825 | // @ts-expect-error flush exists on ServerResponse when compression is used |
| 1826 | if (typeof res.flush === 'function') res.flush(); |
| 1827 | } catch { /* connection already closed, ignore */ } |
| 1828 | }, 15000); |
| 1829 | |
| 1830 | try { |
| 1831 | log.startPhase('send', '发送到 Cursor (非流式)'); |
| 1832 | const apiStart = Date.now(); |
| 1833 | let { text: fullText, usage: cursorUsage } = await sendCursorRequestFull(cursorReq); |
| 1834 | log.recordTTFT(); |
| 1835 | log.recordCursorApiTime(apiStart); |
| 1836 | log.recordRawResponse(fullText); |
| 1837 | log.startPhase('response', '处理响应'); |
| 1838 | const hasTools = (body.tools?.length ?? 0) > 0; |
| 1839 | let activeCursorReq = cursorReq; |
| 1840 | let retryCount = 0; |
| 1841 | |
| 1842 | log.info('Handler', 'response', `非流式原始响应: ${fullText.length} chars`, { |
| 1843 | preview: fullText.substring(0, 300), |
| 1844 | hasTools, |
| 1845 | }); |
| 1846 | |
| 1847 | // ★ Thinking 提取(在拒绝检测之前) |
| 1848 | // 始终剥离 thinking 标签,避免泄漏到最终文本中 |
| 1849 | let thinkingContent = ''; |
| 1850 | if (hasLeadingThinking(fullText)) { |
| 1851 | const { thinkingContent: extracted, strippedText } = extractThinking(fullText); |
| 1852 | if (extracted) { |
| 1853 | thinkingContent = extracted; |
| 1854 | fullText = strippedText; |
| 1855 | if (clientRequestedThinking) { |
| 1856 | log.info('Handler', 'thinking', `非流式剥离 thinking → content block: ${thinkingContent.length} chars, 剩余 ${fullText.length} chars`); |
| 1857 | } else { |
| 1858 | log.info('Handler', 'thinking', `非流式剥离 thinking (非客户端请求): ${thinkingContent.length} chars, 剩余 ${fullText.length} chars`); |
| 1859 | } |
| 1860 | } |
| 1861 | } |
| 1862 | |
| 1863 | // 拒绝检测 + 自动重试 |
| 1864 | // fullText 已在上方剥离 thinking 标签,可直接用于拒绝检测 |
| 1865 | const shouldRetry = () => { |
| 1866 | return isRefusal(fullText) && !(hasTools && hasToolCalls(fullText)); |
| 1867 | }; |
| 1868 | |
| 1869 | if (shouldRetry()) { |
| 1870 | for (let attempt = 0; attempt < MAX_REFUSAL_RETRIES; attempt++) { |
| 1871 | retryCount++; |
| 1872 | log.warn('Handler', 'retry', `非流式检测到拒绝(第${retryCount}次重试)`, { preview: fullText.substring(0, 200) }); |
| 1873 | log.updateSummary({ retryCount }); |
| 1874 | const retryBody = buildRetryRequest(body, attempt); |
| 1875 | activeCursorReq = await convertToCursorRequest(retryBody); |
no test coverage detected