wave-agent-sdk 0.0.8 → 0.0.10

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.
Files changed (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +340 -137
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  24. package/dist/managers/permissionManager.d.ts +66 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +208 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +72 -13
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +25 -9
  78. package/dist/types/commands.d.ts +0 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +35 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/builtinSubagents.d.ts +7 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  121. package/dist/utils/builtinSubagents.js +65 -0
  122. package/dist/utils/cacheControlUtils.d.ts +8 -33
  123. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  124. package/dist/utils/cacheControlUtils.js +83 -126
  125. package/dist/utils/constants.d.ts +0 -12
  126. package/dist/utils/constants.d.ts.map +1 -1
  127. package/dist/utils/constants.js +1 -13
  128. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  129. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  130. package/dist/utils/convertMessagesForAPI.js +33 -14
  131. package/dist/utils/fileSearch.d.ts +14 -0
  132. package/dist/utils/fileSearch.d.ts.map +1 -0
  133. package/dist/utils/fileSearch.js +88 -0
  134. package/dist/utils/fileUtils.d.ts +14 -2
  135. package/dist/utils/fileUtils.d.ts.map +1 -1
  136. package/dist/utils/fileUtils.js +101 -17
  137. package/dist/utils/globalLogger.d.ts +0 -14
  138. package/dist/utils/globalLogger.d.ts.map +1 -1
  139. package/dist/utils/globalLogger.js +0 -16
  140. package/dist/utils/largeOutputHandler.d.ts +15 -0
  141. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  142. package/dist/utils/largeOutputHandler.js +40 -0
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/subagentParser.d.ts +2 -2
  152. package/dist/utils/subagentParser.d.ts.map +1 -1
  153. package/dist/utils/subagentParser.js +10 -7
  154. package/dist/utils/tokenEstimator.d.ts +39 -0
  155. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  156. package/dist/utils/tokenEstimator.js +55 -0
  157. package/package.json +5 -8
  158. package/src/agent.ts +460 -216
  159. package/src/index.ts +2 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +276 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +89 -16
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +48 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/builtinSubagents.ts +71 -0
  205. package/src/utils/cacheControlUtils.ts +106 -171
  206. package/src/utils/constants.ts +1 -16
  207. package/src/utils/convertMessagesForAPI.ts +38 -14
  208. package/src/utils/fileSearch.ts +107 -0
  209. package/src/utils/fileUtils.ts +114 -19
  210. package/src/utils/globalLogger.ts +0 -17
  211. package/src/utils/largeOutputHandler.ts +55 -0
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/subagentParser.ts +11 -8
  216. package/src/utils/tokenEstimator.ts +68 -0
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -0,0 +1,760 @@
1
+ import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
+ import { relative } from "path";
3
+ import { logger } from "../utils/globalLogger.js";
4
+ import { getDisplayPath } from "../utils/path.js";
5
+ import type {
6
+ LspLocation as Location,
7
+ LspLocationLink as LocationLink,
8
+ LspHover as Hover,
9
+ LspSymbolInformation as SymbolInformation,
10
+ LspDocumentSymbol as DocumentSymbol,
11
+ LspCallHierarchyItem as CallHierarchyItem,
12
+ LspCallHierarchyIncomingCall as CallHierarchyIncomingCall,
13
+ LspCallHierarchyOutgoingCall as CallHierarchyOutgoingCall,
14
+ } from "../types/lsp.js";
15
+
16
+ /**
17
+ * Formats an LSP URI into a readable file path
18
+ */
19
+ function formatUri(uri: string, workdir?: string): string {
20
+ if (!uri) {
21
+ logger.warn(
22
+ "formatUri called with undefined URI - indicates malformed LSP server response",
23
+ );
24
+ return "<unknown location>";
25
+ }
26
+
27
+ let path = uri.replace(/^file:\/\//, "");
28
+ try {
29
+ path = decodeURIComponent(path);
30
+ } catch (e) {
31
+ const message = e instanceof Error ? e.message : String(e);
32
+ logger.warn(
33
+ `Failed to decode LSP URI '${uri}': ${message}. Using un-decoded path: ${path}`,
34
+ );
35
+ }
36
+
37
+ if (workdir) {
38
+ const relativePath = relative(workdir, path);
39
+ if (
40
+ relativePath.length < path.length &&
41
+ !relativePath.startsWith("../../")
42
+ ) {
43
+ return relativePath;
44
+ }
45
+ }
46
+ return path;
47
+ }
48
+
49
+ /**
50
+ * Groups items by their URI
51
+ */
52
+ function groupItemsByUri<
53
+ T extends { uri: string } | { location: Location } | LocationLink,
54
+ >(items: T[], workdir?: string): Map<string, T[]> {
55
+ const map = new Map<string, T[]>();
56
+ for (const item of items) {
57
+ let uri: string;
58
+ if ("uri" in item) {
59
+ uri = item.uri;
60
+ } else if ("location" in item) {
61
+ uri = item.location.uri;
62
+ } else {
63
+ uri = item.targetUri;
64
+ }
65
+ const path = formatUri(uri, workdir);
66
+ const existing = map.get(path);
67
+ if (existing) {
68
+ existing.push(item);
69
+ } else {
70
+ map.set(path, [item]);
71
+ }
72
+ }
73
+ return map;
74
+ }
75
+
76
+ /**
77
+ * Formats a single location as path:line:character
78
+ */
79
+ function formatLocation(loc: Location, workdir?: string): string {
80
+ const path = formatUri(loc.uri, workdir);
81
+ const line = loc.range.start.line + 1;
82
+ const character = loc.range.start.character + 1;
83
+ return `${path}:${line}:${character}`;
84
+ }
85
+
86
+ /**
87
+ * Converts a LocationLink to a Location
88
+ */
89
+ function locationLinkToLocation(link: LocationLink): Location {
90
+ return {
91
+ uri: link.targetUri,
92
+ range: link.targetSelectionRange || link.targetRange,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Checks if an object is a LocationLink
98
+ */
99
+ function isLocationLink(loc: Location | LocationLink): loc is LocationLink {
100
+ return "targetUri" in loc;
101
+ }
102
+
103
+ /**
104
+ * Formats the result of a goToDefinition operation
105
+ */
106
+ function formatGoToDefinitionResult(
107
+ result: Location | Location[] | LocationLink | LocationLink[] | null,
108
+ workdir?: string,
109
+ ): string {
110
+ if (!result) {
111
+ return "No definition found. This may occur if the cursor is not on a symbol, or if the definition is in an external library not indexed by the LSP server.";
112
+ }
113
+
114
+ if (Array.isArray(result)) {
115
+ const locations = result.map((loc) =>
116
+ isLocationLink(loc) ? locationLinkToLocation(loc) : loc,
117
+ );
118
+ const validLocations = locations.filter((loc) => loc && loc.uri);
119
+
120
+ if (validLocations.length === 0) {
121
+ return "No definition found. This may occur if the cursor is not on a symbol, or if the definition is in an external library not indexed by the LSP server.";
122
+ }
123
+
124
+ if (validLocations.length === 1) {
125
+ return `Defined in ${formatLocation(validLocations[0], workdir)}`;
126
+ }
127
+
128
+ const formatted = validLocations
129
+ .map((loc) => ` ${formatLocation(loc, workdir)}`)
130
+ .join("\n");
131
+ return `Found ${validLocations.length} definitions:\n${formatted}`;
132
+ }
133
+
134
+ const loc = isLocationLink(result) ? locationLinkToLocation(result) : result;
135
+ return `Defined in ${formatLocation(loc, workdir)}`;
136
+ }
137
+
138
+ /**
139
+ * Formats the result of a findReferences operation
140
+ */
141
+ function formatFindReferencesResult(
142
+ result: Location[] | null,
143
+ workdir?: string,
144
+ ): string {
145
+ if (!result || result.length === 0) {
146
+ return "No references found. This may occur if the symbol has no usages, or if the LSP server has not fully indexed the workspace.";
147
+ }
148
+
149
+ const validLocations = result.filter((loc) => loc && loc.uri);
150
+ if (validLocations.length === 0) {
151
+ return "No references found. This may occur if the symbol has no usages, or if the LSP server has not fully indexed the workspace.";
152
+ }
153
+
154
+ if (validLocations.length === 1) {
155
+ return `Found 1 reference:\n ${formatLocation(validLocations[0], workdir)}`;
156
+ }
157
+
158
+ const grouped = groupItemsByUri(validLocations, workdir);
159
+ const lines = [
160
+ `Found ${validLocations.length} references across ${grouped.size} files:`,
161
+ ];
162
+
163
+ for (const [path, locs] of grouped) {
164
+ lines.push(`\n${path}:`);
165
+ for (const loc of locs) {
166
+ const line = loc.range.start.line + 1;
167
+ const character = loc.range.start.character + 1;
168
+ lines.push(` Line ${line}:${character}`);
169
+ }
170
+ }
171
+
172
+ return lines.join("\n");
173
+ }
174
+
175
+ /**
176
+ * Formats hover contents
177
+ */
178
+ function formatHoverContents(contents: Hover["contents"]): string {
179
+ if (Array.isArray(contents)) {
180
+ return contents
181
+ .map((c) => (typeof c === "string" ? c : c.value))
182
+ .join("\n\n");
183
+ }
184
+ if (typeof contents === "string") {
185
+ return contents;
186
+ }
187
+ return contents.value;
188
+ }
189
+
190
+ /**
191
+ * Formats the result of a hover operation
192
+ */
193
+ function formatHoverResult(result: Hover | null): string {
194
+ if (!result) {
195
+ return "No hover information available. This may occur if the cursor is not on a symbol, or if the LSP server has not fully indexed the file.";
196
+ }
197
+
198
+ const contents = formatHoverContents(result.contents);
199
+ if (result.range) {
200
+ const line = result.range.start.line + 1;
201
+ const character = result.range.start.character + 1;
202
+ return `Hover info at ${line}:${character}:\n\n${contents}`;
203
+ }
204
+ return contents;
205
+ }
206
+
207
+ /**
208
+ * Gets the name of a symbol kind
209
+ */
210
+ function getSymbolKindName(kind: number): string {
211
+ const kinds: Record<number, string> = {
212
+ 1: "File",
213
+ 2: "Module",
214
+ 3: "Namespace",
215
+ 4: "Package",
216
+ 5: "Class",
217
+ 6: "Method",
218
+ 7: "Property",
219
+ 8: "Field",
220
+ 9: "Constructor",
221
+ 10: "Enum",
222
+ 11: "Interface",
223
+ 12: "Function",
224
+ 13: "Variable",
225
+ 14: "Constant",
226
+ 15: "String",
227
+ 16: "Number",
228
+ 17: "Boolean",
229
+ 18: "Array",
230
+ 19: "Object",
231
+ 20: "Key",
232
+ 21: "Null",
233
+ 22: "EnumMember",
234
+ 23: "Struct",
235
+ 24: "Event",
236
+ 25: "Operator",
237
+ 26: "TypeParameter",
238
+ };
239
+ return kinds[kind] || "Unknown";
240
+ }
241
+
242
+ /**
243
+ * Formats a single document symbol recursively
244
+ */
245
+ function formatDocumentSymbol(symbol: DocumentSymbol, depth = 0): string[] {
246
+ const results: string[] = [];
247
+ const indent = " ".repeat(depth);
248
+ const kindName = getSymbolKindName(symbol.kind);
249
+ let line = `${indent}${symbol.name} (${kindName})`;
250
+
251
+ if (symbol.detail) {
252
+ line += ` ${symbol.detail}`;
253
+ }
254
+
255
+ const startLine = symbol.range.start.line + 1;
256
+ line += ` - Line ${startLine}`;
257
+ results.push(line);
258
+
259
+ if (symbol.children && symbol.children.length > 0) {
260
+ for (const child of symbol.children) {
261
+ results.push(...formatDocumentSymbol(child, depth + 1));
262
+ }
263
+ }
264
+
265
+ return results;
266
+ }
267
+
268
+ /**
269
+ * Formats the result of a documentSymbol operation
270
+ */
271
+ function formatDocumentSymbolResult(
272
+ result: DocumentSymbol[] | SymbolInformation[] | null,
273
+ workdir?: string,
274
+ ): string {
275
+ if (!result || result.length === 0) {
276
+ return "No symbols found in document. This may occur if the file is empty, not supported by the LSP server, or if the server has not fully indexed the file.";
277
+ }
278
+
279
+ const first = result[0];
280
+ if (first && "location" in first) {
281
+ return formatWorkspaceSymbolResult(result as SymbolInformation[], workdir);
282
+ }
283
+
284
+ const lines = ["Document symbols:"];
285
+ for (const symbol of result as DocumentSymbol[]) {
286
+ lines.push(...formatDocumentSymbol(symbol));
287
+ }
288
+ return lines.join("\n");
289
+ }
290
+
291
+ /**
292
+ * Formats the result of a workspaceSymbol operation
293
+ */
294
+ function formatWorkspaceSymbolResult(
295
+ result: SymbolInformation[] | null,
296
+ workdir?: string,
297
+ ): string {
298
+ if (!result || result.length === 0) {
299
+ return "No symbols found in workspace. This may occur if the workspace is empty, or if the LSP server has not finished indexing the project.";
300
+ }
301
+
302
+ const validSymbols = result.filter((s) => s && s.location && s.location.uri);
303
+ if (validSymbols.length === 0) {
304
+ return "No symbols found in workspace. This may occur if the workspace is empty, or if the LSP server has not finished indexing the project.";
305
+ }
306
+
307
+ const lines = [
308
+ `Found ${validSymbols.length} symbol${validSymbols.length === 1 ? "" : "s"} in workspace:`,
309
+ ];
310
+ const grouped = groupItemsByUri(validSymbols, workdir);
311
+
312
+ for (const [path, symbols] of grouped) {
313
+ lines.push(`\n${path}:`);
314
+ for (const s of symbols) {
315
+ const kindName = getSymbolKindName(s.kind);
316
+ const startLine = s.location.range.start.line + 1;
317
+ let line = ` ${s.name} (${kindName}) - Line ${startLine}`;
318
+ if (s.containerName) {
319
+ line += ` in ${s.containerName}`;
320
+ }
321
+ lines.push(line);
322
+ }
323
+ }
324
+
325
+ return lines.join("\n");
326
+ }
327
+
328
+ /**
329
+ * Formats a call hierarchy item
330
+ */
331
+ function formatCallHierarchyItem(
332
+ item: CallHierarchyItem,
333
+ workdir?: string,
334
+ ): string {
335
+ if (!item.uri) {
336
+ logger.warn("formatCallHierarchyItem: CallHierarchyItem has undefined URI");
337
+ return `${item.name} (${getSymbolKindName(item.kind)}) - <unknown location>`;
338
+ }
339
+
340
+ const path = formatUri(item.uri, workdir);
341
+ const startLine = item.range.start.line + 1;
342
+ const kindName = getSymbolKindName(item.kind);
343
+ let line = `${item.name} (${kindName}) - ${path}:${startLine}`;
344
+
345
+ if (item.detail) {
346
+ line += ` [${item.detail}]`;
347
+ }
348
+ return line;
349
+ }
350
+
351
+ /**
352
+ * Formats the result of a prepareCallHierarchy operation
353
+ */
354
+ function formatPrepareCallHierarchyResult(
355
+ result: CallHierarchyItem[] | null,
356
+ workdir?: string,
357
+ ): string {
358
+ if (!result || result.length === 0) {
359
+ return "No call hierarchy item found at this position";
360
+ }
361
+
362
+ if (result.length === 1) {
363
+ return `Call hierarchy item: ${formatCallHierarchyItem(result[0], workdir)}`;
364
+ }
365
+
366
+ const lines = [`Found ${result.length} call hierarchy items:`];
367
+ for (const item of result) {
368
+ lines.push(` ${formatCallHierarchyItem(item, workdir)}`);
369
+ }
370
+ return lines.join("\n");
371
+ }
372
+
373
+ /**
374
+ * Formats the result of an incomingCalls operation
375
+ */
376
+ function formatIncomingCallsResult(
377
+ result: CallHierarchyIncomingCall[] | null,
378
+ workdir?: string,
379
+ ): string {
380
+ if (!result || result.length === 0) {
381
+ return "No incoming calls found (nothing calls this function)";
382
+ }
383
+
384
+ const lines = [
385
+ `Found ${result.length} incoming call${result.length === 1 ? "" : "s"}:`,
386
+ ];
387
+ const grouped = new Map<string, CallHierarchyIncomingCall[]>();
388
+
389
+ for (const call of result) {
390
+ if (!call.from) {
391
+ logger.warn(
392
+ "formatIncomingCallsResult: CallHierarchyIncomingCall has undefined from field",
393
+ );
394
+ continue;
395
+ }
396
+ const path = formatUri(call.from.uri, workdir);
397
+ const existing = grouped.get(path);
398
+ if (existing) {
399
+ existing.push(call);
400
+ } else {
401
+ grouped.set(path, [call]);
402
+ }
403
+ }
404
+
405
+ for (const [path, calls] of grouped) {
406
+ lines.push(`\n${path}:`);
407
+ for (const call of calls) {
408
+ if (!call.from) continue;
409
+ const kindName = getSymbolKindName(call.from.kind);
410
+ const startLine = call.from.range.start.line + 1;
411
+ let line = ` ${call.from.name} (${kindName}) - Line ${startLine}`;
412
+
413
+ if (call.fromRanges && call.fromRanges.length > 0) {
414
+ const ranges = call.fromRanges
415
+ .map((r) => `${r.start.line + 1}:${r.start.character + 1}`)
416
+ .join(", ");
417
+ line += ` [calls at: ${ranges}]`;
418
+ }
419
+ lines.push(line);
420
+ }
421
+ }
422
+
423
+ return lines.join("\n");
424
+ }
425
+
426
+ /**
427
+ * Formats the result of an outgoingCalls operation
428
+ */
429
+ function formatOutgoingCallsResult(
430
+ result: CallHierarchyOutgoingCall[] | null,
431
+ workdir?: string,
432
+ ): string {
433
+ if (!result || result.length === 0) {
434
+ return "No outgoing calls found (this function calls nothing)";
435
+ }
436
+
437
+ const lines = [
438
+ `Found ${result.length} outgoing call${result.length === 1 ? "" : "s"}:`,
439
+ ];
440
+ const grouped = new Map<string, CallHierarchyOutgoingCall[]>();
441
+
442
+ for (const call of result) {
443
+ if (!call.to) {
444
+ logger.warn(
445
+ "formatOutgoingCallsResult: CallHierarchyOutgoingCall has undefined to field",
446
+ );
447
+ continue;
448
+ }
449
+ const path = formatUri(call.to.uri, workdir);
450
+ const existing = grouped.get(path);
451
+ if (existing) {
452
+ existing.push(call);
453
+ } else {
454
+ grouped.set(path, [call]);
455
+ }
456
+ }
457
+
458
+ for (const [path, calls] of grouped) {
459
+ lines.push(`\n${path}:`);
460
+ for (const call of calls) {
461
+ if (!call.to) continue;
462
+ const kindName = getSymbolKindName(call.to.kind);
463
+ const startLine = call.to.range.start.line + 1;
464
+ let line = ` ${call.to.name} (${kindName}) - Line ${startLine}`;
465
+
466
+ if (call.fromRanges && call.fromRanges.length > 0) {
467
+ const ranges = call.fromRanges
468
+ .map((r) => `${r.start.line + 1}:${r.start.character + 1}`)
469
+ .join(", ");
470
+ line += ` [called from: ${ranges}]`;
471
+ }
472
+ lines.push(line);
473
+ }
474
+ }
475
+
476
+ return lines.join("\n");
477
+ }
478
+
479
+ /**
480
+ * LSP tool plugin - interact with LSP servers for code intelligence
481
+ */
482
+ export const lspTool: ToolPlugin = {
483
+ name: "LSP",
484
+ config: {
485
+ type: "function",
486
+ function: {
487
+ name: "LSP",
488
+ description: `Interact with Language Server Protocol (LSP) servers to get code intelligence features.
489
+
490
+ Supported operations:
491
+ - goToDefinition: Find where a symbol is defined
492
+ - findReferences: Find all references to a symbol
493
+ - hover: Get hover information (documentation, type info) for a symbol
494
+ - documentSymbol: Get all symbols (functions, classes, variables) in a document
495
+ - workspaceSymbol: Search for symbols across the entire workspace
496
+ - goToImplementation: Find implementations of an interface or abstract method
497
+ - prepareCallHierarchy: Get call hierarchy item at a position (functions/methods)
498
+ - incomingCalls: Find all functions/methods that call the function at a position
499
+ - outgoingCalls: Find all functions/methods called by the function at a position
500
+
501
+ All operations require:
502
+ - filePath: The file to operate on
503
+ - line: The line number (1-based, as shown in editors)
504
+ - character: The character offset (1-based, as shown in editors)
505
+
506
+ Note: LSP servers must be configured for the file type. If no server is available, an error will be returned.`,
507
+ parameters: {
508
+ type: "object",
509
+ properties: {
510
+ operation: {
511
+ type: "string",
512
+ enum: [
513
+ "goToDefinition",
514
+ "findReferences",
515
+ "hover",
516
+ "documentSymbol",
517
+ "workspaceSymbol",
518
+ "goToImplementation",
519
+ "prepareCallHierarchy",
520
+ "incomingCalls",
521
+ "outgoingCalls",
522
+ ],
523
+ description: "The LSP operation to perform",
524
+ },
525
+ filePath: {
526
+ type: "string",
527
+ description: "The absolute or relative path to the file",
528
+ },
529
+ line: {
530
+ type: "number",
531
+ description: "The line number (1-based, as shown in editors)",
532
+ },
533
+ character: {
534
+ type: "number",
535
+ description: "The character offset (1-based, as shown in editors)",
536
+ },
537
+ },
538
+ required: ["operation", "filePath", "line", "character"],
539
+ },
540
+ },
541
+ },
542
+ execute: async (
543
+ args: Record<string, unknown>,
544
+ context: ToolContext,
545
+ ): Promise<ToolResult> => {
546
+ const { operation, filePath, line, character } = args as {
547
+ operation: string;
548
+ filePath: string;
549
+ line: number;
550
+ character: number;
551
+ };
552
+
553
+ if (context.lspManager) {
554
+ try {
555
+ const result = await context.lspManager.execute({
556
+ operation,
557
+ filePath,
558
+ line,
559
+ character,
560
+ });
561
+
562
+ if (!result.success) {
563
+ return {
564
+ success: false,
565
+ content: "",
566
+ error: result.content,
567
+ };
568
+ }
569
+
570
+ const rawResult = JSON.parse(result.content);
571
+ let formattedContent: string;
572
+ switch (operation) {
573
+ case "goToDefinition":
574
+ case "goToImplementation":
575
+ formattedContent = formatGoToDefinitionResult(
576
+ rawResult as
577
+ | Location
578
+ | Location[]
579
+ | LocationLink
580
+ | LocationLink[]
581
+ | null,
582
+ context.workdir,
583
+ );
584
+ break;
585
+ case "findReferences":
586
+ formattedContent = formatFindReferencesResult(
587
+ rawResult as Location[] | null,
588
+ context.workdir,
589
+ );
590
+ break;
591
+ case "hover":
592
+ formattedContent = formatHoverResult(rawResult as Hover | null);
593
+ break;
594
+ case "documentSymbol":
595
+ formattedContent = formatDocumentSymbolResult(
596
+ rawResult as DocumentSymbol[] | SymbolInformation[] | null,
597
+ context.workdir,
598
+ );
599
+ break;
600
+ case "workspaceSymbol":
601
+ formattedContent = formatWorkspaceSymbolResult(
602
+ rawResult as SymbolInformation[] | null,
603
+ context.workdir,
604
+ );
605
+ break;
606
+ case "prepareCallHierarchy":
607
+ formattedContent = formatPrepareCallHierarchyResult(
608
+ rawResult as CallHierarchyItem[] | null,
609
+ context.workdir,
610
+ );
611
+ break;
612
+ case "incomingCalls":
613
+ formattedContent = formatIncomingCallsResult(
614
+ rawResult as CallHierarchyIncomingCall[] | null,
615
+ context.workdir,
616
+ );
617
+ break;
618
+ case "outgoingCalls":
619
+ formattedContent = formatOutgoingCallsResult(
620
+ rawResult as CallHierarchyOutgoingCall[] | null,
621
+ context.workdir,
622
+ );
623
+ break;
624
+ default:
625
+ formattedContent = JSON.stringify(rawResult, null, 2);
626
+ }
627
+
628
+ return {
629
+ success: true,
630
+ content: formattedContent,
631
+ };
632
+ } catch (error) {
633
+ return {
634
+ success: false,
635
+ content: "",
636
+ error: `LSP operation failed: ${error instanceof Error ? error.message : String(error)}`,
637
+ };
638
+ }
639
+ }
640
+
641
+ if (!context.mcpManager) {
642
+ return {
643
+ success: false,
644
+ content: "",
645
+ error: "MCP manager not available in tool context",
646
+ };
647
+ }
648
+
649
+ try {
650
+ // Call the MCP tool named "LSP"
651
+ // We assume there is an MCP server that provides this tool
652
+ const result = await context.mcpManager.executeMcpTool("LSP", {
653
+ operation,
654
+ filePath,
655
+ line,
656
+ character,
657
+ });
658
+
659
+ if (!result.success) {
660
+ return {
661
+ success: false,
662
+ content: "",
663
+ error: result.content,
664
+ };
665
+ }
666
+
667
+ let rawResult: unknown;
668
+ try {
669
+ rawResult = JSON.parse(result.content);
670
+ } catch {
671
+ // If it's not JSON, it might already be formatted or an error message
672
+ return {
673
+ success: true,
674
+ content: result.content,
675
+ };
676
+ }
677
+
678
+ let formattedContent: string;
679
+ switch (operation) {
680
+ case "goToDefinition":
681
+ case "goToImplementation":
682
+ formattedContent = formatGoToDefinitionResult(
683
+ rawResult as
684
+ | Location
685
+ | Location[]
686
+ | LocationLink
687
+ | LocationLink[]
688
+ | null,
689
+ context.workdir,
690
+ );
691
+ break;
692
+ case "findReferences":
693
+ formattedContent = formatFindReferencesResult(
694
+ rawResult as Location[] | null,
695
+ context.workdir,
696
+ );
697
+ break;
698
+ case "hover":
699
+ formattedContent = formatHoverResult(rawResult as Hover | null);
700
+ break;
701
+ case "documentSymbol":
702
+ formattedContent = formatDocumentSymbolResult(
703
+ rawResult as DocumentSymbol[] | SymbolInformation[] | null,
704
+ context.workdir,
705
+ );
706
+ break;
707
+ case "workspaceSymbol":
708
+ formattedContent = formatWorkspaceSymbolResult(
709
+ rawResult as SymbolInformation[] | null,
710
+ context.workdir,
711
+ );
712
+ break;
713
+ case "prepareCallHierarchy":
714
+ formattedContent = formatPrepareCallHierarchyResult(
715
+ rawResult as CallHierarchyItem[] | null,
716
+ context.workdir,
717
+ );
718
+ break;
719
+ case "incomingCalls":
720
+ formattedContent = formatIncomingCallsResult(
721
+ rawResult as CallHierarchyIncomingCall[] | null,
722
+ context.workdir,
723
+ );
724
+ break;
725
+ case "outgoingCalls":
726
+ formattedContent = formatOutgoingCallsResult(
727
+ rawResult as CallHierarchyOutgoingCall[] | null,
728
+ context.workdir,
729
+ );
730
+ break;
731
+ default:
732
+ formattedContent = JSON.stringify(rawResult, null, 2);
733
+ }
734
+
735
+ return {
736
+ success: true,
737
+ content: formattedContent,
738
+ };
739
+ } catch (error) {
740
+ return {
741
+ success: false,
742
+ content: "",
743
+ error: `LSP operation failed: ${error instanceof Error ? error.message : String(error)}`,
744
+ };
745
+ }
746
+ },
747
+ formatCompactParams: (
748
+ params: Record<string, unknown>,
749
+ context: ToolContext,
750
+ ): string => {
751
+ const { operation, filePath, line, character } = params as {
752
+ operation: string;
753
+ filePath: string;
754
+ line: number;
755
+ character: number;
756
+ };
757
+ const displayPath = getDisplayPath(filePath, context.workdir);
758
+ return `${operation} ${displayPath}:${line}:${character}`;
759
+ },
760
+ };