| 1 | import type { Item } from "./useController"; |
| 2 | import { isShellToolName, isPowerShellToolName } from "./shellToolIdentity"; |
| 3 | |
| 4 | export type ToolItem = Extract<Item, { kind: "tool" }>; |
| 5 | export type ToolPresentationKind = "search" | "web" | "shell" | "agent" | "file" | "present" | "tool"; |
| 6 | |
| 7 | /** Execution metadata is authoritative, including old timeouts carrying a false zero exit code. */ |
| 8 | export function toolPresentation(item: ToolItem): { |
| 9 | state: ToolItem["status"]; dot: "ongoing" | "done" | "error" | "warning" | "idle"; |
| 10 | label: "chat.running" | "chat.done" | "chat.failed" | "chat.stopped" | "chat.timedOut" | "chat.notRun" | "chat.background" | "chat.unknown"; |
| 11 | exitCode?: number; |
| 12 | } { |
| 13 | const execution = item.execution; |
| 14 | switch (execution?.state) { |
| 15 | case "timed_out": return { state: "error", dot: "error", label: "chat.timedOut" }; |
| 16 | case "cancelled": return { state: "stopped", dot: "warning", label: "chat.stopped" }; |
| 17 | case "not_run": return { state: "stopped", dot: "warning", label: "chat.notRun" }; |
| 18 | case "background_started": return { state: "stopped", dot: "ongoing", label: "chat.background" }; |
| 19 | case "failed": return { state: "error", dot: "error", label: "chat.failed", exitCode: execution.exitCode }; |
| 20 | case "completed": |
| 21 | if (execution.exitCode == null) return { state: "unknown", dot: "idle", label: "chat.unknown" }; |
| 22 | return execution.exitCode === 0 ? { state: "done", dot: "done", label: "chat.done", exitCode: 0 } |
| 23 | : { state: "error", dot: "error", label: "chat.failed", exitCode: execution.exitCode }; |
| 24 | } |
| 25 | if (item.status === "running") return { state: "running", dot: "ongoing", label: "chat.running" }; |
| 26 | if (item.resultMissing || item.status === "unknown") return { state: "unknown", dot: "idle", label: "chat.unknown" }; |
| 27 | if (item.status === "error" || item.error || (execution?.exitCode != null && execution.exitCode !== 0)) { |
| 28 | return { state: "error", dot: "error", label: "chat.failed", exitCode: execution?.exitCode }; |
| 29 | } |
| 30 | if (item.status === "stopped") return { state: "stopped", dot: "warning", label: "chat.stopped" }; |
| 31 | if (classifyTool(item) === "shell" && (execution?.exitCode == null || (execution.state && execution.state !== "running"))) { |
| 32 | return { state: "unknown", dot: "idle", label: "chat.unknown" }; |
| 33 | } |
| 34 | return { state: "done", dot: "done", label: "chat.done", exitCode: execution?.exitCode }; |
| 35 | } |
| 36 | |
| 37 | const AGENT_TOOLS = new Set(["task", "read_only_task", "parallel_tasks", "fleet", "subagent"]); |
| 38 | const FILE_TOOLS = new Set([ |
| 39 | "read_file", "glob", "grep", "ls", "code_index", |
| 40 | "write_file", "edit_file", "multi_edit", "move_file", "notebook_edit", "delete_range", "delete_symbol", |
| 41 | ]); |
| 42 | |
| 43 | /** |
| 44 | * Resolve a renderer from trusted built-in identities. A plugin whose display |
| 45 | * name happens to contain "read" or "write" must remain a generic tool. |
| 46 | */ |
| 47 | export function classifyTool(item: ToolItem): ToolPresentationKind { |
| 48 | if (item.name === "present") return "present"; |
| 49 | if (item.name === "web_search") return "search"; |
| 50 | if (item.name === "web_fetch") return "web"; |
| 51 | if (isShellToolName(item.name) || item.isShell) return "shell"; |
| 52 | if (AGENT_TOOLS.has(item.name)) return "agent"; |
| 53 | if (FILE_TOOLS.has(item.name)) return "file"; |
| 54 | return "tool"; |
| 55 | } |
| 56 | |
| 57 | export function shellDisplayName(item: ToolItem): string { |
| 58 | const shell = item.execution?.shell?.trim().toLowerCase() || (isPowerShellToolName(item.name) ? "pwsh" : ""); |
| 59 | if (shell === "powershell" || shell === "pwsh") return "PowerShell"; |
| 60 | if (shell === "git-bash") return "Git Bash"; |
| 61 | if (shell === "bash") return "Bash"; |
| 62 | if (shell === "zsh") return "Zsh"; |
| 63 | if (shell === "sh") return "Shell"; |
| 64 | return "Terminal"; |
| 65 | } |
| 66 |