1import { describe, expect, it } from "vite-plus/test";
2
3import { summarizeT3ToolCalls, type T3ToolSummaryCall } from "./t3ToolSummary.ts";
4
5function completed(input: unknown, output?: unknown): T3ToolSummaryCall {
6 return { input, output, outcome: "completed" };
7}
8
9describe("summarizeT3ToolCalls", () => {
10 it("reads provider result envelopes without treating JSON in the message as result data", () => {
11 const result = { messageId: "message-1", threadId: "actual-thread" };
12 const json = JSON.stringify(result);
13 const outputs = [
14 result,
15 { structuredContent: result },
16 [{ type: "text", text: json }],
17 { content: [{ text: { text: json } }], isError: false },
18 json,
19 ];
20 const calls = outputs.map((output) =>
21 completed(
22 {
23 toolName: "t3_thread_send",
24 args: { threadId: "input-thread", message: '{"threadId":"fake"}' },
25 },
26 output,
27 ),
28 );
29 expect(summarizeT3ToolCalls("thread-send", calls).label).toBe("Sent 1 message to 1 thread");
30 expect(
31 summarizeT3ToolCalls("thread-send", [
32 completed({ toolName: "t3_thread_send", args: { threadId: "input-thread" } }),
33 completed({ threadId: "input-thread" }),
34 ]).label,
35 ).toBe("Sent 2 messages to 1 thread");
36 });
37
38 it("counts the pull requests batch link and unlink calls changed", () => {
39 const urls = ["https://github.com/o/r/pull/1", "https://github.com/o/r/pull/1", "not-a-pr"];
40 const links = completed({ urls }, { linkedCount: 1 });
41 expect(summarizeT3ToolCalls("link-pr", [links, completed({ number: 3 })]).label).toBe(
42 "Linked 2 pull requests",
43 );
44 expect(summarizeT3ToolCalls("link-pr", [completed({ urls }, {})]).label).toBe(
45 "Linked pull requests",
46 );
47 const batch = completed({ states: ["merged"] }, { unlinkedCount: 2 });
48 expect(summarizeT3ToolCalls("unlink-pr", [batch, completed({ number: 3 })]).label).toBe(
49 "Unlinked 3 pull requests",
50 );
51 const uncounted = completed({ states: ["merged"] }, { isError: false });
52 expect(summarizeT3ToolCalls("unlink-pr", [uncounted]).label).toBe("Unlinked pull requests");
53 });
54
55 it("excludes failed and unfinished sends even if the provider reports completed", () => {
56 const calls: T3ToolSummaryCall[] = [
57 completed({ threadId: "success" }, { messageId: "ok", threadId: "success" }),
58 completed(
59 { threadId: "failed" },
60 { isError: true, structuredContent: { threadId: "failed" } },
61 ),
62 completed({ threadId: "failed" }, [
63 { type: "text", text: JSON.stringify({ _tag: "OrchestratorMcpFailure" }) },
64 ]),
65 { input: { threadId: "cancelled" }, output: undefined, outcome: "unfinished" },
66 ];
67 expect(summarizeT3ToolCalls("thread-send", calls)).toEqual({
68 label: "Sent 1 message to 1 thread",
69 failedCount: 2,
70 });
71 expect(summarizeT3ToolCalls("thread-send", [calls[1]!])).toEqual({
72 label: "Tried to send 1 message to 1 thread",
73 failedCount: 1,
74 });
75 });
76
77 it("does not confuse a child's failure or wait timeout with failure of the orchestration call", () => {
78 const failedChild = { taskId: "task-1", status: "failed", summary: "command not found" };
79 expect(
80 summarizeT3ToolCalls("delegate", [completed({}, failedChild), completed({}, failedChild)]),
81 ).toEqual({
82 label: "Delegated 1 task",
83 failedCount: 0,
84 });
85 expect(
86 summarizeT3ToolCalls(
87 "task-status",
88 Array.from({ length: 4 }, () => completed({ taskId: "task-1" }, failedChild)),
89 ).label,
90 ).toBe("Checked task status 4 times");
91 expect(
92 summarizeT3ToolCalls("thread-wait", [
93 completed({ threadId: "thread-1" }, { threadId: "thread-1", timedOut: true }),
94 ]),
95 ).toEqual({
96 label: "Waited on 1 thread",
97 failedCount: 0,
98 });
99 });
100});