import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { existsSync } from "node:fs"; import { toAbsolutePath } from "../lsp/_shared/paths.js"; import { LSP_DIAGNOSTIC_ICON } from "../lsp/_shared/output.js"; import { getGlobalLspManager } from "../lsp/manager.js"; import { getEventPaths, isMutationToolResult } from "../lsp/mutation-events.js"; export type LspEnrichableToolResult = AgentToolResult; export type LspMutationToolResultInput = { toolName: string; input: unknown; result: LspEnrichableToolResult; ctx: ExtensionContext; isError?: boolean; }; function textContent(text: string): AgentToolResult["content"][number] { return { type: "text", text }; } function hasLspDiagnosticsContent(result: LspEnrichableToolResult): boolean { return result.content.some((part) => typeof (part as { text?: unknown }).text === "string" && (part as { text: string }).text.includes("LSP diagnostics:")); } function isMissingFileError(error: unknown): boolean { return (error as { code?: unknown })?.code === "ENOENT" || String((error as Error)?.message ?? error).includes("ENOENT"); } export async function appendLspDiagnosticsToMutationResult(options: LspMutationToolResultInput & { result: T }): Promise { if (options.isError || !isMutationToolResult({ toolName: options.toolName, input: options.input, details: options.result.details })) return options.result; if (hasLspDiagnosticsContent(options.result)) return options.result; try { const files = [...new Set(getEventPaths(options.input, options.result.details).map((inputPath) => toAbsolutePath(inputPath, options.ctx.cwd)))].filter((file) => existsSync(file)); if (files.length === 0) return options.result; const manager = getGlobalLspManager(); const summaries = (await Promise.all(files.map((file) => manager.updateDiagnosticsForFile(options.ctx, file)))) .filter((summary) => summary.trim()); const summary = summaries.join("\n\n"); if (!summary.trim()) return options.result; return { ...options.result, content: [...options.result.content, textContent(summary)], }; } catch (error) { if (isMissingFileError(error)) return options.result; return { ...options.result, content: [...options.result.content, textContent(`LSP diagnostics:\n\n${LSP_DIAGNOSTIC_ICON} lsp: ${(error as Error).message}`)], }; } } export async function shutdownGlobalLspManager(): Promise { await getGlobalLspManager().shutdownAll(); }