/** * Human-readable tool-call labels and compact argument previews. * * Overrides: * - read_file / write_file / edit_file on a `/memories/...` path → * "Reading memory" / "Updating memory". * - read_file on a `/skills//...` path → "Skill: ". * - think_tool → "Reflection". * - the tool-selector's internal ToolSelectionResponse → "Adaptive tool * selection". */ /** True when a virtual path targets the global memories directory. */ function isMemoryPath(path: string): boolean { const p = (path || "").trim(); return p === "/memories" || p.startsWith("/memories/"); } /** If a virtual path targets a skill (`/skills//...`), return ``. * An empty first segment (e.g. `/skills//x`) is treated as no match. */ function skillNameFromPath(path: string): string { const p = (path || "").trim(); if (!p.startsWith("/skills/")) return ""; const [skill] = p.slice("/skills/".length).split("/"); return skill || ""; } /** Coerce tool args to an object — they may stream in as a JSON string. */ function asArgsObject(args: unknown): Record { if (args && typeof args === "object") return args as Record; if (typeof args === "string") { try { const parsed = JSON.parse(args); if (parsed && typeof parsed === "object") { return parsed as Record; } } catch { // Partial / non-JSON args while still streaming — treat as empty. } } return {}; } /** Translated strings for {@link formatToolLabelWith}; matches the * `chat.toolLabels.*` catalog keys. */ export interface ToolLabelStrings { unknownTool: string; adaptiveToolSelection: string; reflection: string; readingMemory: string; updatingMemory: string; /** Skill label; `{name}` is the skill directory name. */ skill: (name: string) => string; execute: string; grep: string; readFile: string; writeFile: string; editFile: string; listFiles: string; task: string; } const EN_TOOL_LABELS: ToolLabelStrings = { unknownTool: "Unknown Tool", adaptiveToolSelection: "Adaptive tool selection", reflection: "Reflection", readingMemory: "Reading memory", updatingMemory: "Updating memory", skill: (name) => `Skill: ${name}`, execute: "Running command", grep: "Searching files", readFile: "Reading file", writeFile: "Writing file", editFile: "Editing file", listFiles: "Listing files", task: "Running subagent", }; export function formatToolLabelWith( labels: ToolLabelStrings, name: string, args?: unknown ): string { if (!name) return labels.unknownTool; // The tool-selector middleware's internal structured-output call. if (name === "ToolSelectionResponse") return labels.adaptiveToolSelection; const nameLower = name.toLowerCase(); if (nameLower === "think_tool") return labels.reflection; if ( nameLower === "read_file" || nameLower === "write_file" || nameLower === "edit_file" ) { const a = asArgsObject(args); const path = String(a.path ?? a.file_path ?? ""); if (isMemoryPath(path)) { return nameLower === "read_file" ? labels.readingMemory : labels.updatingMemory; } if (nameLower === "read_file") { const skill = skillNameFromPath(path); if (skill) return labels.skill(skill); } } switch (nameLower) { case "execute": return labels.execute; case "grep": return labels.grep; case "read_file": return labels.readFile; case "write_file": return labels.writeFile; case "edit_file": return labels.editFile; case "ls": case "list_files": return labels.listFiles; case "task": return labels.task; } // Everything else keeps its raw tool name. return name; } export function formatToolLabel(name: string, args?: unknown): string { return formatToolLabelWith(EN_TOOL_LABELS, name, args); } function compactValue(value: unknown): string { if (typeof value === "string") return value.replace(/\s+/g, " ").trim(); if (value === null || value === undefined) return ""; try { return JSON.stringify(value).replace(/\s+/g, " ").trim(); } catch { return String(value).replace(/\s+/g, " ").trim(); } } /** The most useful argument for a one-line activity row. */ export function toolArgumentPreview(name: string, args?: unknown): string { const a = asArgsObject(args); const nameLower = name.toLowerCase(); let value: unknown; if (nameLower === "execute") value = a.command ?? a.cmd; else if (nameLower === "grep") value = a.pattern ?? a.query; else if ( nameLower === "read_file" || nameLower === "write_file" || nameLower === "edit_file" || nameLower === "ls" || nameLower === "list_files" ) { value = a.path ?? a.file_path; } else if (nameLower === "task") { value = a.subagent_type; } if (value === undefined) { value = Object.values(a).find( (candidate) => typeof candidate === "string" || typeof candidate === "number" ); } const preview = compactValue(value); const maxLength = 88; return preview.length > maxLength ? `${preview.slice(0, maxLength - 1)}…` : preview; }