From ce6445bf774bcd1934b60c670f77290653fe834a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=A6=E9=98=B3?= Date: Thu, 7 May 2026 15:33:03 +0800 Subject: [PATCH] Keep chat stream alive during long runs --- frontend/app/app/api/chat/route.ts | 102 ++++++++++++++++------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/frontend/app/app/api/chat/route.ts b/frontend/app/app/api/chat/route.ts index 207b431..1ce5e78 100644 --- a/frontend/app/app/api/chat/route.ts +++ b/frontend/app/app/api/chat/route.ts @@ -13,6 +13,7 @@ import { } from "@/lib/claw-auth"; export const runtime = "nodejs"; +export const maxDuration = 300; type ClawChatResponse = { final_output?: string; @@ -92,55 +93,68 @@ export async function POST(req: Request) { const stream = createUIMessageStream({ execute: async ({ writer }) => { const streamState = { textStarted: false, textStreamed: false }; + let heartbeatCount = 0; + const heartbeat = setInterval(() => { + heartbeatCount += 1; + writer.write({ + type: "reasoning-delta", + id: "reasoning-1", + delta: `\n仍在处理请求,已等待 ${formatDuration(heartbeatCount * 15000)}。`, + }); + }, 15000); writer.write({ type: "start" }); writer.write({ type: "start-step" }); - const startedAt = Date.now(); - writer.write({ type: "reasoning-start", id: "reasoning-1" }); - writer.write({ - type: "reasoning-delta", - id: "reasoning-1", - delta: "思考中,正在判断是否需要调用工具。", - }); - const payload = await callClawBackendStream( - userPrompt, - runtimeContext, - account.id, - sessionId, - resumeId, - writer, - streamState, - ); - const elapsedMs = payload.elapsed_ms ?? Date.now() - startedAt; - const text = formatClawResponse(payload); + try { + const startedAt = Date.now(); + writer.write({ type: "reasoning-start", id: "reasoning-1" }); + writer.write({ + type: "reasoning-delta", + id: "reasoning-1", + delta: "思考中,正在判断是否需要调用工具。", + }); + const payload = await callClawBackendStream( + userPrompt, + runtimeContext, + account.id, + sessionId, + resumeId, + writer, + streamState, + ); + const elapsedMs = payload.elapsed_ms ?? Date.now() - startedAt; + const text = formatClawResponse(payload); - writer.write({ - type: "reasoning-delta", - id: "reasoning-1", - delta: `\n思考并执行完成,用时 ${formatDuration(elapsedMs)}。`, - }); - writer.write({ type: "reasoning-end", id: "reasoning-1" }); - writeToolTrace(writer, payload.transcript); - if (!streamState.textStreamed) { - writer.write({ type: "text-start", id: "text-1" }); - writer.write({ type: "text-delta", id: "text-1", delta: text }); - writer.write({ type: "text-end", id: "text-1" }); - } else if (streamState.textStarted) { - writer.write({ type: "text-end", id: "text-1" }); - streamState.textStarted = false; + writer.write({ + type: "reasoning-delta", + id: "reasoning-1", + delta: `\n思考并执行完成,用时 ${formatDuration(elapsedMs)}。`, + }); + writer.write({ type: "reasoning-end", id: "reasoning-1" }); + writeToolTrace(writer, payload.transcript); + if (!streamState.textStreamed) { + writer.write({ type: "text-start", id: "text-1" }); + writer.write({ type: "text-delta", id: "text-1", delta: text }); + writer.write({ type: "text-end", id: "text-1" }); + } else if (streamState.textStarted) { + writer.write({ type: "text-end", id: "text-1" }); + streamState.textStarted = false; + } + writer.write({ type: "finish-step" }); + writer.write({ + type: "finish", + finishReason: payload.error ? "error" : "stop", + messageMetadata: { + sessionId: payload.session_id, + toolCalls: payload.tool_calls, + stopReason: payload.stop_reason, + elapsedMs, + usage: payload.usage, + }, + }); + } finally { + clearInterval(heartbeat); } - writer.write({ type: "finish-step" }); - writer.write({ - type: "finish", - finishReason: payload.error ? "error" : "stop", - messageMetadata: { - sessionId: payload.session_id, - toolCalls: payload.tool_calls, - stopReason: payload.stop_reason, - elapsedMs, - usage: payload.usage, - }, - }); }, });