Keep chat stream alive during long runs

This commit is contained in:
武阳
2026-05-07 15:33:03 +08:00
parent cd4e73c159
commit ce6445bf77
+58 -44
View File
@@ -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,
},
});
},
});