summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/logic
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/chat/logic')
-rw-r--r--src/features/chat/logic/tool-summary.test.ts41
-rw-r--r--src/features/chat/logic/tool-summary.ts57
2 files changed, 97 insertions, 1 deletions
diff --git a/src/features/chat/logic/tool-summary.test.ts b/src/features/chat/logic/tool-summary.test.ts
index 31c7002..b6c7e0b 100644
--- a/src/features/chat/logic/tool-summary.test.ts
+++ b/src/features/chat/logic/tool-summary.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { ToolEntry } from "../../../core/chunks";
-import { toolLabel, viewToolEntry } from "./tool-summary";
+import { toolIcon, toolLabel, viewToolEntry } from "./tool-summary";
function entry(
name: string,
@@ -61,6 +61,45 @@ describe("tool labels", () => {
});
});
+describe("tool icons", () => {
+ const builtIns = [
+ "run_shell",
+ "read_file",
+ "edit_file",
+ "write_file",
+ "web_search",
+ "lsp",
+ "todo_write",
+ "load_skill",
+ "consult_vision",
+ "read_image",
+ "youtube_transcript",
+ "summon_agent",
+ "send_agent_message",
+ "read_agent",
+ "open_agent",
+ "cancel_agent_message",
+ "list_agents",
+ "list_agent_models",
+ ];
+ it("gives every built-in tool its own distinct icon", () => {
+ const icons = builtIns.map(toolIcon);
+ expect(icons).not.toContain("tool");
+ expect(icons).not.toContain("mcp");
+ expect(new Set(icons).size).toBe(builtIns.length);
+ expect(view("run_shell", {}).icon).toBe("terminal");
+ });
+ it.each([
+ ["github__create_issue", "mcp"],
+ ["mcp__github__create_issue", "mcp"],
+ ["scaffold_echo", "tool"],
+ ["__proto__", "tool"],
+ ["constructor", "tool"],
+ ])("%s → %s", (name, icon) => {
+ expect(toolIcon(name)).toBe(icon);
+ });
+});
+
describe("tool summaries", () => {
it.each([
["run_shell", { command: "bun run test\n --run" }, "bun run test --run"],
diff --git a/src/features/chat/logic/tool-summary.ts b/src/features/chat/logic/tool-summary.ts
index 75682de..566e40f 100644
--- a/src/features/chat/logic/tool-summary.ts
+++ b/src/features/chat/logic/tool-summary.ts
@@ -5,6 +5,8 @@ export interface ToolCardView {
readonly name: string;
/** English display name, e.g. `run_shell` → "Run command" (see {@link toolLabel}). */
readonly label: string;
+ /** Which icon the card shows — a semantic key; the UI maps it to a Phosphor icon. */
+ readonly icon: ToolIconKey;
readonly status: "pending" | "ok" | "error";
readonly callSummary: string;
readonly callBadges: readonly {
@@ -157,6 +159,60 @@ const TOOL_LABELS: Readonly<Record<string, string>> = {
list_agent_models: "List agent models",
};
+/**
+ * Semantic icon keys (Svelte-free; `ui/tool-icons.ts` maps each to a Phosphor
+ * component). Unknown tools get `mcp` when namespaced `<server>__<tool>`,
+ * else the generic `tool` wrench.
+ */
+export type ToolIconKey =
+ | "terminal"
+ | "file-read"
+ | "file-edit"
+ | "file-write"
+ | "search"
+ | "code"
+ | "todos"
+ | "skill"
+ | "vision"
+ | "image"
+ | "youtube"
+ | "agent-summon"
+ | "agent-send"
+ | "agent-read"
+ | "agent-open"
+ | "agent-cancel"
+ | "agents"
+ | "models"
+ | "mcp"
+ | "tool";
+
+const TOOL_ICONS: Readonly<Record<string, ToolIconKey>> = {
+ run_shell: "terminal",
+ read_file: "file-read",
+ edit_file: "file-edit",
+ write_file: "file-write",
+ web_search: "search",
+ lsp: "code",
+ todo_write: "todos",
+ load_skill: "skill",
+ consult_vision: "vision",
+ read_image: "image",
+ youtube_transcript: "youtube",
+ summon_agent: "agent-summon",
+ send_agent_message: "agent-send",
+ read_agent: "agent-read",
+ open_agent: "agent-open",
+ cancel_agent_message: "agent-cancel",
+ list_agents: "agents",
+ list_agent_models: "models",
+};
+
+/** The icon key for a tool (built-in table, else MCP plug / generic wrench). */
+export function toolIcon(name: string): ToolIconKey {
+ if (Object.hasOwn(TOOL_ICONS, name)) return TOOL_ICONS[name] ?? "tool";
+ return name.split("__").filter((p) => p !== "").length >= 2 ? "mcp" : "tool";
+}
+
/** `create_issue` / `createIssue` / `create-issue` → "Create issue". */
function sentenceCase(raw: string): string {
const words = raw
@@ -352,6 +408,7 @@ export function viewToolEntry(entry: ToolEntry): ToolCardView {
return {
name: entry.toolName,
label: toolLabel(entry.toolName),
+ icon: toolIcon(entry.toolName),
status: result === null ? "pending" : result.isError ? "error" : "ok",
callSummary,
callBadges,