返回 DeepSeek-Reasonix
chatToolPresentation.ts
根目录 / desktop / frontend / src / lib / chatToolPresentation.ts
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
66 lines TYPESCRIPT