@oh-my-pi/pi-coding-agent 13.3.6 → 13.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +115 -0
- package/package.json +9 -18
- package/scripts/format-prompts.ts +7 -172
- package/src/capability/mcp.ts +5 -0
- package/src/cli/args.ts +1 -0
- package/src/config/prompt-templates.ts +9 -55
- package/src/config/settings-schema.ts +24 -0
- package/src/discovery/builtin.ts +1 -0
- package/src/discovery/codex.ts +1 -2
- package/src/discovery/helpers.ts +0 -5
- package/src/discovery/mcp-json.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/lsp/client.ts +8 -0
- package/src/lsp/config.ts +2 -3
- package/src/lsp/index.ts +379 -99
- package/src/lsp/render.ts +21 -31
- package/src/lsp/types.ts +21 -8
- package/src/lsp/utils.ts +193 -1
- package/src/mcp/config-writer.ts +3 -0
- package/src/mcp/config.ts +1 -0
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/mcp/types.ts +5 -0
- package/src/modes/components/settings-defs.ts +9 -0
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +6 -2
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/theme/mermaid-cache.ts +4 -4
- package/src/modes/theme/theme.ts +33 -0
- package/src/prompts/system/custom-system-prompt.md +0 -10
- package/src/prompts/system/subagent-user-prompt.md +2 -0
- package/src/prompts/system/system-prompt.md +12 -9
- package/src/prompts/tools/ast-find.md +20 -0
- package/src/prompts/tools/ast-replace.md +21 -0
- package/src/prompts/tools/bash.md +2 -0
- package/src/prompts/tools/hashline.md +26 -8
- package/src/prompts/tools/lsp.md +22 -5
- package/src/prompts/tools/task.md +0 -1
- package/src/sdk.ts +11 -5
- package/src/session/agent-session.ts +293 -83
- package/src/system-prompt.ts +3 -34
- package/src/task/executor.ts +8 -7
- package/src/task/index.ts +8 -55
- package/src/task/template.ts +2 -4
- package/src/task/types.ts +0 -5
- package/src/task/worktree.ts +6 -2
- package/src/tools/ast-find.ts +316 -0
- package/src/tools/ast-replace.ts +294 -0
- package/src/tools/bash.ts +2 -1
- package/src/tools/browser.ts +2 -8
- package/src/tools/fetch.ts +55 -18
- package/src/tools/index.ts +8 -0
- package/src/tools/jtd-to-json-schema.ts +29 -13
- package/src/tools/path-utils.ts +34 -0
- package/src/tools/python.ts +2 -1
- package/src/tools/renderers.ts +4 -0
- package/src/tools/ssh.ts +2 -1
- package/src/tools/submit-result.ts +143 -44
- package/src/tools/todo-write.ts +34 -0
- package/src/tools/tool-timeouts.ts +29 -0
- package/src/utils/mime.ts +37 -14
- package/src/utils/prompt-format.ts +172 -0
- package/src/web/scrapers/arxiv.ts +12 -12
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +17 -9
- package/src/web/scrapers/readthedocs.ts +3 -3
- package/src/web/scrapers/twitter.ts +11 -11
- package/src/web/scrapers/wikipedia.ts +4 -5
- package/src/utils/ignore-files.ts +0 -119
package/src/lsp/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
|
|
|
9
9
|
import type { ToolSession } from "../tools";
|
|
10
10
|
import { resolveToCwd } from "../tools/path-utils";
|
|
11
11
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
12
|
+
import { clampTimeout } from "../tools/tool-timeouts";
|
|
12
13
|
import {
|
|
13
14
|
ensureFileOpen,
|
|
14
15
|
getActiveClients,
|
|
@@ -27,6 +28,9 @@ import { applyTextEditsToString, applyWorkspaceEdit } from "./edits";
|
|
|
27
28
|
import { detectLspmux } from "./lspmux";
|
|
28
29
|
import { renderCall, renderResult } from "./render";
|
|
29
30
|
import {
|
|
31
|
+
type CodeAction,
|
|
32
|
+
type CodeActionContext,
|
|
33
|
+
type Command,
|
|
30
34
|
type Diagnostic,
|
|
31
35
|
type DocumentSymbol,
|
|
32
36
|
type Hover,
|
|
@@ -42,16 +46,25 @@ import {
|
|
|
42
46
|
type WorkspaceEdit,
|
|
43
47
|
} from "./types";
|
|
44
48
|
import {
|
|
49
|
+
applyCodeAction,
|
|
50
|
+
collectGlobMatches,
|
|
51
|
+
dedupeWorkspaceSymbols,
|
|
45
52
|
extractHoverText,
|
|
46
53
|
fileToUri,
|
|
54
|
+
filterWorkspaceSymbols,
|
|
55
|
+
formatCodeAction,
|
|
47
56
|
formatDiagnostic,
|
|
48
57
|
formatDiagnosticsSummary,
|
|
49
58
|
formatDocumentSymbol,
|
|
50
59
|
formatLocation,
|
|
51
60
|
formatSymbolInformation,
|
|
52
61
|
formatWorkspaceEdit,
|
|
62
|
+
hasGlobPattern,
|
|
63
|
+
readLocationContext,
|
|
64
|
+
resolveSymbolColumn,
|
|
53
65
|
sortDiagnostics,
|
|
54
66
|
symbolKindToIcon,
|
|
67
|
+
uriToFile,
|
|
55
68
|
} from "./utils";
|
|
56
69
|
|
|
57
70
|
export type { LspServerStatus } from "./client";
|
|
@@ -238,6 +251,10 @@ function getLspServerForFile(config: LspConfig, filePath: string): [string, Serv
|
|
|
238
251
|
}
|
|
239
252
|
|
|
240
253
|
const DIAGNOSTIC_MESSAGE_LIMIT = 50;
|
|
254
|
+
const SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS = 3000;
|
|
255
|
+
const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400;
|
|
256
|
+
const MAX_GLOB_DIAGNOSTIC_TARGETS = 20;
|
|
257
|
+
const WORKSPACE_SYMBOL_LIMIT = 200;
|
|
241
258
|
|
|
242
259
|
function limitDiagnosticMessages(messages: string[]): string[] {
|
|
243
260
|
if (messages.length <= DIAGNOSTIC_MESSAGE_LIMIT) {
|
|
@@ -246,15 +263,52 @@ function limitDiagnosticMessages(messages: string[]): string[] {
|
|
|
246
263
|
return messages.slice(0, DIAGNOSTIC_MESSAGE_LIMIT);
|
|
247
264
|
}
|
|
248
265
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (entries.length === 0) return null;
|
|
266
|
+
const LOCATION_CONTEXT_LINES = 1;
|
|
267
|
+
const REFERENCE_CONTEXT_LIMIT = 50;
|
|
252
268
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
269
|
+
function normalizeLocationResult(result: Location | Location[] | LocationLink | LocationLink[] | null): Location[] {
|
|
270
|
+
if (!result) return [];
|
|
271
|
+
const raw = Array.isArray(result) ? result : [result];
|
|
272
|
+
return raw.flatMap(loc => {
|
|
273
|
+
if ("uri" in loc) {
|
|
274
|
+
return [loc as Location];
|
|
275
|
+
}
|
|
276
|
+
if ("targetUri" in loc) {
|
|
277
|
+
const link = loc as LocationLink;
|
|
278
|
+
return [{ uri: link.targetUri, range: link.targetSelectionRange ?? link.targetRange }];
|
|
279
|
+
}
|
|
280
|
+
return [];
|
|
281
|
+
});
|
|
282
|
+
}
|
|
256
283
|
|
|
257
|
-
|
|
284
|
+
async function formatLocationWithContext(location: Location, cwd: string): Promise<string> {
|
|
285
|
+
const header = ` ${formatLocation(location, cwd)}`;
|
|
286
|
+
const context = await readLocationContext(
|
|
287
|
+
uriToFile(location.uri),
|
|
288
|
+
location.range.start.line + 1,
|
|
289
|
+
LOCATION_CONTEXT_LINES,
|
|
290
|
+
);
|
|
291
|
+
if (context.length === 0) {
|
|
292
|
+
return header;
|
|
293
|
+
}
|
|
294
|
+
return `${header}\n${context.map(lineText => ` ${lineText}`).join("\n")}`;
|
|
295
|
+
}
|
|
296
|
+
async function reloadServer(client: LspClient, serverName: string, signal?: AbortSignal): Promise<string> {
|
|
297
|
+
let output = `Restarted ${serverName}`;
|
|
298
|
+
const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
|
|
299
|
+
for (const method of reloadMethods) {
|
|
300
|
+
try {
|
|
301
|
+
await sendRequest(client, method, method.includes("Configuration") ? { settings: {} } : null, signal);
|
|
302
|
+
output = `Reloaded ${serverName}`;
|
|
303
|
+
break;
|
|
304
|
+
} catch {
|
|
305
|
+
// Method not supported, try next
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (output.startsWith("Restarted")) {
|
|
309
|
+
client.proc.kill();
|
|
310
|
+
}
|
|
311
|
+
return output;
|
|
258
312
|
}
|
|
259
313
|
|
|
260
314
|
async function waitForDiagnostics(
|
|
@@ -887,7 +941,10 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
887
941
|
_onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
|
|
888
942
|
_context?: AgentToolContext,
|
|
889
943
|
): Promise<AgentToolResult<LspToolDetails>> {
|
|
890
|
-
const { action, file,
|
|
944
|
+
const { action, file, line, symbol, occurrence, query, new_name, apply, timeout } = params;
|
|
945
|
+
const timeoutSec = clampTimeout("lsp", timeout);
|
|
946
|
+
const timeoutSignal = AbortSignal.timeout(timeoutSec * 1000);
|
|
947
|
+
signal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
891
948
|
throwIfAborted(signal);
|
|
892
949
|
|
|
893
950
|
const config = getConfig(this.session.cwd);
|
|
@@ -916,8 +973,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
916
973
|
|
|
917
974
|
// Diagnostics can be batch or single-file - queries all applicable servers
|
|
918
975
|
if (action === "diagnostics") {
|
|
919
|
-
|
|
920
|
-
if (!targets) {
|
|
976
|
+
if (!file) {
|
|
921
977
|
// No file specified - run workspace diagnostics
|
|
922
978
|
const result = await runWorkspaceDiagnostics(this.session.cwd, signal);
|
|
923
979
|
return {
|
|
@@ -931,9 +987,34 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
931
987
|
};
|
|
932
988
|
}
|
|
933
989
|
|
|
934
|
-
|
|
990
|
+
let targets: string[];
|
|
991
|
+
let truncatedGlobTargets = false;
|
|
992
|
+
if (hasGlobPattern(file)) {
|
|
993
|
+
const globMatches = await collectGlobMatches(file, this.session.cwd, MAX_GLOB_DIAGNOSTIC_TARGETS);
|
|
994
|
+
targets = globMatches.matches;
|
|
995
|
+
truncatedGlobTargets = globMatches.truncated;
|
|
996
|
+
} else {
|
|
997
|
+
targets = [file];
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (targets.length === 0) {
|
|
1001
|
+
return {
|
|
1002
|
+
content: [{ type: "text", text: `No files matched pattern: ${file}` }],
|
|
1003
|
+
details: { action, success: true, request: params },
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const detailed = targets.length > 1 || truncatedGlobTargets;
|
|
1008
|
+
const diagnosticsWaitTimeoutMs = detailed
|
|
1009
|
+
? Math.min(BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS, timeoutSec * 1000)
|
|
1010
|
+
: Math.min(SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS, timeoutSec * 1000);
|
|
935
1011
|
const results: string[] = [];
|
|
936
1012
|
const allServerNames = new Set<string>();
|
|
1013
|
+
if (truncatedGlobTargets) {
|
|
1014
|
+
results.push(
|
|
1015
|
+
`${theme.status.warning} Pattern matched more than ${MAX_GLOB_DIAGNOSTIC_TARGETS} files; showing first ${MAX_GLOB_DIAGNOSTIC_TARGETS}. Narrow the glob or use workspace diagnostics.`,
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
937
1018
|
|
|
938
1019
|
for (const target of targets) {
|
|
939
1020
|
throwIfAborted(signal);
|
|
@@ -962,7 +1043,13 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
962
1043
|
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
963
1044
|
const minVersion = client.diagnosticsVersion;
|
|
964
1045
|
await refreshFile(client, resolved, signal);
|
|
965
|
-
const diagnostics = await waitForDiagnostics(
|
|
1046
|
+
const diagnostics = await waitForDiagnostics(
|
|
1047
|
+
client,
|
|
1048
|
+
uri,
|
|
1049
|
+
diagnosticsWaitTimeoutMs,
|
|
1050
|
+
signal,
|
|
1051
|
+
minVersion,
|
|
1052
|
+
);
|
|
966
1053
|
allDiagnostics.push(...diagnostics);
|
|
967
1054
|
} catch (err) {
|
|
968
1055
|
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
@@ -1029,10 +1116,107 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1029
1116
|
}
|
|
1030
1117
|
|
|
1031
1118
|
const resolvedFile = file ? resolveToCwd(file, this.session.cwd) : null;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1119
|
+
if (action === "symbols" && !resolvedFile) {
|
|
1120
|
+
const normalizedQuery = query?.trim();
|
|
1121
|
+
if (!normalizedQuery) {
|
|
1122
|
+
return {
|
|
1123
|
+
content: [{ type: "text", text: "Error: query parameter required for workspace symbol search" }],
|
|
1124
|
+
details: { action, success: false, request: params },
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
const servers = getLspServers(config);
|
|
1128
|
+
if (servers.length === 0) {
|
|
1129
|
+
return {
|
|
1130
|
+
content: [{ type: "text", text: "No language server found for this action" }],
|
|
1131
|
+
details: { action, success: false, request: params },
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
const aggregatedSymbols: SymbolInformation[] = [];
|
|
1135
|
+
const respondingServers = new Set<string>();
|
|
1136
|
+
for (const [workspaceServerName, workspaceServerConfig] of servers) {
|
|
1137
|
+
throwIfAborted(signal);
|
|
1138
|
+
try {
|
|
1139
|
+
const workspaceClient = await getOrCreateClient(workspaceServerConfig, this.session.cwd);
|
|
1140
|
+
const workspaceResult = (await sendRequest(
|
|
1141
|
+
workspaceClient,
|
|
1142
|
+
"workspace/symbol",
|
|
1143
|
+
{ query: normalizedQuery },
|
|
1144
|
+
signal,
|
|
1145
|
+
)) as SymbolInformation[] | null;
|
|
1146
|
+
if (!workspaceResult || workspaceResult.length === 0) {
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
respondingServers.add(workspaceServerName);
|
|
1150
|
+
aggregatedSymbols.push(...filterWorkspaceSymbols(workspaceResult, normalizedQuery));
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
1153
|
+
throw err;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const dedupedSymbols = dedupeWorkspaceSymbols(aggregatedSymbols);
|
|
1158
|
+
if (dedupedSymbols.length === 0) {
|
|
1159
|
+
return {
|
|
1160
|
+
content: [{ type: "text", text: `No symbols matching "${normalizedQuery}"` }],
|
|
1161
|
+
details: {
|
|
1162
|
+
action,
|
|
1163
|
+
serverName: Array.from(respondingServers).join(", "),
|
|
1164
|
+
success: true,
|
|
1165
|
+
request: params,
|
|
1166
|
+
},
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
const limitedSymbols = dedupedSymbols.slice(0, WORKSPACE_SYMBOL_LIMIT);
|
|
1170
|
+
const lines = limitedSymbols.map(s => formatSymbolInformation(s, this.session.cwd));
|
|
1171
|
+
const truncationLine =
|
|
1172
|
+
dedupedSymbols.length > WORKSPACE_SYMBOL_LIMIT
|
|
1173
|
+
? `\n... ${dedupedSymbols.length - WORKSPACE_SYMBOL_LIMIT} additional symbol(s) omitted`
|
|
1174
|
+
: "";
|
|
1175
|
+
return {
|
|
1176
|
+
content: [
|
|
1177
|
+
{
|
|
1178
|
+
type: "text",
|
|
1179
|
+
text: `Found ${dedupedSymbols.length} symbol(s) matching "${normalizedQuery}":\n${lines.map(l => ` ${l}`).join("\n")}${truncationLine}`,
|
|
1180
|
+
},
|
|
1181
|
+
],
|
|
1182
|
+
details: {
|
|
1183
|
+
action,
|
|
1184
|
+
serverName: Array.from(respondingServers).join(", "),
|
|
1185
|
+
success: true,
|
|
1186
|
+
request: params,
|
|
1187
|
+
},
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1035
1190
|
|
|
1191
|
+
if (action === "reload" && !resolvedFile) {
|
|
1192
|
+
const servers = getLspServers(config);
|
|
1193
|
+
if (servers.length === 0) {
|
|
1194
|
+
return {
|
|
1195
|
+
content: [{ type: "text", text: "No language server found for this action" }],
|
|
1196
|
+
details: { action, success: false, request: params },
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
const outputs: string[] = [];
|
|
1200
|
+
for (const [workspaceServerName, workspaceServerConfig] of servers) {
|
|
1201
|
+
throwIfAborted(signal);
|
|
1202
|
+
try {
|
|
1203
|
+
const workspaceClient = await getOrCreateClient(workspaceServerConfig, this.session.cwd);
|
|
1204
|
+
outputs.push(await reloadServer(workspaceClient, workspaceServerName, signal));
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
1207
|
+
throw err;
|
|
1208
|
+
}
|
|
1209
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1210
|
+
outputs.push(`Failed to reload ${workspaceServerName}: ${errorMessage}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return {
|
|
1214
|
+
content: [{ type: "text", text: outputs.join("\n") }],
|
|
1215
|
+
details: { action, serverName: servers.map(([name]) => name).join(", "), success: true, request: params },
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const serverInfo = resolvedFile ? getLspServerForFile(config, resolvedFile) : null;
|
|
1036
1220
|
if (!serverInfo) {
|
|
1037
1221
|
return {
|
|
1038
1222
|
content: [{ type: "text", text: "No language server found for this action" }],
|
|
@@ -1051,7 +1235,11 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1051
1235
|
}
|
|
1052
1236
|
|
|
1053
1237
|
const uri = targetFile ? fileToUri(targetFile) : "";
|
|
1054
|
-
const
|
|
1238
|
+
const resolvedLine = line ?? 1;
|
|
1239
|
+
const resolvedCharacter = targetFile
|
|
1240
|
+
? await resolveSymbolColumn(targetFile, resolvedLine, symbol, occurrence)
|
|
1241
|
+
: 0;
|
|
1242
|
+
const position = { line: resolvedLine - 1, character: resolvedCharacter };
|
|
1055
1243
|
|
|
1056
1244
|
let output: string;
|
|
1057
1245
|
|
|
@@ -1071,33 +1259,66 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1071
1259
|
signal,
|
|
1072
1260
|
)) as Location | Location[] | LocationLink | LocationLink[] | null;
|
|
1073
1261
|
|
|
1074
|
-
|
|
1262
|
+
const locations = normalizeLocationResult(result);
|
|
1263
|
+
|
|
1264
|
+
if (locations.length === 0) {
|
|
1075
1265
|
output = "No definition found";
|
|
1076
1266
|
} else {
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const link = loc as LocationLink;
|
|
1085
|
-
return [{ uri: link.targetUri, range: link.targetSelectionRange ?? link.targetRange }];
|
|
1086
|
-
}
|
|
1087
|
-
return [];
|
|
1088
|
-
});
|
|
1267
|
+
const lines = await Promise.all(
|
|
1268
|
+
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
1269
|
+
);
|
|
1270
|
+
output = `Found ${locations.length} definition(s):\n${lines.join("\n")}`;
|
|
1271
|
+
}
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1089
1274
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1275
|
+
case "type_definition": {
|
|
1276
|
+
const result = (await sendRequest(
|
|
1277
|
+
client,
|
|
1278
|
+
"textDocument/typeDefinition",
|
|
1279
|
+
{
|
|
1280
|
+
textDocument: { uri },
|
|
1281
|
+
position,
|
|
1282
|
+
},
|
|
1283
|
+
signal,
|
|
1284
|
+
)) as Location | Location[] | LocationLink | LocationLink[] | null;
|
|
1285
|
+
|
|
1286
|
+
const locations = normalizeLocationResult(result);
|
|
1287
|
+
|
|
1288
|
+
if (locations.length === 0) {
|
|
1289
|
+
output = "No type definition found";
|
|
1290
|
+
} else {
|
|
1291
|
+
const lines = await Promise.all(
|
|
1292
|
+
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
1293
|
+
);
|
|
1294
|
+
output = `Found ${locations.length} type definition(s):\n${lines.join("\n")}`;
|
|
1097
1295
|
}
|
|
1098
1296
|
break;
|
|
1099
1297
|
}
|
|
1100
1298
|
|
|
1299
|
+
case "implementation": {
|
|
1300
|
+
const result = (await sendRequest(
|
|
1301
|
+
client,
|
|
1302
|
+
"textDocument/implementation",
|
|
1303
|
+
{
|
|
1304
|
+
textDocument: { uri },
|
|
1305
|
+
position,
|
|
1306
|
+
},
|
|
1307
|
+
signal,
|
|
1308
|
+
)) as Location | Location[] | LocationLink | LocationLink[] | null;
|
|
1309
|
+
|
|
1310
|
+
const locations = normalizeLocationResult(result);
|
|
1311
|
+
|
|
1312
|
+
if (locations.length === 0) {
|
|
1313
|
+
output = "No implementation found";
|
|
1314
|
+
} else {
|
|
1315
|
+
const lines = await Promise.all(
|
|
1316
|
+
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
1317
|
+
);
|
|
1318
|
+
output = `Found ${locations.length} implementation(s):\n${lines.join("\n")}`;
|
|
1319
|
+
}
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1101
1322
|
case "references": {
|
|
1102
1323
|
const result = (await sendRequest(
|
|
1103
1324
|
client,
|
|
@@ -1105,7 +1326,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1105
1326
|
{
|
|
1106
1327
|
textDocument: { uri },
|
|
1107
1328
|
position,
|
|
1108
|
-
context: { includeDeclaration:
|
|
1329
|
+
context: { includeDeclaration: true },
|
|
1109
1330
|
},
|
|
1110
1331
|
signal,
|
|
1111
1332
|
)) as Location[] | null;
|
|
@@ -1113,7 +1334,19 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1113
1334
|
if (!result || result.length === 0) {
|
|
1114
1335
|
output = "No references found";
|
|
1115
1336
|
} else {
|
|
1116
|
-
const
|
|
1337
|
+
const contextualReferences = result.slice(0, REFERENCE_CONTEXT_LIMIT);
|
|
1338
|
+
const plainReferences = result.slice(REFERENCE_CONTEXT_LIMIT);
|
|
1339
|
+
const contextualLines = await Promise.all(
|
|
1340
|
+
contextualReferences.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
1341
|
+
);
|
|
1342
|
+
const plainLines = plainReferences.map(location => ` ${formatLocation(location, this.session.cwd)}`);
|
|
1343
|
+
const lines = plainLines.length
|
|
1344
|
+
? [
|
|
1345
|
+
...contextualLines,
|
|
1346
|
+
` ... ${plainLines.length} additional reference(s) shown without context`,
|
|
1347
|
+
...plainLines,
|
|
1348
|
+
]
|
|
1349
|
+
: contextualLines;
|
|
1117
1350
|
output = `Found ${result.length} reference(s):\n${lines.join("\n")}`;
|
|
1118
1351
|
}
|
|
1119
1352
|
break;
|
|
@@ -1138,52 +1371,118 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1138
1371
|
break;
|
|
1139
1372
|
}
|
|
1140
1373
|
|
|
1141
|
-
case "
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1374
|
+
case "code_actions": {
|
|
1375
|
+
const diagnostics = client.diagnostics.get(uri) ?? [];
|
|
1376
|
+
const context: CodeActionContext = {
|
|
1377
|
+
diagnostics,
|
|
1378
|
+
only: !apply && query ? [query] : undefined,
|
|
1379
|
+
triggerKind: 1,
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
const result = (await sendRequest(
|
|
1383
|
+
client,
|
|
1384
|
+
"textDocument/codeAction",
|
|
1385
|
+
{
|
|
1386
|
+
textDocument: { uri },
|
|
1387
|
+
range: { start: position, end: position },
|
|
1388
|
+
context,
|
|
1389
|
+
},
|
|
1390
|
+
signal,
|
|
1391
|
+
)) as (CodeAction | Command)[] | null;
|
|
1392
|
+
|
|
1393
|
+
if (!result || result.length === 0) {
|
|
1394
|
+
output = "No code actions available";
|
|
1395
|
+
break;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (apply === true && query) {
|
|
1399
|
+
const normalizedQuery = query.trim();
|
|
1400
|
+
if (normalizedQuery.length === 0) {
|
|
1401
|
+
output = "Error: query parameter required when apply=true for code_actions";
|
|
1402
|
+
break;
|
|
1151
1403
|
}
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1404
|
+
const parsedIndex = /^\d+$/.test(normalizedQuery) ? Number.parseInt(normalizedQuery, 10) : null;
|
|
1405
|
+
const selectedAction = result.find(
|
|
1406
|
+
(actionItem, index) =>
|
|
1407
|
+
(parsedIndex !== null && index === parsedIndex) ||
|
|
1408
|
+
actionItem.title.toLowerCase().includes(normalizedQuery.toLowerCase()),
|
|
1409
|
+
);
|
|
1410
|
+
|
|
1411
|
+
if (!selectedAction) {
|
|
1412
|
+
const actionLines = result.map((actionItem, index) => ` ${formatCodeAction(actionItem, index)}`);
|
|
1413
|
+
output = `No code action matches "${normalizedQuery}". Available actions:\n${actionLines.join("\n")}`;
|
|
1414
|
+
break;
|
|
1160
1415
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
{
|
|
1167
|
-
|
|
1416
|
+
|
|
1417
|
+
const appliedAction = await applyCodeAction(selectedAction, {
|
|
1418
|
+
resolveCodeAction: async actionItem =>
|
|
1419
|
+
(await sendRequest(client, "codeAction/resolve", actionItem, signal)) as CodeAction,
|
|
1420
|
+
applyWorkspaceEdit: async edit => applyWorkspaceEdit(edit, this.session.cwd),
|
|
1421
|
+
executeCommand: async commandItem => {
|
|
1422
|
+
await sendRequest(
|
|
1423
|
+
client,
|
|
1424
|
+
"workspace/executeCommand",
|
|
1425
|
+
{
|
|
1426
|
+
command: commandItem.command,
|
|
1427
|
+
arguments: commandItem.arguments ?? [],
|
|
1428
|
+
},
|
|
1429
|
+
signal,
|
|
1430
|
+
);
|
|
1168
1431
|
},
|
|
1169
|
-
|
|
1170
|
-
)) as (DocumentSymbol | SymbolInformation)[] | null;
|
|
1432
|
+
});
|
|
1171
1433
|
|
|
1172
|
-
if (!
|
|
1173
|
-
output = "
|
|
1434
|
+
if (!appliedAction) {
|
|
1435
|
+
output = `Action "${selectedAction.title}" has no workspace edit or command to apply`;
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const summaryLines: string[] = [];
|
|
1440
|
+
if (appliedAction.edits.length > 0) {
|
|
1441
|
+
summaryLines.push(" Workspace edit:");
|
|
1442
|
+
summaryLines.push(...appliedAction.edits.map(item => ` ${item}`));
|
|
1443
|
+
}
|
|
1444
|
+
if (appliedAction.executedCommands.length > 0) {
|
|
1445
|
+
summaryLines.push(" Executed command(s):");
|
|
1446
|
+
summaryLines.push(...appliedAction.executedCommands.map(commandName => ` ${commandName}`));
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
output = `Applied "${appliedAction.title}":\n${summaryLines.join("\n")}`;
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const actionLines = result.map((actionItem, index) => ` ${formatCodeAction(actionItem, index)}`);
|
|
1454
|
+
output = `${result.length} code action(s):\n${actionLines.join("\n")}`;
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
case "symbols": {
|
|
1458
|
+
if (!targetFile) {
|
|
1459
|
+
output = "Error: file parameter required for document symbols";
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
// File-based document symbols
|
|
1463
|
+
const result = (await sendRequest(
|
|
1464
|
+
client,
|
|
1465
|
+
"textDocument/documentSymbol",
|
|
1466
|
+
{
|
|
1467
|
+
textDocument: { uri },
|
|
1468
|
+
},
|
|
1469
|
+
signal,
|
|
1470
|
+
)) as (DocumentSymbol | SymbolInformation)[] | null;
|
|
1471
|
+
|
|
1472
|
+
if (!result || result.length === 0) {
|
|
1473
|
+
output = "No symbols found";
|
|
1474
|
+
} else {
|
|
1475
|
+
const relPath = path.relative(this.session.cwd, targetFile);
|
|
1476
|
+
if ("selectionRange" in result[0]) {
|
|
1477
|
+
const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
|
|
1478
|
+
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
|
|
1174
1479
|
} else {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
const line = s.location.range.start.line + 1;
|
|
1182
|
-
const icon = symbolKindToIcon(s.kind);
|
|
1183
|
-
return `${icon} ${s.name} @ line ${line}`;
|
|
1184
|
-
});
|
|
1185
|
-
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
|
|
1186
|
-
}
|
|
1480
|
+
const lines = (result as SymbolInformation[]).map(s => {
|
|
1481
|
+
const line = s.location.range.start.line + 1;
|
|
1482
|
+
const icon = symbolKindToIcon(s.kind);
|
|
1483
|
+
return `${icon} ${s.name} @ line ${line}`;
|
|
1484
|
+
});
|
|
1485
|
+
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
|
|
1187
1486
|
}
|
|
1188
1487
|
}
|
|
1189
1488
|
break;
|
|
@@ -1224,26 +1523,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1224
1523
|
}
|
|
1225
1524
|
|
|
1226
1525
|
case "reload": {
|
|
1227
|
-
|
|
1228
|
-
output = `Restarted ${serverName}`;
|
|
1229
|
-
const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
|
|
1230
|
-
for (const method of reloadMethods) {
|
|
1231
|
-
try {
|
|
1232
|
-
await sendRequest(
|
|
1233
|
-
client,
|
|
1234
|
-
method,
|
|
1235
|
-
method.includes("Configuration") ? { settings: {} } : null,
|
|
1236
|
-
signal,
|
|
1237
|
-
);
|
|
1238
|
-
output = `Reloaded ${serverName}`;
|
|
1239
|
-
break;
|
|
1240
|
-
} catch {
|
|
1241
|
-
// Method not supported, try next
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
if (output.startsWith("Restarted")) {
|
|
1245
|
-
client.proc.kill();
|
|
1246
|
-
}
|
|
1526
|
+
output = await reloadServer(client, serverName, signal);
|
|
1247
1527
|
break;
|
|
1248
1528
|
}
|
|
1249
1529
|
|