fix webui tool trace dedupe
This commit is contained in:
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useClient } from "@/providers/ClientProvider";
|
||||
import { toMediaAttachment } from "@/lib/media";
|
||||
import { toolTraceLinesFromEvents } from "@/lib/tool-traces";
|
||||
import { mergeUniqueToolTraceLines, toolTraceLinesFromEvents } from "@/lib/tool-traces";
|
||||
import type { StreamError } from "@/lib/nanobot-client";
|
||||
import type {
|
||||
InboundEvent,
|
||||
@@ -652,10 +652,16 @@ export function useNanobotStream(
|
||||
: last.content
|
||||
? [last.content]
|
||||
: [];
|
||||
const mergedLines = structuredLines.length > 0
|
||||
? mergeUniqueToolTraceLines(previousTraces, structuredLines)
|
||||
: null;
|
||||
if (mergedLines && !mergedLines.added) return prev;
|
||||
const merged: UIMessage = {
|
||||
...last,
|
||||
traces: [...previousTraces, ...lines],
|
||||
content: lines[lines.length - 1],
|
||||
traces: mergedLines ? mergedLines.traces : [...previousTraces, ...lines],
|
||||
content: mergedLines
|
||||
? mergedLines.traces[mergedLines.traces.length - 1]
|
||||
: lines[lines.length - 1],
|
||||
activitySegmentId: last.activitySegmentId ?? segmentId,
|
||||
};
|
||||
return [...prev.slice(0, -1), merged];
|
||||
|
||||
@@ -44,20 +44,35 @@ const VALID_PHASES = new Set(["start", "end", "error"]);
|
||||
export function toolTraceLinesFromEvents(events: unknown): string[] {
|
||||
if (!Array.isArray(events)) return [];
|
||||
const seen = new Set<string>();
|
||||
return events
|
||||
.filter((event) => {
|
||||
if (!event || typeof event !== "object") return false;
|
||||
const phase = (event as { phase?: unknown }).phase;
|
||||
if (!(phase && typeof phase === "string" && VALID_PHASES.has(phase))) {
|
||||
return false;
|
||||
}
|
||||
const callId = (event as { call_id?: unknown }).call_id;
|
||||
if (callId && typeof callId === "string") {
|
||||
if (seen.has(callId)) return false;
|
||||
seen.add(callId);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(formatToolCallTrace)
|
||||
.filter((trace): trace is string => !!trace);
|
||||
const lines: string[] = [];
|
||||
for (const event of events) {
|
||||
if (!event || typeof event !== "object") continue;
|
||||
const phase = (event as { phase?: unknown }).phase;
|
||||
if (!(phase && typeof phase === "string" && VALID_PHASES.has(phase))) continue;
|
||||
const callId = (event as { call_id?: unknown }).call_id;
|
||||
if (callId && typeof callId === "string") {
|
||||
if (seen.has(callId)) continue;
|
||||
seen.add(callId);
|
||||
}
|
||||
const line = formatToolCallTrace(event);
|
||||
if (!line) continue;
|
||||
lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function mergeUniqueToolTraceLines(
|
||||
previousTraces: string[],
|
||||
lines: string[],
|
||||
): { traces: string[]; added: boolean } {
|
||||
const seen = new Set(previousTraces);
|
||||
const traces = [...previousTraces];
|
||||
let added = false;
|
||||
for (const line of lines) {
|
||||
if (seen.has(line)) continue;
|
||||
seen.add(line);
|
||||
traces.push(line);
|
||||
added = true;
|
||||
}
|
||||
return { traces, added };
|
||||
}
|
||||
|
||||
@@ -308,6 +308,56 @@ describe("useNanobotStream", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("dedupes finish-phase tool events after their start trace", () => {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(() => useNanobotStream("chat-tool-finish", EMPTY_MESSAGES), {
|
||||
wrapper: wrap(fake.client),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-tool-finish", {
|
||||
event: "message",
|
||||
chat_id: "chat-tool-finish",
|
||||
text: 'exec({"cmd":"ls"})',
|
||||
kind: "tool_hint",
|
||||
tool_events: [{
|
||||
phase: "start",
|
||||
call_id: "call-exec",
|
||||
name: "exec",
|
||||
arguments: { cmd: "ls" },
|
||||
}],
|
||||
});
|
||||
fake.emit("chat-tool-finish", {
|
||||
event: "message",
|
||||
chat_id: "chat-tool-finish",
|
||||
text: "",
|
||||
kind: "progress",
|
||||
tool_events: [
|
||||
{
|
||||
phase: "end",
|
||||
call_id: "call-exec",
|
||||
name: "exec",
|
||||
arguments: { cmd: "ls" },
|
||||
result: "ok",
|
||||
},
|
||||
{
|
||||
phase: "error",
|
||||
call_id: "call-read",
|
||||
name: "read_file",
|
||||
arguments: { path: "notes.md" },
|
||||
error: "missing",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.messages).toHaveLength(1);
|
||||
expect(result.current.messages[0].traces).toEqual([
|
||||
'exec({"cmd":"ls"})',
|
||||
'read_file({"path":"notes.md"})',
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders live file_edit events as their own activity trace", () => {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(() => useNanobotStream("chat-file-edit", EMPTY_MESSAGES), {
|
||||
|
||||
Reference in New Issue
Block a user