Files
paseo/packages/app/src/tool-calls/grouping.test.ts
T
Matt CowgerandGitHub 81113d1582 feat(app): add tool call detail levels (#2031)
* feat(app): compact tool call sequences

* feat(app): summarize compact tool activity

* feat(app): add tool call detail levels

* fix(app): stabilize compact tool groups

* fix(app): address compact tool review feedback
2026-07-14 18:55:06 +08:00

265 lines
9.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { ToolCallDetail } from "@getpaseo/protocol/agent-types";
import type { StreamItem, ToolCallItem } from "@/types/stream";
import { compactToolCallRuns } from "./grouping";
function toolCall(
id: string,
detail: ToolCallDetail,
options: {
name?: string;
status?: "running" | "completed" | "failed" | "canceled";
} = {},
): ToolCallItem {
return {
kind: "tool_call",
id,
timestamp: new Date(`2026-01-01T00:00:${id.padStart(2, "0")}.000Z`),
payload: {
source: "agent",
data: {
provider: "claude",
callId: id,
name: options.name ?? detail.type,
status: options.status ?? "completed",
error: options.status === "failed" ? "boom" : null,
detail,
},
},
};
}
function assistant(id: string): StreamItem {
return {
kind: "assistant_message",
id,
text: id,
timestamp: new Date("2026-01-01T00:01:00.000Z"),
};
}
describe("compactToolCallRuns", () => {
it("preserves the original arrays when compaction is disabled", () => {
const tail = [toolCall("1", { type: "shell", command: "one" })];
const head = [toolCall("2", { type: "shell", command: "two" })];
const result = compactToolCallRuns({ tail, head, enabled: false });
expect(result.tail).toBe(tail);
expect(result.head).toBe(head);
expect(result.groupsByHostId.size).toBe(0);
});
it("replaces four contiguous calls with a stable first-call host and latest timestamp", () => {
const calls = [
toolCall("1", { type: "shell", command: "one" }),
toolCall("2", { type: "shell", command: "two" }),
toolCall("3", { type: "read", filePath: "/repo/src/a.ts" }),
toolCall("4", { type: "edit", filePath: "/repo/src/a.ts" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
expect(result.tail).toHaveLength(1);
expect(result.tail[0]).toMatchObject({ id: "1", timestamp: calls[3]?.timestamp });
expect(result.head).toEqual([]);
expect(result.groupsByHostId.get("1")?.id).toBe("1");
expect(result.groupsByHostId.get("1")?.calls).toEqual(calls);
expect(result.groupsByHostId.get("1")).toMatchObject({
editedFileCount: 1,
commandCount: 2,
readFileCount: 1,
otherToolCount: 0,
categories: [
{ key: "shell", label: "Shell", callCount: 2, resources: [] },
{ key: "read", label: "Read", callCount: 1, resources: ["a.ts"] },
{ key: "edit", label: "Edit", callCount: 1, resources: ["a.ts"] },
],
});
const nextCall = toolCall("5", { type: "shell", command: "five" }, { status: "running" });
const updated = compactToolCallRuns({
tail: [...calls, nextCall],
head: [],
enabled: true,
});
expect(updated.tail[0]).toMatchObject({ id: "1", timestamp: nextCall.timestamp });
expect(updated.groupsByHostId.get("1")?.id).toBe("1");
});
it("does not compact short runs and stops at visible content boundaries", () => {
const firstRun = [
toolCall("1", { type: "shell", command: "one" }),
toolCall("2", { type: "shell", command: "two" }),
toolCall("3", { type: "shell", command: "three" }),
];
const boundary = assistant("assistant");
const secondRun = [
toolCall("4", { type: "read", filePath: "/repo/a.ts" }),
toolCall("5", { type: "read", filePath: "/repo/b.ts" }),
toolCall("6", { type: "read", filePath: "/repo/c.ts" }),
toolCall("7", { type: "read", filePath: "/repo/d.ts" }),
];
const result = compactToolCallRuns({
tail: [...firstRun, boundary, ...secondRun],
head: [],
enabled: true,
});
expect(result.tail.slice(0, -1)).toEqual([...firstRun, boundary]);
expect(result.tail.at(-1)).toMatchObject({ id: "4", timestamp: secondRun[3]?.timestamp });
expect([...result.groupsByHostId.keys()]).toEqual(["4"]);
});
it("forms one group across the history and live-head boundary", () => {
const tail = [
assistant("assistant"),
toolCall("1", { type: "shell", command: "one" }),
toolCall("2", { type: "shell", command: "two" }),
];
const head = [
toolCall("3", { type: "read", filePath: "/repo/a.ts" }),
toolCall("4", { type: "edit", filePath: "/repo/a.ts" }, { status: "running" }),
];
const result = compactToolCallRuns({ tail, head, enabled: true });
expect(result.tail).toEqual([tail[0]]);
expect(result.head).toHaveLength(1);
expect(result.head[0]).toMatchObject({ id: "1", timestamp: head[1]?.timestamp });
expect(result.groupsByHostId.get("1")?.calls).toEqual([...tail.slice(1), ...head]);
expect(result.groupsByHostId.get("1")?.isRunning).toBe(true);
});
it("separates reads and searches from other tools", () => {
const calls = [
toolCall("1", { type: "read", filePath: "/repo/src/a.ts" }),
toolCall("2", { type: "read", filePath: "C:\\repo\\src\\beta.ts" }),
toolCall("3", { type: "fetch", url: "https://github.com/org/repo" }),
toolCall(
"4",
{ type: "search", query: "paseo", toolName: "web_search" },
{ status: "failed" },
),
toolCall("5", { type: "fetch", url: "not a url" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
const group = result.groupsByHostId.get("1");
expect(group).toMatchObject({
failedCount: 1,
isRunning: false,
editedFileCount: 0,
commandCount: 0,
readFileCount: 2,
searchCount: 1,
otherToolCount: 2,
});
});
it("counts unique edited files while counting each shell command", () => {
const calls = [
toolCall("1", { type: "edit", filePath: "/repo/a.ts" }),
toolCall("2", { type: "edit", filePath: "/repo/a.ts" }),
toolCall("3", { type: "write", filePath: "/repo/b.ts" }),
toolCall("4", { type: "shell", command: "npm test" }),
toolCall("5", { type: "shell", command: "npm run lint" }),
toolCall("6", { type: "read", filePath: "/repo/c.ts" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
expect(result.groupsByHostId.get("1")).toMatchObject({
editedFileCount: 2,
commandCount: 2,
readFileCount: 1,
otherToolCount: 0,
});
});
it("counts Paseo calls separately from other tools", () => {
const calls = [
toolCall("1", { type: "unknown", input: null, output: null }, { name: "paseo.list_agents" }),
toolCall(
"2",
{ type: "unknown", input: null, output: null },
{ name: "mcp__paseo__list_worktrees" },
),
toolCall("3", { type: "fetch", url: "https://paseo.sh" }),
toolCall("4", { type: "fetch", url: "https://github.com/getpaseo" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
expect(result.groupsByHostId.get("1")).toMatchObject({
otherToolCount: 2,
paseoCallCount: 2,
});
});
it("classifies direct Brave and Paseo runtime tool names", () => {
const unknownDetail = { type: "unknown" as const, input: null, output: null };
const calls = [
toolCall("1", unknownDetail, { name: "brave-search_brave_web_search" }),
toolCall("2", unknownDetail, { name: "brave-search_brave_llm_context" }),
toolCall("3", unknownDetail, { name: "paseo_list_providers" }),
toolCall("4", unknownDetail, { name: "paseo_list_worktrees" }),
toolCall("5", unknownDetail, { name: "paseo_list_worktrees" }),
toolCall("6", unknownDetail, { name: "mcp__exa__web_search" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
expect(result.groupsByHostId.get("1")).toMatchObject({
searchCount: 3,
otherToolCount: 0,
paseoCallCount: 3,
});
});
it("keeps plan and spoken-message calls outside compact groups", () => {
const shellCalls = ["1", "2", "3", "4"].map((id) =>
toolCall(id, { type: "shell", command: id }),
);
const plan = toolCall("5", { type: "plan", text: "Plan" });
const speak = toolCall(
"6",
{ type: "unknown", input: "Hello", output: null },
{ name: "speak" },
);
const result = compactToolCallRuns({
tail: [...shellCalls, plan, speak],
head: [],
enabled: true,
});
expect(result.tail[0]).toMatchObject({ id: "1", timestamp: shellCalls[3]?.timestamp });
expect(result.tail.slice(1)).toEqual([plan, speak]);
expect(result.groupsByHostId.get("1")?.calls).toEqual(shellCalls);
});
it("deduplicates file resources by full path while displaying basenames", () => {
const calls = [
toolCall("1", { type: "read", filePath: "/repo/src/index.ts" }),
toolCall("2", { type: "read", filePath: "/repo/tests/index.ts" }),
toolCall("3", { type: "read", filePath: "/repo/src/other.ts" }),
toolCall("4", { type: "read", filePath: "/repo/src/index.ts" }),
];
const result = compactToolCallRuns({ tail: calls, head: [], enabled: true });
expect(result.groupsByHostId.get("1")).toMatchObject({
readFileCount: 3,
categories: [
{
key: "read",
resources: ["index.ts", "index.ts", "other.ts"],
},
],
});
});
});