优化进度事件处理,添加请求持续时间统计

This commit is contained in:
2026-05-13 17:33:40 +08:00
parent 319b3c8ea5
commit 3021fc42ec
2 changed files with 70 additions and 18 deletions
+2
View File
@@ -2,3 +2,5 @@ node_modules/
.opencode/node_modules/ .opencode/node_modules/
.local.env .local.env
.vscode .vscode
data/
logs/
+65 -15
View File
@@ -240,7 +240,6 @@ export const buildChatRouter = (
}), }),
); );
} }
res.write(toSse("done", { session_id: clientSessionId }));
} }
} }
} finally { } finally {
@@ -341,6 +340,16 @@ type StreamPromptOptions = {
write: (event: string, data: Record<string, unknown>) => void; write: (event: string, data: Record<string, unknown>) => void;
}; };
type ProgressStatus = "running" | "completed" | "error";
type ProgressPayload = {
id: string;
phase: string;
status: ProgressStatus;
title: string;
detail?: string;
};
const streamPromptResponse = async ({ const streamPromptResponse = async ({
runtime, runtime,
opencodeSessionId, opencodeSessionId,
@@ -353,6 +362,9 @@ const streamPromptResponse = async ({
}: StreamPromptOptions): Promise<{ aborted: boolean; failed: boolean }> => { }: StreamPromptOptions): Promise<{ aborted: boolean; failed: boolean }> => {
const eventStream = await runtime.subscribeEvents(); const eventStream = await runtime.subscribeEvents();
const iterator = eventStream[Symbol.asyncIterator](); const iterator = eventStream[Symbol.asyncIterator]();
const requestStartedAt = Date.now();
const progressStartedAtMap = new Map<string, number>();
const finalizedProgressIds = new Set<string>();
const emittedToolParts = new Set<string>(); const emittedToolParts = new Set<string>();
const partTypes = new Map<string, Part["type"]>(); const partTypes = new Map<string, Part["type"]>();
const pendingPartTextDeltas = new Map<string, string[]>(); const pendingPartTextDeltas = new Map<string, string[]>();
@@ -375,8 +387,48 @@ const streamPromptResponse = async ({
}) })
: null; : null;
const emitProgress = ({ id, phase, status, title, detail }: ProgressPayload) => {
if (status === "running" && finalizedProgressIds.has(id)) {
return;
}
const now = Date.now();
const startedAt = progressStartedAtMap.get(id) ?? now;
if (!progressStartedAtMap.has(id)) {
progressStartedAtMap.set(id, startedAt);
}
if (status === "running") {
write("progress", { write("progress", {
session_id: clientSessionId, session_id: clientSessionId,
id,
phase,
status,
title,
detail,
started_at: startedAt,
elapsed_ms: Math.max(0, now - startedAt),
});
return;
}
const durationMs = Math.max(0, now - startedAt);
finalizedProgressIds.add(id);
progressStartedAtMap.delete(id);
write("progress", {
session_id: clientSessionId,
id,
phase,
status,
title,
detail,
started_at: startedAt,
ended_at: now,
duration_ms: durationMs,
});
};
emitProgress({
id: "request-received", id: "request-received",
phase: "start", phase: "start",
status: "running", status: "running",
@@ -438,8 +490,7 @@ const streamPromptResponse = async ({
} }
if (event.type === "session.status") { if (event.type === "session.status") {
write("progress", { emitProgress({
session_id: clientSessionId,
id: "session-status", id: "session-status",
phase: "session", phase: "session",
status: event.properties.status.type === "idle" ? "completed" : "running", status: event.properties.status.type === "idle" ? "completed" : "running",
@@ -515,8 +566,7 @@ const streamPromptResponse = async ({
reasoningDeltas.get(part.id) ?? [], reasoningDeltas.get(part.id) ?? [],
part.time.end, part.time.end,
); );
write("progress", { emitProgress({
session_id: clientSessionId,
id: part.id, id: part.id,
phase: "planning", phase: "planning",
status: part.time.end ? "completed" : "running", status: part.time.end ? "completed" : "running",
@@ -530,8 +580,7 @@ const streamPromptResponse = async ({
const isToolFinalState = const isToolFinalState =
part.state.status === "completed" || part.state.status === "error"; part.state.status === "completed" || part.state.status === "error";
write("progress", { emitProgress({
session_id: clientSessionId,
id: part.id, id: part.id,
phase: "tool", phase: "tool",
status: normalizeToolStatus(part.state.status), status: normalizeToolStatus(part.state.status),
@@ -587,8 +636,7 @@ const streamPromptResponse = async ({
const completed = event.properties.todos.filter( const completed = event.properties.todos.filter(
(todo) => todo.status === "completed", (todo) => todo.status === "completed",
).length; ).length;
write("progress", { emitProgress({
session_id: clientSessionId,
id: "todo-progress", id: "todo-progress",
phase: "planning", phase: "planning",
status: completed === event.properties.todos.length ? "completed" : "running", status: completed === event.properties.todos.length ? "completed" : "running",
@@ -607,6 +655,7 @@ const streamPromptResponse = async ({
? getErrorMessage(event.properties.error) ? getErrorMessage(event.properties.error)
: "opencode session error", : "opencode session error",
detail: event.properties.error?.name, detail: event.properties.error?.name,
total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
}); });
failed = true; failed = true;
done = true; done = true;
@@ -614,8 +663,7 @@ const streamPromptResponse = async ({
} }
if (event.type === "session.idle") { if (event.type === "session.idle") {
write("progress", { emitProgress({
session_id: clientSessionId,
id: "session-status", id: "session-status",
phase: "session", phase: "session",
status: "completed", status: "completed",
@@ -641,16 +689,14 @@ const streamPromptResponse = async ({
if (!emittedText) { if (!emittedText) {
await emitFallbackMessage(runtime, opencodeSessionId, clientSessionId, write); await emitFallbackMessage(runtime, opencodeSessionId, clientSessionId, write);
} }
write("progress", { emitProgress({
session_id: clientSessionId,
id: "request-received", id: "request-received",
phase: "start", phase: "start",
status: "completed", status: "completed",
title: "请求处理完成", title: "请求处理完成",
detail: "本次请求的分析、工具执行和结果整理流程已经完成。", detail: "本次请求的分析、工具执行和结果整理流程已经完成。",
}); });
write("progress", { emitProgress({
session_id: clientSessionId,
id: "request-completed", id: "request-completed",
phase: "complete", phase: "complete",
status: "completed", status: "completed",
@@ -659,6 +705,10 @@ const streamPromptResponse = async ({
? "最终回答已生成并推送到前端。" ? "最终回答已生成并推送到前端。"
: "已完成分析,并通过兜底消息补发最终回答内容。", : "已完成分析,并通过兜底消息补发最终回答内容。",
}); });
write("done", {
session_id: clientSessionId,
total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
});
return { aborted: false, failed: false }; return { aborted: false, failed: false };
} finally { } finally {
await iterator.return?.(undefined); await iterator.return?.(undefined);