fix(workbench): keep realtime turn timing complete (#1972)
This commit is contained in:
@@ -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 ?? ""));
|
||||
|
||||
Reference in New Issue
Block a user