Skip to content

Commit 154f982

Browse files
committed
feat: add WU Error/Warning AI tool
Signed-off-by: Gordon Smith <[email protected]>
1 parent f822bdb commit 154f982

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,33 @@
244244
]
245245
}
246246
},
247+
{
248+
"name": "ecl-extension-getWorkunitErrors",
249+
"tags": [
250+
"workunit",
251+
"errors",
252+
"warnings",
253+
"exceptions",
254+
"ecl-extension"
255+
],
256+
"toolReferenceName": "getWorkunitErrors",
257+
"displayName": "Get Workunit Errors/Warnings",
258+
"modelDescription": "Fetch errors, warnings, and exceptions from a specific workunit. Returns detailed information about compilation errors, runtime exceptions, and warning messages. Requires a WUID (Workunit ID).",
259+
"canBeReferencedInPrompt": true,
260+
"icon": "$(warning)",
261+
"inputSchema": {
262+
"type": "object",
263+
"properties": {
264+
"wuid": {
265+
"type": "string",
266+
"description": "The Workunit ID (WUID) to fetch errors and warnings for"
267+
}
268+
},
269+
"required": [
270+
"wuid"
271+
]
272+
}
273+
},
247274
{
248275
"name": "ecl-extension-syntaxCheck",
249276
"tags": [

src/ecl/lm/tools.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as vscode from "vscode";
22
import { FindWorkunitsTool } from "./tools/findWorkunits";
3+
import { GetWorkunitErrorsTool } from "./tools/getWorkunitErrors";
34
import { FindLogicalFilesTool } from "./tools/findLogicalFiles";
45
import { SyntaxCheckTool } from "./tools/syntaxCheck";
56

@@ -9,6 +10,8 @@ export class ECLLMTools {
910

1011
protected constructor(ctx: vscode.ExtensionContext) {
1112
ctx.subscriptions.push(vscode.lm.registerTool("ecl-extension-findWorkunits", new FindWorkunitsTool()));
13+
ctx.subscriptions.push(vscode.lm.registerTool("ecl-extension-getWorkunitErrors", new GetWorkunitErrorsTool()));
14+
1215
ctx.subscriptions.push(vscode.lm.registerTool("ecl-extension-findLogicalFiles", new FindLogicalFilesTool()));
1316
ctx.subscriptions.push(vscode.lm.registerTool("ecl-extension-syntaxCheck", new SyntaxCheckTool()));
1417
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import * as vscode from "vscode";
2+
import { Workunit, type WsWorkunits } from "@hpcc-js/comms";
3+
import { isPlatformConnected } from "../../../hpccplatform/session";
4+
import { reporter } from "../../../telemetry";
5+
import localize from "../../../util/localize";
6+
import { createServiceOptions, logToolEvent, requireConnectedSession, throwIfCancellationRequested } from "../utils";
7+
8+
const SEVERITY = {
9+
ERROR: ["Error", "error"],
10+
WARNING: ["Warning", "warning"],
11+
INFO: ["Info", "info", "Information"],
12+
} as const;
13+
14+
interface FormattedException {
15+
severity: string;
16+
source: string;
17+
message: string;
18+
code: number;
19+
fileName?: string;
20+
lineNo?: number;
21+
column?: number;
22+
}
23+
24+
export interface IGetWorkunitErrorsParameters {
25+
/**
26+
* Workunit ID (WUID) to fetch errors and warnings for
27+
*/
28+
wuid: string;
29+
}
30+
31+
function isSeverityType(exception: WsWorkunits.ECLException, severities: readonly string[]): boolean {
32+
return severities.some(s => s === exception.Severity);
33+
}
34+
35+
function formatException(exception: WsWorkunits.ECLException): FormattedException {
36+
return {
37+
severity: exception.Severity,
38+
source: exception.Source,
39+
message: exception.Message,
40+
code: exception.Code,
41+
fileName: exception.FileName,
42+
lineNo: exception.LineNo,
43+
column: exception.Column,
44+
};
45+
}
46+
47+
function addExceptionGroup(
48+
parts: vscode.LanguageModelTextPart[],
49+
title: string,
50+
exceptions: WsWorkunits.ECLException[]
51+
): void {
52+
if (exceptions.length === 0) return;
53+
54+
parts.push(new vscode.LanguageModelTextPart(title));
55+
for (const exception of exceptions) {
56+
parts.push(new vscode.LanguageModelTextPart(JSON.stringify(formatException(exception), null, 2)));
57+
}
58+
}
59+
60+
function categorizeExceptions(exceptions: WsWorkunits.ECLException[]) {
61+
const errors = exceptions.filter(e => isSeverityType(e, SEVERITY.ERROR));
62+
const warnings = exceptions.filter(e => isSeverityType(e, SEVERITY.WARNING));
63+
const infos = exceptions.filter(e => isSeverityType(e, SEVERITY.INFO));
64+
const others = exceptions.filter(e =>
65+
!isSeverityType(e, SEVERITY.ERROR) &&
66+
!isSeverityType(e, SEVERITY.WARNING) &&
67+
!isSeverityType(e, SEVERITY.INFO)
68+
);
69+
70+
return { errors, warnings, infos, others };
71+
}
72+
73+
export class GetWorkunitErrorsTool implements vscode.LanguageModelTool<IGetWorkunitErrorsParameters> {
74+
async invoke(options: vscode.LanguageModelToolInvocationOptions<IGetWorkunitErrorsParameters>, token: vscode.CancellationToken) {
75+
reporter?.sendTelemetryEvent("lmTool.invoke", { tool: "getWorkunitErrors" });
76+
const params = options.input;
77+
78+
const wuid = typeof params.wuid === "string" ? params.wuid.trim() : "";
79+
if (wuid.length === 0) {
80+
throw new vscode.LanguageModelError(localize("WUID is required"), { cause: "invalid_parameters" });
81+
}
82+
83+
logToolEvent("getWorkunitErrors", "invoke start", { wuid });
84+
85+
const session = requireConnectedSession();
86+
const opts = await createServiceOptions(session);
87+
88+
try {
89+
// Attach to the workunit and fetch its details
90+
const wu = Workunit.attach(opts, wuid);
91+
await wu.refresh();
92+
93+
throwIfCancellationRequested(token);
94+
95+
const parts: vscode.LanguageModelTextPart[] = [];
96+
97+
// Add workunit basic state information
98+
const detailsUrl = session.wuDetailsUrl(wu.Wuid);
99+
parts.push(new vscode.LanguageModelTextPart(localize("Errors/Warnings for Workunit {0}:", wuid)));
100+
101+
const summary = localize(
102+
"{0} on {1} is {2}.",
103+
wu.Wuid,
104+
wu.Cluster || localize("unknown cluster"),
105+
wu.State || localize("unknown state")
106+
);
107+
parts.push(new vscode.LanguageModelTextPart(summary));
108+
109+
// Fetch and add exceptions (errors and warnings)
110+
const exceptions = await wu.fetchECLExceptions().catch(() => []);
111+
throwIfCancellationRequested(token);
112+
113+
if (exceptions.length === 0) {
114+
parts.push(new vscode.LanguageModelTextPart(localize("No errors or warnings found for this workunit.")));
115+
} else {
116+
const { errors, warnings, infos, others } = categorizeExceptions(exceptions);
117+
118+
parts.push(new vscode.LanguageModelTextPart(localize(
119+
"Total: {0} exception(s) - {1} error(s), {2} warning(s), {3} info, {4} other",
120+
exceptions.length.toString(),
121+
errors.length.toString(),
122+
warnings.length.toString(),
123+
infos.length.toString(),
124+
others.length.toString()
125+
)));
126+
127+
addExceptionGroup(parts, localize("\nErrors ({0}):", errors.length.toString()), errors);
128+
addExceptionGroup(parts, localize("\nWarnings ({0}):", warnings.length.toString()), warnings);
129+
addExceptionGroup(parts, localize("\nInformational ({0}):", infos.length.toString()), infos);
130+
addExceptionGroup(parts, localize("\nOther ({0}):", others.length.toString()), others);
131+
}
132+
133+
if (detailsUrl) {
134+
parts.push(new vscode.LanguageModelTextPart(`\n${localize("ECL Watch URL:")} ${detailsUrl}`));
135+
}
136+
137+
const { errors, warnings } = categorizeExceptions(exceptions);
138+
logToolEvent("getWorkunitErrors", "invoke success", {
139+
wuid: wu.Wuid,
140+
state: wu.State,
141+
exceptionCount: exceptions.length,
142+
errorCount: errors.length,
143+
warningCount: warnings.length,
144+
});
145+
146+
return new vscode.LanguageModelToolResult(parts);
147+
} catch (error) {
148+
const errorMessage = error instanceof Error ? error.message : String(error);
149+
logToolEvent("getWorkunitErrors", "invoke failed", { wuid, error: errorMessage });
150+
throw new vscode.LanguageModelError(
151+
localize("Failed to fetch workunit errors/warnings: {0}", errorMessage),
152+
{ cause: error }
153+
);
154+
}
155+
}
156+
157+
async prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<IGetWorkunitErrorsParameters>, _token: vscode.CancellationToken) {
158+
const connected = isPlatformConnected();
159+
const wuid = typeof options.input.wuid === "string" ? options.input.wuid.trim() : "";
160+
161+
return {
162+
invocationMessage: connected
163+
? localize("Fetching errors/warnings for workunit {0}", wuid || localize("(unspecified)"))
164+
: localize("Cannot fetch: HPCC Platform not connected"),
165+
confirmationMessages: connected ? undefined : {
166+
title: localize("HPCC Platform not connected"),
167+
message: new vscode.MarkdownString(localize("This tool requires an active HPCC connection.")),
168+
}
169+
};
170+
}
171+
}

0 commit comments

Comments
 (0)