summaryrefslogtreecommitdiffhomepage
path: root/packages/core/tests/tools/read-tab.test.ts
blob: 71e419c74a10bd50cc049df70e483300bf85754b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { describe, expect, it } from "vitest";
import { createReadTabTool, type ReadTabCallbacks } from "../../src/tools/read-tab.js";
import type { TabResolution } from "../../src/tools/send-to-tab.js";

function makeCallbacks(overrides: Partial<ReadTabCallbacks> = {}): ReadTabCallbacks {
	return {
		resolveShortId: (): TabResolution => ({
			status: "ok",
			tab: { id: "target-id", title: "Target", handle: "targ" },
		}),
		getLastResponse: () => ({ text: "the answer is 42", status: "idle" }),
		listOpenHandles: () => [{ handle: "targ", title: "Target" }],
		...overrides,
	};
}

describe("createReadTabTool — schema & description", () => {
	it("is a non-blocking snapshot read", () => {
		const tool = createReadTabTool(makeCallbacks());
		expect(tool.name).toBe("read_tab");
		expect(tool.description).toContain("SNAPSHOT");
		expect(tool.description.toLowerCase()).toContain("does not block");
	});
});

describe("createReadTabTool — execute()", () => {
	it("returns the last assistant response wrapped in a tab_response tag", async () => {
		const tool = createReadTabTool(makeCallbacks());
		const out = await tool.execute({ tab_id: "targ" });
		expect(out).toContain("<tab_response");
		expect(out).toContain('tab="targ"');
		expect(out).toContain('status="idle"');
		expect(out).toContain("the answer is 42");
		expect(out).toContain("</tab_response>");
	});

	it("notes that a running tab's response is its previous completed turn", async () => {
		const tool = createReadTabTool(
			makeCallbacks({
				getLastResponse: () => ({ text: "older turn", status: "running" }),
			}),
		);
		const out = await tool.execute({ tab_id: "targ" });
		expect(out).toContain("still running");
		expect(out).toContain("older turn");
	});

	it("explains when a tab has no completed response yet (idle)", async () => {
		const tool = createReadTabTool(
			makeCallbacks({
				getLastResponse: () => ({ text: null, status: "idle" }),
			}),
		);
		const out = await tool.execute({ tab_id: "targ" });
		expect(out).toContain("no completed response");
		expect(out).toContain("no assistant responses yet");
	});

	it("explains when a tab is still on its first turn (running, no prior text)", async () => {
		const tool = createReadTabTool(
			makeCallbacks({
				getLastResponse: () => ({ text: null, status: "running" }),
			}),
		);
		const out = await tool.execute({ tab_id: "targ" });
		expect(out).toContain("no completed response");
		expect(out).toContain("still working on its first turn");
	});

	it("rejects an empty tab_id and lists open handles", async () => {
		const tool = createReadTabTool(makeCallbacks());
		const out = await tool.execute({ tab_id: "" });
		expect(out).toContain("Error");
		expect(out).toContain("targ");
	});

	it("returns a helpful error when the id is unknown", async () => {
		const tool = createReadTabTool(makeCallbacks({ resolveShortId: () => ({ status: "none" }) }));
		const out = await tool.execute({ tab_id: "zzzz" });
		expect(out).toContain("no open tab matches");
		expect(out).toContain("Currently open tabs:");
	});

	it("asks for more characters when the id is ambiguous", async () => {
		const tool = createReadTabTool(
			makeCallbacks({
				resolveShortId: () => ({
					status: "ambiguous",
					matches: [
						{ id: "a1", title: "One", handle: "abcd1" },
						{ id: "a2", title: "Two", handle: "abcd2" },
					],
				}),
			}),
		);
		const out = await tool.execute({ tab_id: "abcd" });
		expect(out).toContain("ambiguous");
		expect(out).toContain("abcd1");
		expect(out).toContain("abcd2");
	});
});