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 assistantLike = isAssistantLikeRole(role);
|
||||||
const checkpointStatus = assistantLike ? normalizeTerminalStatus(checkpoint?.status) : null;
|
const checkpointStatus = assistantLike ? normalizeTerminalStatus(checkpoint?.status) : null;
|
||||||
const status = normalizeStatus(checkpointStatus ?? (assistantLike ? turn?.status : null) ?? messageStatus);
|
const status = normalizeStatus(checkpointStatus ?? (assistantLike ? turn?.status : null) ?? messageStatus);
|
||||||
const timingSource = assistantLike ? (checkpointStatus ? checkpoint : turn ?? checkpoint ?? message) : message;
|
const timing = assistantLike
|
||||||
const timing = factTimingProjection(timingSource, status);
|
? factCombinedTimingProjection(status, checkpoint, turn, message)
|
||||||
|
: factTimingProjection(message, status);
|
||||||
const text = projectionText(message?.text, message?.content, message?.message, message?.finalResponse);
|
const text = projectionText(message?.text, message?.content, message?.message, message?.finalResponse);
|
||||||
const normalizedParts = parts.length > 0
|
const normalizedParts = parts.length > 0
|
||||||
? [...parts].sort(compareFactPartsAsc).map((part) => factPartDto(part, messageId, traceId)).filter(Boolean)
|
? [...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 traceStatus = normalizeTerminalStatus(trace.status);
|
||||||
const status = normalizeStatus(checkpointStatus ?? traceStatus ?? turn?.status ?? session?.status);
|
const status = normalizeStatus(checkpointStatus ?? traceStatus ?? turn?.status ?? session?.status);
|
||||||
const finalText = projectionText(turn?.finalResponse, turn?.assistantText, turn?.text, assistantMessage?.text);
|
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 {
|
return {
|
||||||
turnId: resolvedTurnId,
|
turnId: resolvedTurnId,
|
||||||
traceId: safeTrace,
|
traceId: safeTrace,
|
||||||
@@ -960,16 +961,61 @@ function factProjectionForTrace(facts = {}, traceId) {
|
|||||||
function factTimingProjection(record = null, status = null) {
|
function factTimingProjection(record = null, status = null) {
|
||||||
const source = objectValue(record?.timing);
|
const source = objectValue(record?.timing);
|
||||||
const observedAt = new Date().toISOString();
|
const observedAt = new Date().toISOString();
|
||||||
const startedAt = timestampIso(source?.startedAt);
|
const startedAt = timestampIso(source?.startedAt ?? record?.startedAt);
|
||||||
const lastEventAt = timestampIso(source?.lastEventAt);
|
const lastEventAt = timestampIso(source?.lastEventAt ?? record?.lastEventAt ?? record?.updatedAt ?? record?.createdAt);
|
||||||
const normalizedStatus = normalizeStatus(status ?? record?.status);
|
const normalizedStatus = normalizeStatus(status ?? record?.status);
|
||||||
const terminal = record?.terminal === true || TERMINAL_STATUSES.has(normalizedStatus) && !RUNNING_STATUSES.has(normalizedStatus);
|
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 durationMs = elapsedFactMs(startedAt, terminal ? finishedAt : observedAt);
|
||||||
const lastEventAgeMs = terminal ? null : elapsedFactMs(lastEventAt, observedAt);
|
const lastEventAgeMs = terminal ? null : elapsedFactMs(lastEventAt, observedAt);
|
||||||
return { startedAt, lastEventAt, finishedAt, durationMs, observedAt: terminal ? null : observedAt, lastEventAgeMs, valuesRedacted: source?.valuesRedacted !== false };
|
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) {
|
function elapsedFactMs(startedAt, endedAt) {
|
||||||
const start = Date.parse(String(startedAt ?? ""));
|
const start = Date.parse(String(startedAt ?? ""));
|
||||||
const end = Date.parse(String(endedAt ?? ""));
|
const end = Date.parse(String(endedAt ?? ""));
|
||||||
|
|||||||
Reference in New Issue
Block a user