| 1 | import assert from "node:assert/strict"; |
| 2 | import { classifyTool, shellDisplayName, type ToolItem } from "../lib/chatToolPresentation"; |
| 3 | import { deriveTurnFiles, fileIdentity } from "../lib/turnFiles"; |
| 4 | import { fileResourceCapabilities, type FileResourceRef } from "../lib/fileResource"; |
| 5 | import { subjectOf, summarize } from "../lib/tools"; |
| 6 | import { historyMessagesToItems } from "../lib/historyItems"; |
| 7 | |
| 8 | const tool = (overrides: Partial<ToolItem>): ToolItem => ({ |
| 9 | kind: "tool", id: "call", name: "unknown", args: "{}", readOnly: true, status: "done", ...overrides, |
| 10 | }); |
| 11 | |
| 12 | assert.equal(classifyTool(tool({ name: "bash", isShell: true, execution: { shell: "pwsh" } })), "shell"); |
| 13 | for (const name of ["pwsh", "PWSH", "PowerShell", "powershell", "bash", "BASH", "shell"]) { |
| 14 | const args = JSON.stringify({ command: "Write-Output example" }); |
| 15 | assert.equal(classifyTool(tool({ name })), "shell", `live ${name} has a terminal renderer before metadata arrives`); |
| 16 | assert.equal(subjectOf(name, args), "Write-Output example"); |
| 17 | assert.equal(summarize(name, args, "one\ntwo\n"), summarize("bash", args, "one\ntwo\n")); |
| 18 | const history = historyMessagesToItems([ |
| 19 | { role: "assistant", content: "", toolCalls: [{ id: "call", name, arguments: args }] }, |
| 20 | { role: "tool", content: "example", toolName: name, toolCallId: "call" }, |
| 21 | ], "test"); |
| 22 | const call = history.items.find(item => item.kind === "tool"); |
| 23 | assert.ok(call?.kind === "tool" && call.isShell, `restored ${name} remains a shell`); |
| 24 | } |
| 25 | assert.equal(shellDisplayName(tool({ name: "pwsh" })), "PowerShell"); |
| 26 | assert.equal(classifyTool(tool({ name: "mcp_pwsh" })), "tool"); |
| 27 | assert.equal(shellDisplayName(tool({ name: "bash", isShell: true, execution: { shell: "pwsh" } })), "PowerShell"); |
| 28 | assert.equal(shellDisplayName(tool({ name: "bash", isShell: true, execution: { shell: "zsh" } })), "Zsh"); |
| 29 | assert.equal(shellDisplayName(tool({ name: "bash", isShell: true })), "Terminal"); |
| 30 | assert.equal(classifyTool(tool({ name: "write_file" })), "file"); |
| 31 | assert.equal(classifyTool(tool({ name: "plugin_write_report", capabilityId: "mcp-tool:plugin:write" })), "tool", |
| 32 | "untrusted names containing write do not acquire the native file renderer"); |
| 33 | |
| 34 | const calls: ToolItem[] = [ |
| 35 | tool({ id: "write", name: "write_file", args: '{"path":"./docs/guide.md","content":"hi"}', readOnly: false }), |
| 36 | tool({ id: "edit", name: "edit_file", args: '{"path":"docs/./guide.md"}', readOnly: false }), |
| 37 | tool({ id: "noop", name: "write_file", args: '{"path":"noop.txt"}', output: "noop.txt already contains the exact content; no changes made", readOnly: false }), |
| 38 | tool({ id: "failed", name: "edit_file", args: '{"path":"failed.txt"}', error: "not found", status: "error", readOnly: false }), |
| 39 | tool({ id: "shell", name: "bash", args: '{"command":"touch guessed.txt"}', readOnly: false, isShell: true }), |
| 40 | tool({ id: "read", name: "read_file", args: '{"path":"read.txt"}', readOnly: true }), |
| 41 | tool({ id: "move", name: "move_file", args: '{"source_path":"old.txt","destination_path":"new.txt"}', readOnly: false }), |
| 42 | ]; |
| 43 | assert.deepEqual(deriveTurnFiles(calls), [ |
| 44 | { path: "./docs/guide.md", toolCallId: "edit", operation: "modified" }, |
| 45 | { path: "new.txt", toolCallId: "move", operation: "written" }, |
| 46 | ]); |
| 47 | assert.equal(fileIdentity("a/./b/../c.txt"), "a/c.txt"); |
| 48 | // Capabilities answer for a caller-shaped reference, and read only its host and |
| 49 | // name: the origin and the tool call never change what the row may offer. |
| 50 | const remoteReportRef: FileResourceRef = { source: "workspace", hostId: "remote-a", tabId: "tab", toolCallId: "write", path: "report.md" }; |
| 51 | assert.deepEqual(fileResourceCapabilities(remoteReportRef), { |
| 52 | preview: true, source: true, browser: false, revealTree: true, copyPath: true, openNative: false, revealNative: false, saveCopy: true, |
| 53 | }); |
| 54 | const localPresentedRef: FileResourceRef = { source: "presented", hostId: "local", tabId: "tab", toolCallId: "present", path: "app.html" }; |
| 55 | assert.equal(fileResourceCapabilities(localPresentedRef).browser, true); |
| 56 | |
| 57 | console.log("chat tool presentation: trusted renderer matching, shell labels and file facts passed"); |
| 58 | |
| 59 | const { historyToolStatus } = await import("../lib/historyToolStatus"); |
| 60 | const { toolPresentation } = await import("../lib/chatToolPresentation"); |
| 61 | assert.equal(historyToolStatus(undefined), "unknown", "unloaded results must not become stopped"); |
| 62 | assert.equal(historyToolStatus(undefined, { id: "call", name: "bash", arguments: "{}", resultObservation: { state: "completed", messageId: "result", version: 1 } }), "done"); |
| 63 | assert.equal(toolPresentation(tool({ status: "unknown", resultMissing: true })).state, "unknown"); |
| 64 | assert.equal(historyToolStatus({ role: "tool", content: "", execution: { state: "cancelled" } }), "stopped"); |
| 65 |