import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { keyHint } from "@earendil-works/pi-coding-agent"; import { Text } from "@earendil-works/pi-tui"; import { runCli } from "../cli.ts"; import { TOOLS } from "../schemas.ts"; /** * `cbm_get_code_snippet`: same passthrough execute as the generic tools, but * with a bespoke TUI renderer. The raw JSON carries a lot of metrics, so the * renderer collapses it to "retrieved " / "not found" (expandable to the * signature + location). The LLM still sees the full JSON in `content`. */ type Snippet = { name?: string; qualified_name?: string; label?: string; file_path?: string; start_line?: number; end_line?: number; signature?: string; return_type?: string; source?: string; }; export function registerGetCodeSnippet(pi: ExtensionAPI) { const def = TOOLS.find((t) => t.tool === "get_code_snippet")!; pi.registerTool({ name: "cbm_get_code_snippet", label: "cbm:get_code_snippet", description: def.description, parameters: def.parameters, async execute(_toolCallId, params, signal) { if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }], details: {} }; } let out: string; try { out = await runCli("get_code_snippet", params, signal ?? undefined); } catch (e) { // Not-found / lookup failures exit non-zero. Surface the message to the // LLM as text and flag it for the renderer instead of a hard throw. const msg = (e as Error).message; return { content: [{ type: "text", text: msg }], details: { notFound: true }, }; } let parsed: Snippet | null = null; try { parsed = JSON.parse(out) as Snippet; } catch { /* fall back to raw text below */ } return { // LLM still sees the full JSON. content: [{ type: "text", text: out || "{}" }], // Renderer reads structured fields from details. details: parsed ?? { raw: out }, }; }, renderCall(args, theme) { const a = args as { qualified_name?: string }; let s = theme.fg("toolTitle", theme.bold("cbm:get_code_snippet ")); s += theme.fg("accent", a.qualified_name ?? "…"); return new Text(s, 0, 0); }, renderResult(result, { expanded, isPartial }, theme) { if (isPartial) { return new Text(theme.fg("warning", "⏳ Retrieving…"), 0, 0); } const d = result.details as Snippet & { raw?: string; notFound?: boolean }; if (!d || d.notFound) { return new Text(theme.fg("dim", "∅ not found"), 0, 0); } if (d.raw !== undefined) { return new Text(theme.fg("error", `✗ ${d.raw || "retrieve failed"}`), 0, 0); } const label = d.label ? theme.fg("dim", `${d.label} `) : ""; const name = theme.fg("accent", d.name ?? d.qualified_name ?? "?"); const range = d.start_line != null ? `:${d.start_line}${d.end_line != null ? `-${d.end_line}` : ""}` : ""; const loc = d.file_path ? " " + theme.fg("muted", `${d.file_path}${range}`) : ""; const head = theme.fg("success", theme.bold("✔ retrieved ")) + label + name + loc; if (!expanded) { return new Text( head + " " + theme.fg("dim", `(${keyHint("app.tools.expand", "expand")})`), 0, 0, ); } const lines: string[] = [head]; if (d.signature || d.return_type) { lines.push(" " + theme.fg("muted", `${d.signature ?? ""}${d.return_type ?? ""}`.trim())); } if (d.source) { const n = d.source.split("\n").length; lines.push(" " + theme.fg("dim", `${n} line${n === 1 ? "" : "s"} of source`)); } return new Text(lines.join("\n"), 0, 0); }, }); }