Carry elapsed time through replay tool parts
This commit is contained in:
@@ -585,7 +585,7 @@ function collectActivityItems(messages: readonly MessageState[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (part.type === "tool-call") {
|
if (part.type === "tool-call") {
|
||||||
const input = stripStageNote(part.args);
|
const input = stripClawToolMetadata(part.args);
|
||||||
const stageNote = getStageNote(part.args);
|
const stageNote = getStageNote(part.args);
|
||||||
const argsText = formatToolArgs(input);
|
const argsText = formatToolArgs(input);
|
||||||
if (stageNote) {
|
if (stageNote) {
|
||||||
@@ -632,12 +632,13 @@ function getStageNote(value: unknown) {
|
|||||||
return typeof note === "string" ? note.trim() : "";
|
return typeof note === "string" ? note.trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripStageNote(value: unknown): unknown {
|
function stripClawToolMetadata(value: unknown): unknown {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
||||||
const { __claw_stage_note: _stageNote, ...rest } = value as Record<
|
const {
|
||||||
string,
|
__claw_stage_note: _stageNote,
|
||||||
unknown
|
__claw_elapsed_ms: _elapsedMs,
|
||||||
>;
|
...rest
|
||||||
|
} = value as Record<string, unknown>;
|
||||||
return rest;
|
return rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -425,10 +425,11 @@ export function toReplayRepository(
|
|||||||
: toolParts
|
: toolParts
|
||||||
: [{ type: "text", text: content }]
|
: [{ type: "text", text: content }]
|
||||||
) as UIMessage["parts"];
|
) as UIMessage["parts"];
|
||||||
|
const replayParts = withReplayElapsedMs(parts, elapsedMs);
|
||||||
const uiMessage: UIMessage = {
|
const uiMessage: UIMessage = {
|
||||||
id,
|
id,
|
||||||
role: message.role,
|
role: message.role,
|
||||||
parts,
|
parts: replayParts,
|
||||||
...(message.role === "assistant"
|
...(message.role === "assistant"
|
||||||
? {
|
? {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -442,7 +443,7 @@ export function toReplayRepository(
|
|||||||
id,
|
id,
|
||||||
role: message.role,
|
role: message.role,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
content: toThreadMessageContent(parts, elapsedMs),
|
content: toThreadMessageContent(replayParts, elapsedMs),
|
||||||
...(message.role === "assistant"
|
...(message.role === "assistant"
|
||||||
? {
|
? {
|
||||||
status: { type: "complete", reason: "stop" },
|
status: { type: "complete", reason: "stop" },
|
||||||
@@ -797,6 +798,28 @@ function attachStageNote(input: unknown, stageNote?: string) {
|
|||||||
return { value: input, __claw_stage_note: note };
|
return { value: input, __claw_stage_note: note };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withReplayElapsedMs(parts: UIMessage["parts"], elapsedMs?: number) {
|
||||||
|
if (elapsedMs === undefined) return parts;
|
||||||
|
return parts.map((part) => {
|
||||||
|
if (part.type === "reasoning") return { ...part, elapsedMs };
|
||||||
|
if (part.type === "dynamic-tool") {
|
||||||
|
return {
|
||||||
|
...part,
|
||||||
|
elapsedMs,
|
||||||
|
input: attachReplayElapsed(part.input ?? {}, elapsedMs),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
}) as UIMessage["parts"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachReplayElapsed(input: unknown, elapsedMs: number) {
|
||||||
|
if (input && typeof input === "object" && !Array.isArray(input)) {
|
||||||
|
return { ...input, __claw_elapsed_ms: elapsedMs };
|
||||||
|
}
|
||||||
|
return { value: input, __claw_elapsed_ms: elapsedMs };
|
||||||
|
}
|
||||||
|
|
||||||
function toThreadMessageContent(parts: UIMessage["parts"], elapsedMs?: number) {
|
function toThreadMessageContent(parts: UIMessage["parts"], elapsedMs?: number) {
|
||||||
return parts.map((part) => {
|
return parts.map((part) => {
|
||||||
const elapsedPayload = elapsedMs === undefined ? {} : { elapsedMs };
|
const elapsedPayload = elapsedMs === undefined ? {} : { elapsedMs };
|
||||||
@@ -808,7 +831,7 @@ function toThreadMessageContent(parts: UIMessage["parts"], elapsedMs?: number) {
|
|||||||
toolName: part.toolName,
|
toolName: part.toolName,
|
||||||
toolCallId: part.toolCallId,
|
toolCallId: part.toolCallId,
|
||||||
args: part.input ?? {},
|
args: part.input ?? {},
|
||||||
argsText: JSON.stringify(part.input ?? {}),
|
argsText: JSON.stringify(stripReplayElapsed(part.input ?? {})),
|
||||||
...elapsedPayload,
|
...elapsedPayload,
|
||||||
...(part.state === "output-available" ? { result: part.output } : {}),
|
...(part.state === "output-available" ? { result: part.output } : {}),
|
||||||
};
|
};
|
||||||
@@ -817,6 +840,15 @@ function toThreadMessageContent(parts: UIMessage["parts"], elapsedMs?: number) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripReplayElapsed(value: unknown): unknown {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
||||||
|
const { __claw_elapsed_ms: _elapsedMs, ...rest } = value as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
function parseToolInput(value: unknown) {
|
function parseToolInput(value: unknown) {
|
||||||
if (typeof value !== "string") return value ?? {};
|
if (typeof value !== "string") return value ?? {};
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1316,10 +1316,7 @@ const ActivityChainSummary: FC<{
|
|||||||
);
|
);
|
||||||
const storedElapsedMs = useAuiState((s) => {
|
const storedElapsedMs = useAuiState((s) => {
|
||||||
const value = readMetadataValue(s.message.metadata, "elapsedMs");
|
const value = readMetadataValue(s.message.metadata, "elapsedMs");
|
||||||
const contentValue = findNumberInActivityParts(
|
const contentValue = findElapsedMsInActivityParts(s.message.content);
|
||||||
s.message.content,
|
|
||||||
"elapsedMs",
|
|
||||||
);
|
|
||||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||||
return contentValue;
|
return contentValue;
|
||||||
}
|
}
|
||||||
@@ -1397,6 +1394,21 @@ function findNumberInActivityParts(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findElapsedMsInActivityParts(content: readonly ActivitySummaryPart[]) {
|
||||||
|
for (const part of content) {
|
||||||
|
if (typeof part.elapsedMs === "number" && Number.isFinite(part.elapsedMs)) {
|
||||||
|
return Math.max(0, Math.round(part.elapsedMs));
|
||||||
|
}
|
||||||
|
const args = part.args;
|
||||||
|
if (!args || typeof args !== "object" || Array.isArray(args)) continue;
|
||||||
|
const value = (args as Record<string, unknown>).__claw_elapsed_ms;
|
||||||
|
if (typeof value === "number" && Number.isFinite(value)) {
|
||||||
|
return Math.max(0, Math.round(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function summarizeActivityChainLabel(
|
function summarizeActivityChainLabel(
|
||||||
content: readonly ActivitySummaryPart[],
|
content: readonly ActivitySummaryPart[],
|
||||||
status: string | undefined,
|
status: string | undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user