|
1 | 1 | import * as vscode from "vscode"; |
2 | 2 | import * as os from "os"; |
3 | | -import { sessionManager } from "../../../hpccplatform/session"; |
| 3 | +import { isPlatformConnected } from "../../../hpccplatform/session"; |
4 | 4 | import { reporter } from "../../../telemetry"; |
5 | 5 | import localize from "../../../util/localize"; |
| 6 | +import { logToolEvent, requireConnectedSession, throwIfCancellationRequested } from "../utils/index"; |
6 | 7 |
|
7 | 8 | export interface ISyntaxCheckParameters { |
8 | 9 | ecl: string; |
9 | 10 | } |
10 | 11 |
|
11 | 12 | export class SyntaxCheckTool implements vscode.LanguageModelTool<ISyntaxCheckParameters> { |
12 | | - async invoke(options: vscode.LanguageModelToolInvocationOptions<ISyntaxCheckParameters>, _token: vscode.CancellationToken) { |
| 13 | + async invoke(options: vscode.LanguageModelToolInvocationOptions<ISyntaxCheckParameters>, token: vscode.CancellationToken) { |
13 | 14 | reporter?.sendTelemetryEvent("lmTool.invoke", { tool: "syntaxCheck" }); |
14 | 15 | const params = options.input; |
15 | | - if (typeof params.ecl === "string") { |
16 | | - const tmpFileName = `ecl_syntax_check_${Date.now()}.ecl`; |
17 | | - const tmpUri = vscode.Uri.joinPath(vscode.Uri.file(os.tmpdir()), tmpFileName); |
18 | | - try { |
19 | | - const eclContent = new TextEncoder().encode(params.ecl); |
20 | | - await vscode.workspace.fs.writeFile(tmpUri, eclContent); |
21 | | - const result = await sessionManager.session?.checkSyntax(tmpUri); |
22 | | - return new vscode.LanguageModelToolResult([ |
23 | | - new vscode.LanguageModelTextPart(result ? localize("ECL syntax is valid.") : localize("ECL syntax is invalid.")) |
24 | | - ]); |
25 | | - } catch (error) { |
26 | | - return new vscode.LanguageModelToolResult([ |
27 | | - new vscode.LanguageModelTextPart(`${localize("Error checking syntax:")}: ${error}`) |
28 | | - ]); |
29 | | - } finally { |
30 | | - try { |
31 | | - await vscode.workspace.fs.delete(tmpUri); |
32 | | - } catch (e) { |
33 | | - // ignore |
| 16 | + if (typeof params.ecl !== "string" || params.ecl.trim().length === 0) { |
| 17 | + throw new vscode.LanguageModelError(localize("ECL code is required"), { cause: "invalid_parameters" }); |
| 18 | + } |
| 19 | + |
| 20 | + logToolEvent("syntaxCheck", "invoke start", { inputLength: params.ecl.length }); |
| 21 | + |
| 22 | + const session = requireConnectedSession(); |
| 23 | + const tmpFileName = `ecl_syntax_check_${Date.now()}.ecl`; |
| 24 | + const tmpUri = vscode.Uri.joinPath(vscode.Uri.file(os.tmpdir()), tmpFileName); |
| 25 | + |
| 26 | + try { |
| 27 | + const eclContent = new TextEncoder().encode(params.ecl); |
| 28 | + await vscode.workspace.fs.writeFile(tmpUri, eclContent); |
| 29 | + |
| 30 | + throwIfCancellationRequested(token); |
| 31 | + |
| 32 | + const result = await session.checkSyntax(tmpUri); |
| 33 | + |
| 34 | + throwIfCancellationRequested(token); |
| 35 | + |
| 36 | + const errors = result?.errors ?? []; |
| 37 | + const checked = result?.checked ?? []; |
| 38 | + const issueCount = errors.length; |
| 39 | + const checkedCount = checked.length; |
| 40 | + |
| 41 | + const parts: vscode.LanguageModelTextPart[] = []; |
| 42 | + |
| 43 | + if (issueCount === 0) { |
| 44 | + parts.push(new vscode.LanguageModelTextPart(localize("No syntax errors found. Checked {0} file(s).", checkedCount.toString()))); |
| 45 | + } else { |
| 46 | + parts.push(new vscode.LanguageModelTextPart(localize("Detected {0} syntax issue(s) across {1} file(s).", issueCount.toString(), Math.max(checkedCount, 1).toString()))); |
| 47 | + |
| 48 | + const formatted = errors |
| 49 | + .map((error: any, idx: number) => { |
| 50 | + const filePath = typeof error.filePath === "string" && error.filePath.length > 0 ? error.filePath : checked[0] || tmpUri.fsPath; |
| 51 | + const line = typeof error.line === "number" ? error.line : undefined; |
| 52 | + const column = typeof error.col === "number" ? error.col : undefined; |
| 53 | + const severity = typeof error.severity === "string" ? error.severity : "error"; |
| 54 | + const code = error.code ? `[${error.code}] ` : ""; |
| 55 | + const location = [filePath, line, column] |
| 56 | + .filter(value => value !== undefined && value !== "") |
| 57 | + .join(":"); |
| 58 | + const message = error.msg || error.message || localize("Unknown syntax issue"); |
| 59 | + return `${idx + 1}. ${location} ${severity.toUpperCase()} ${code}${message}`; |
| 60 | + }) |
| 61 | + .join("\n"); |
| 62 | + |
| 63 | + if (formatted) { |
| 64 | + parts.push(new vscode.LanguageModelTextPart(formatted)); |
34 | 65 | } |
35 | 66 | } |
36 | | - } else { |
37 | | - return new vscode.LanguageModelToolResult([ |
38 | | - new vscode.LanguageModelTextPart(localize("Invalid input: ECL code must be a string.")) |
39 | | - ]); |
| 67 | + |
| 68 | + if (checkedCount > 0) { |
| 69 | + const checkedFiles = checked.join("\n"); |
| 70 | + parts.push(new vscode.LanguageModelTextPart(`${localize("Files checked:")}\n${checkedFiles}`)); |
| 71 | + } |
| 72 | + |
| 73 | + logToolEvent("syntaxCheck", "invoke success", { |
| 74 | + issueCount, |
| 75 | + checkedCount, |
| 76 | + filesChecked: checked, |
| 77 | + }); |
| 78 | + |
| 79 | + return new vscode.LanguageModelToolResult(parts); |
| 80 | + } catch (error) { |
| 81 | + const message = error instanceof Error ? error.message : String(error); |
| 82 | + logToolEvent("syntaxCheck", "invoke failed", { error: message }); |
| 83 | + throw new vscode.LanguageModelError(localize("Error checking syntax: {0}", message), { cause: error }); |
| 84 | + } finally { |
| 85 | + try { |
| 86 | + await vscode.workspace.fs.delete(tmpUri); |
| 87 | + } catch { |
| 88 | + // ignore |
| 89 | + } |
40 | 90 | } |
41 | 91 | } |
42 | 92 |
|
43 | 93 | async prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<ISyntaxCheckParameters>, _token: vscode.CancellationToken) { |
44 | | - const confirmationMessages = { |
| 94 | + const connected = isPlatformConnected(); |
| 95 | + const eclPreview = options.input.ecl ? `\n\n${options.input.ecl.slice(0, 200)}${options.input.ecl.length > 200 ? "…" : ""}` : ""; |
| 96 | + |
| 97 | + const confirmationMessages = connected ? { |
45 | 98 | title: localize("Check ECL Syntax"), |
46 | 99 | message: new vscode.MarkdownString( |
47 | | - localize("Check the syntax of ECL code?") + (options.input.ecl !== undefined ? ` ${localize("in ECL code")} ${options.input.ecl}` : "") |
| 100 | + localize("Check the syntax of ECL code?") + eclPreview |
48 | 101 | ), |
| 102 | + } : { |
| 103 | + title: localize("HPCC Platform not connected"), |
| 104 | + message: new vscode.MarkdownString(localize("This tool requires an active HPCC connection.")), |
49 | 105 | }; |
50 | 106 |
|
51 | 107 | return { |
52 | | - invocationMessage: localize("Checking ECL syntax"), |
| 108 | + invocationMessage: connected |
| 109 | + ? localize("Checking ECL syntax") |
| 110 | + : localize("Cannot check syntax: HPCC Platform not connected"), |
53 | 111 | confirmationMessages, |
54 | 112 | }; |
55 | 113 | } |
|
0 commit comments