import fs from "node:fs"; import { redactToolDetail } from "../logging/redact.js"; import { shortenHomeInString } from "../utils.js"; type ToolDisplayActionSpec = { label?: string; detailKeys?: string[]; }; type ToolDisplaySpec = { emoji?: string; title?: string; label?: string; detailKeys?: string[]; actions?: Record; }; type ToolDisplayConfig = { version?: number; fallback?: ToolDisplaySpec; tools?: Record; }; export type ToolDisplay = { name: string; emoji: string; title: string; label: string; verb?: string; detail?: string; }; const TOOL_DISPLAY_CONFIG: ToolDisplayConfig = (() => { try { const raw = fs.readFileSync( new URL("./tool-display.json", import.meta.url), "utf8", ); return JSON.parse(raw) as ToolDisplayConfig; } catch { return {}; } })(); const FALLBACK = TOOL_DISPLAY_CONFIG.fallback ?? { emoji: "🧩" }; const TOOL_MAP = TOOL_DISPLAY_CONFIG.tools ?? {}; function normalizeToolName(name?: string): string { return (name ?? "tool").trim(); } function defaultTitle(name: string): string { const cleaned = name.replace(/_/g, " ").trim(); if (!cleaned) return "Tool"; return cleaned .split(/\s+/) .map((part) => part.length <= 2 && part.toUpperCase() === part ? part : `${part.at(0)?.toUpperCase() ?? ""}${part.slice(1)}`, ) .join(" "); } function normalizeVerb(value?: string): string | undefined { const trimmed = value?.trim(); if (!trimmed) return undefined; return trimmed.replace(/_/g, " "); } function coerceDisplayValue(value: unknown): string | undefined { if (value === null || value === undefined) return undefined; if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed) return undefined; const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? ""; if (!firstLine) return undefined; return firstLine.length > 160 ? `${firstLine.slice(0, 157)}…` : firstLine; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (Array.isArray(value)) { const values = value .map((item) => coerceDisplayValue(item)) .filter((item): item is string => Boolean(item)); if (values.length === 0) return undefined; const preview = values.slice(0, 3).join(", "); return values.length > 3 ? `${preview}…` : preview; } return undefined; } function lookupValueByPath(args: unknown, path: string): unknown { if (!args || typeof args !== "object") return undefined; let current: unknown = args; for (const segment of path.split(".")) { if (!segment) return undefined; if (!current || typeof current !== "object") return undefined; const record = current as Record; current = record[segment]; } return current; } function resolveDetailFromKeys( args: unknown, keys: string[], ): string | undefined { for (const key of keys) { const value = lookupValueByPath(args, key); const display = coerceDisplayValue(value); if (display) return display; } return undefined; } function resolveReadDetail(args: unknown): string | undefined { if (!args || typeof args !== "object") return undefined; const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; if (!path) return undefined; const offset = typeof record.offset === "number" ? record.offset : undefined; const limit = typeof record.limit === "number" ? record.limit : undefined; if (offset !== undefined && limit !== undefined) { return `${path}:${offset}-${offset + limit}`; } return path; } function resolveWriteDetail(args: unknown): string | undefined { if (!args || typeof args !== "object") return undefined; const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; return path; } function resolveActionSpec( spec: ToolDisplaySpec | undefined, action: string | undefined, ): ToolDisplayActionSpec | undefined { if (!spec || !action) return undefined; return spec.actions?.[action] ?? undefined; } export function resolveToolDisplay(params: { name?: string; args?: unknown; meta?: string; }): ToolDisplay { const name = normalizeToolName(params.name); const key = name.toLowerCase(); const spec = TOOL_MAP[key]; const emoji = spec?.emoji ?? FALLBACK.emoji ?? "🧩"; const title = spec?.title ?? defaultTitle(name); const label = spec?.label ?? name; const actionRaw = params.args && typeof params.args === "object" ? ((params.args as Record).action as string | undefined) : undefined; const action = typeof actionRaw === "string" ? actionRaw.trim() : undefined; const actionSpec = resolveActionSpec(spec, action); const verb = normalizeVerb(actionSpec?.label ?? action); let detail: string | undefined; if (key === "read") detail = resolveReadDetail(params.args); if (!detail && (key === "write" || key === "edit" || key === "attach")) { detail = resolveWriteDetail(params.args); } const detailKeys = actionSpec?.detailKeys ?? spec?.detailKeys ?? FALLBACK.detailKeys ?? []; if (!detail && detailKeys.length > 0) { detail = resolveDetailFromKeys(params.args, detailKeys); } if (!detail && params.meta) { detail = params.meta; } if (detail) { detail = shortenHomeInString(detail); } return { name, emoji, title, label, verb, detail, }; } export function formatToolDetail(display: ToolDisplay): string | undefined { const parts: string[] = []; if (display.verb) parts.push(display.verb); if (display.detail) parts.push(redactToolDetail(display.detail)); if (parts.length === 0) return undefined; return parts.join(" · "); } export function formatToolSummary(display: ToolDisplay): string { const detail = formatToolDetail(display); return detail ? `${display.emoji} ${display.label}: ${detail}` : `${display.emoji} ${display.label}`; }