fix(workbench): keep realtime turn timing complete (#1972)

This commit is contained in:
Lyon
2026-06-23 14:30:24 +08:00
committed by GitHub
parent 9fc6f655d2
commit 4759afa308
+52 -6
View File
@@ -763,8 +763,9 @@ function factMessageDto(message, parts = [], facts = {}) {
const assistantLike = isAssistantLikeRole(role);
const checkpointStatus = assistantLike ? normalizeTerminalStatus(checkpoint?.status) : null;
const status = normalizeStatus(checkpointStatus ?? (assistantLike ? turn?.status : null) ?? messageStatus);
const timingSource = assistantLike ? (checkpointStatus ? checkpoint : turn ?? checkpoint ?? message) : message;
const timing = factTimingProjection(timingSource, status);
const timing = assistantLike
? factCombinedTimingProjection(status, checkpoint, turn, message)
: factTimingProjection(message, status);
const text = projectionText(message?.text, message?.content, message?.message, message?.finalResponse);
const normalizedParts = parts.length > 0
? [...parts].sort(compareFactPartsAsc).map((part) => factPartDto(part, messageId, traceId)).filter(Boolean)
@@ -830,7 +831,7 @@ function factTurnSnapshot({ turn = null, session = null, facts = {}, traceId, tu
const traceStatus = normalizeTerminalStatus(trace.status);
const status = normalizeStatus(checkpointStatus ?? traceStatus ?? turn?.status ?? session?.status);
const finalText = projectionText(turn?.finalResponse, turn?.assistantText, turn?.text, assistantMessage?.text);
const timing = factTimingProjection(checkpointStatus ? checkpoint : turn ?? checkpoint, status);
const timing = factCombinedTimingProjection(status, checkpoint, turn, trace);
return {
turnId: resolvedTurnId,
traceId: safeTrace,
@@ -960,16 +961,61 @@ function factProjectionForTrace(facts = {}, traceId) {
function factTimingProjection(record = null, status = null) {
const source = objectValue(record?.timing);
const observedAt = new Date().toISOString();
const startedAt = timestampIso(source?.startedAt);
const lastEventAt = timestampIso(source?.lastEventAt);
const startedAt = timestampIso(source?.startedAt ?? record?.startedAt);
const lastEventAt = timestampIso(source?.lastEventAt ?? record?.lastEventAt ?? record?.updatedAt ?? record?.createdAt);
const normalizedStatus = normalizeStatus(status ?? record?.status);
const terminal = record?.terminal === true || TERMINAL_STATUSES.has(normalizedStatus) && !RUNNING_STATUSES.has(normalizedStatus);
const finishedAt = terminal ? timestampIso(source?.finishedAt) : null;
const finishedAt = terminal ? timestampIso(source?.finishedAt ?? record?.finishedAt ?? record?.completedAt) : null;
const durationMs = elapsedFactMs(startedAt, terminal ? finishedAt : observedAt);
const lastEventAgeMs = terminal ? null : elapsedFactMs(lastEventAt, observedAt);
return { startedAt, lastEventAt, finishedAt, durationMs, observedAt: terminal ? null : observedAt, lastEventAgeMs, valuesRedacted: source?.valuesRedacted !== false };
}
function factCombinedTimingProjection(status = null, ...records) {
const normalizedStatus = normalizeStatus(status ?? records.find((record) => record?.status)?.status);
const terminal = TERMINAL_STATUSES.has(normalizedStatus) && !RUNNING_STATUSES.has(normalizedStatus);
const observedAt = new Date().toISOString();
const timings = records.map((record) => factTimingSource(record)).filter(Boolean);
const startedAt = firstTimestampIso(...timings.map((timing) => timing.startedAt));
const lastEventAt = latestTimestampIso(...timings.map((timing) => timing.lastEventAt));
const finishedAt = terminal ? latestTimestampIso(...timings.map((timing) => timing.finishedAt)) : null;
const durationMs = elapsedFactMs(startedAt, terminal ? finishedAt : observedAt);
const lastEventAgeMs = terminal ? null : elapsedFactMs(lastEventAt, observedAt);
return { startedAt, lastEventAt, finishedAt, durationMs, observedAt: terminal ? null : observedAt, lastEventAgeMs, valuesRedacted: true };
}
function factTimingSource(record = null) {
if (!record) return null;
const source = objectValue(record?.timing);
return {
startedAt: timestampIso(source?.startedAt ?? record?.startedAt),
lastEventAt: timestampIso(source?.lastEventAt ?? record?.lastEventAt ?? record?.updatedAt ?? record?.createdAt),
finishedAt: timestampIso(source?.finishedAt ?? record?.finishedAt ?? record?.completedAt)
};
}
function firstTimestampIso(...values) {
for (const value of values) {
const timestamp = timestampIso(value);
if (timestamp) return timestamp;
}
return null;
}
function latestTimestampIso(...values) {
let latest = null;
let latestMs = Number.NEGATIVE_INFINITY;
for (const value of values) {
const timestamp = timestampIso(value);
if (!timestamp) continue;
const ms = Date.parse(timestamp);
if (!Number.isFinite(ms) || ms < latestMs) continue;
latest = timestamp;
latestMs = ms;
}
return latest;
}
function elapsedFactMs(startedAt, endedAt) {
const start = Date.parse(String(startedAt ?? ""));
const end = Date.parse(String(endedAt ?? ""));