@ian-pascoe/pi-lsp 0.1.0 → 0.2.0

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.
@@ -18,6 +18,8 @@ export interface LspServerRoutingDefinition {
18
18
  readonly serverId: string;
19
19
  /** Languages and file patterns accepted by this server. */
20
20
  readonly languages: readonly LspServerLanguage[];
21
+ /** Exclude this server when none of its root markers exists above the requested file. */
22
+ readonly requireRootMarker?: boolean;
21
23
  /** Basename glob patterns that select this server instance's nearest workspace root. */
22
24
  readonly rootMarkers?: readonly string[];
23
25
  }
@@ -71,6 +73,7 @@ export type LspServerFailureCode =
71
73
  | "no-capable-server"
72
74
  | "no-matching-server"
73
75
  | "request-failed"
76
+ | "root-marker-not-found"
74
77
  | "server-unavailable";
75
78
 
76
79
  /** Preserves one matching server's failure without discarding sibling successes. */
@@ -79,8 +82,6 @@ export interface LspServerFailure {
79
82
  readonly code: LspServerFailureCode;
80
83
  /** Searchable caller-facing error prefixed with `Pi LSP:`. */
81
84
  readonly message: string;
82
- /** Selected workspace root when routing reached a concrete Server Instance. */
83
- readonly rootPath?: string;
84
85
  /** Configured server ID, or the requested missing ID. */
85
86
  readonly serverId: string;
86
87
  }
@@ -97,7 +98,7 @@ export interface LspServerSuccess<T> {
97
98
 
98
99
  /** Keeps successful multi-server reads useful when independent servers fail. */
99
100
  export interface LspServerReadResult<T> {
100
- /** Labeled failures in deterministic route order. */
101
+ /** Labeled operational failures in deterministic route order. */
101
102
  readonly failures: readonly LspServerFailure[];
102
103
  /** Labeled successful values in deterministic route order. */
103
104
  readonly successes: readonly LspServerSuccess<T>[];
@@ -132,8 +133,6 @@ export interface LspServerStatusEntry {
132
133
 
133
134
  /** Reports configuration failures and session-scoped Server Instance states. */
134
135
  export interface LspServerManagerStatus {
135
- /** False when either authored `lsp` settings layer was malformed. */
136
- readonly enabled: boolean;
137
136
  /** Server entries ordered by ID and then root. */
138
137
  readonly servers: readonly LspServerStatusEntry[];
139
138
  /** Strict settings failures kept visible until Pi `/reload`. */
@@ -167,8 +166,11 @@ function findNearestLspRoot(
167
166
  rootMarkers: readonly string[] | undefined,
168
167
  ancestorDirectories: readonly LspAncestorDirectory[],
169
168
  cwd: string,
170
- ): string {
171
- if (rootMarkers === undefined || rootMarkers.length === 0) return resolve(cwd);
169
+ requireRootMarker: boolean,
170
+ ): string | undefined {
171
+ if (rootMarkers === undefined || rootMarkers.length === 0) {
172
+ return requireRootMarker ? undefined : resolve(cwd);
173
+ }
172
174
  for (const directory of ancestorDirectories) {
173
175
  if (
174
176
  directory.entryNames.some((entryName) =>
@@ -178,7 +180,7 @@ function findNearestLspRoot(
178
180
  return directory.path;
179
181
  }
180
182
  }
181
- return resolve(cwd);
183
+ return requireRootMarker ? undefined : resolve(cwd);
182
184
  }
183
185
 
184
186
  /** Route one file to every matching configured server in stable settings-map order. */
@@ -196,10 +198,17 @@ export function routeLspServersForFile(
196
198
  languageMatchesFile(candidate, normalizedFilePath),
197
199
  );
198
200
  if (language === undefined) continue;
201
+ const rootPath = findNearestLspRoot(
202
+ serverDefinition.rootMarkers,
203
+ ancestorDirectories,
204
+ cwd,
205
+ serverDefinition.requireRootMarker ?? false,
206
+ );
207
+ if (rootPath === undefined) continue;
199
208
  routes.push({
200
209
  serverId: serverDefinition.serverId,
201
210
  language,
202
- rootPath: findNearestLspRoot(serverDefinition.rootMarkers, ancestorDirectories, cwd),
211
+ rootPath,
203
212
  });
204
213
  }
205
214
 
@@ -235,7 +244,6 @@ function unavailableFailure(route: LspServerRoute, error: string): LspServerFail
235
244
  return {
236
245
  code: "server-unavailable",
237
246
  message: `Pi LSP: server ${route.serverId} is unavailable for ${route.rootPath}: ${error}`,
238
- rootPath: route.rootPath,
239
247
  serverId: route.serverId,
240
248
  };
241
249
  }
@@ -275,26 +283,32 @@ export class LspServerManager<TClient extends LspManagedServerClient = LspManage
275
283
  }
276
284
  }
277
285
  return {
278
- enabled: this.input.settings.enabled,
279
286
  servers,
280
287
  warnings: this.input.settings.warnings,
281
288
  };
282
289
  }
283
290
 
284
- /** Resolve all matching Server Definitions and nearest roots without starting clients. */
285
- async routeFile(filePath: string): Promise<readonly LspServerRoute[]> {
286
- if (!this.input.settings.enabled) return [];
291
+ private async routeFile(filePath: string): Promise<readonly LspServerRoute[]> {
287
292
  const absolutePath = resolve(this.input.cwd, normalizeLspFilePath(filePath));
288
293
  const ancestors = await readLspAncestorDirectories(absolutePath);
289
294
  const definitions = [...this.input.settings.servers.values()].map((definition) => ({
290
295
  languages: definition.languages,
296
+ requireRootMarker: definition.requireRootMarker,
291
297
  rootMarkers: definition.rootMarkers,
292
298
  serverId: definition.id,
293
299
  }));
294
300
  return routeLspServersForFile(definitions, absolutePath, this.input.cwd, ancestors);
295
301
  }
296
302
 
297
- /** Query every matching capable instance while retaining independent successes and failures. */
303
+ /** Report whether any Server Definition accepts the file language before activation gating. */
304
+ hasConfiguredLanguageServerForFile(filePath: string): boolean {
305
+ const absolutePath = resolve(this.input.cwd, normalizeLspFilePath(filePath));
306
+ return [...this.input.settings.servers.values()].some((definition) =>
307
+ definition.languages.some((language) => languageMatchesFile(language, absolutePath)),
308
+ );
309
+ }
310
+
311
+ /** Query matching capable instances while retaining independent operational failures. */
298
312
  async runRead<T>(
299
313
  filePath: string,
300
314
  serverId: string | undefined,
@@ -310,14 +324,14 @@ export class LspServerManager<TClient extends LspManagedServerClient = LspManage
310
324
  }
311
325
 
312
326
  const outcomes = await Promise.all(
313
- routes.map(async (route): Promise<LspServerSuccess<T> | LspServerFailure> => {
327
+ routes.map(async (route): Promise<LspServerSuccess<T> | LspServerFailure | undefined> => {
314
328
  const resolution = await this.ensureClient(route);
315
329
  if (resolution.kind === "failure") return resolution.failure;
316
330
  if (!isCapable(resolution.instance.client)) {
331
+ if (serverId === undefined) return undefined;
317
332
  return {
318
333
  code: "no-capable-server",
319
334
  message: `Pi LSP: server ${route.serverId} does not support the requested operation`,
320
- rootPath: route.rootPath,
321
335
  serverId: route.serverId,
322
336
  };
323
337
  }
@@ -331,7 +345,6 @@ export class LspServerManager<TClient extends LspManagedServerClient = LspManage
331
345
  return {
332
346
  code: "request-failed",
333
347
  message: `Pi LSP: server ${route.serverId} request failed: ${describeLspError(error)}`,
334
- rootPath: route.rootPath,
335
348
  serverId: route.serverId,
336
349
  };
337
350
  }
@@ -341,9 +354,17 @@ export class LspServerManager<TClient extends LspManagedServerClient = LspManage
341
354
  const failures: LspServerFailure[] = [];
342
355
  const successes: LspServerSuccess<T>[] = [];
343
356
  for (const outcome of outcomes) {
357
+ if (outcome === undefined) continue;
344
358
  if ("code" in outcome) failures.push(outcome);
345
359
  else successes.push(outcome);
346
360
  }
361
+ if (failures.length === 0 && successes.length === 0) {
362
+ failures.push({
363
+ code: "no-capable-server",
364
+ message: "Pi LSP: no matching server supports the requested read operation",
365
+ serverId: "*",
366
+ });
367
+ }
347
368
  return { failures, successes };
348
369
  }
349
370
 
@@ -446,10 +467,23 @@ export class LspServerManager<TClient extends LspManagedServerClient = LspManage
446
467
  }
447
468
 
448
469
  private noMatchingFailure(serverId: string | undefined, filePath: string): LspServerFailure {
470
+ const normalizedFilePath = normalizeLspFilePath(filePath);
471
+ const definition =
472
+ serverId === undefined ? undefined : this.input.settings.servers.get(serverId);
473
+ if (
474
+ definition?.requireRootMarker === true &&
475
+ definition.languages.some((language) => languageMatchesFile(language, normalizedFilePath))
476
+ ) {
477
+ return {
478
+ code: "root-marker-not-found",
479
+ message: `Pi LSP: required root marker not found for server ${definition.id} and ${normalizedFilePath}; expected one of: ${definition.rootMarkers.join(", ")}`,
480
+ serverId: definition.id,
481
+ };
482
+ }
449
483
  const requestedServer = serverId === undefined ? "any configured server" : `server ${serverId}`;
450
484
  return {
451
485
  code: "no-matching-server",
452
- message: `Pi LSP: ${requestedServer} does not match ${normalizeLspFilePath(filePath)}`,
486
+ message: `Pi LSP: ${requestedServer} does not match ${normalizedFilePath}`,
453
487
  serverId: serverId ?? "*",
454
488
  };
455
489
  }
@@ -1,9 +1,6 @@
1
1
  import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
 
4
- /** The maximum retained byte length for one language server's stderr log. */
5
- export const MAX_SERVER_STDERR_BYTES = 1024 * 1024;
6
-
7
4
  /** Owns private Result Spill and bounded stderr files for one Pi session. */
8
5
  export interface LspSessionFiles {
9
6
  /** Private directory removed when the Pi session shuts down. */
@@ -12,15 +9,12 @@ export interface LspSessionFiles {
12
9
  writeResultSpill(output: string): Promise<string>;
13
10
  /** Create or return the bounded stderr file path for one language server. */
14
11
  getServerStderrPath(serverId: string): Promise<string>;
15
- /** Retain the latest one megabyte of one language server's stderr stream. */
16
- appendServerStderr(serverId: string, chunk: Uint8Array): Promise<string>;
17
12
  /** Remove all session files after queued writes finish. */
18
13
  close(): Promise<void>;
19
14
  }
20
15
 
21
16
  interface ServerStderrFile {
22
17
  readonly path: string;
23
- content: Buffer;
24
18
  }
25
19
 
26
20
  class LspSessionFileStore implements LspSessionFiles {
@@ -45,22 +39,7 @@ class LspSessionFileStore implements LspSessionFiles {
45
39
  getServerStderrPath(serverId: string): Promise<string> {
46
40
  const stderrFile = this.serverStderrFile(serverId);
47
41
  return this.enqueueSessionFileWrite(async () => {
48
- await writeFile(stderrFile.path, stderrFile.content, { mode: 0o600 });
49
- await chmod(stderrFile.path, 0o600);
50
- return stderrFile.path;
51
- });
52
- }
53
-
54
- appendServerStderr(serverId: string, chunk: Uint8Array): Promise<string> {
55
- const stderrFile = this.serverStderrFile(serverId);
56
- const copiedChunk = Buffer.from(chunk);
57
- return this.enqueueSessionFileWrite(async () => {
58
- const combined = Buffer.concat([stderrFile.content, copiedChunk]);
59
- stderrFile.content =
60
- combined.length <= MAX_SERVER_STDERR_BYTES
61
- ? combined
62
- : Buffer.from(combined.subarray(combined.length - MAX_SERVER_STDERR_BYTES));
63
- await writeFile(stderrFile.path, stderrFile.content, { mode: 0o600 });
42
+ await writeFile(stderrFile.path, "", { mode: 0o600 });
64
43
  await chmod(stderrFile.path, 0o600);
65
44
  return stderrFile.path;
66
45
  });
@@ -81,7 +60,6 @@ class LspSessionFileStore implements LspSessionFiles {
81
60
  if (existing !== undefined) return existing;
82
61
  const stderrFile = {
83
62
  path: join(this.directoryPath, `server-stderr-${this.nextFileIndex++}.log`),
84
- content: Buffer.alloc(0),
85
63
  };
86
64
  this.stderrFiles.set(serverId, stderrFile);
87
65
  return stderrFile;
@@ -332,7 +332,6 @@ const RegularFileSnapshotSchema = Type.Object(
332
332
  {
333
333
  kind: Type.Literal("file"),
334
334
  content_base64: Type.String(),
335
- hash: Type.String({ minLength: 64, maxLength: 64 }),
336
335
  mode: Type.Integer({ minimum: 0 }),
337
336
  },
338
337
  { additionalProperties: false },
@@ -345,13 +344,17 @@ const SymlinkSnapshotSchema = Type.Object(
345
344
  },
346
345
  { additionalProperties: false },
347
346
  );
348
- const FileSnapshotSchema = Type.Union([
347
+ /** Canonical pre-mutation snapshot of one filesystem path used by guarded rollback. */
348
+ export const FileSnapshotSchema = Type.Union([
349
349
  MissingFileSnapshotSchema,
350
350
  RegularFileSnapshotSchema,
351
351
  SymlinkSnapshotSchema,
352
352
  ]);
353
- const ExistingFileSnapshotSchema = Type.Union([RegularFileSnapshotSchema, SymlinkSnapshotSchema]);
354
- const WorkspaceEditOperationSchema = Type.Union([
353
+ export const ExistingFileSnapshotSchema = Type.Union([
354
+ RegularFileSnapshotSchema,
355
+ SymlinkSnapshotSchema,
356
+ ]);
357
+ export const WorkspaceEditOperationSchema = Type.Union([
355
358
  Type.Object(
356
359
  {
357
360
  kind: Type.Literal("modify"),
@@ -440,7 +443,6 @@ const WorkspaceEditApplyDetailsSchema = Type.Object(
440
443
  changed_paths: Type.Array(AbsolutePathSchema),
441
444
  preview_records: Type.Optional(Type.Array(LspWorkspaceEditPreviewRecordSchema)),
442
445
  state: Type.Union([Type.Literal("applied"), Type.Literal("partial_failure")]),
443
- recovery_failure_paths: Type.Optional(Type.Array(AbsolutePathSchema)),
444
446
  },
445
447
  { additionalProperties: false },
446
448
  );
@@ -460,9 +462,3 @@ export type LspWorkspaceEditPreviewRecord = Static<typeof LspWorkspaceEditPrevie
460
462
 
461
463
  /** A normalized per-server outcome used when rendering an LSP read operation. */
462
464
  export type ServerOperationOutcome = Static<typeof ServerOperationOutcomeSchema>;
463
-
464
- /** A persisted Workspace Edit Preview that can be rebuilt from the active session branch. */
465
- export type WorkspaceEditPreviewDetails = Static<typeof WorkspaceEditPreviewDetailsSchema>;
466
-
467
- /** The result of applying one guarded Workspace Edit Preview. */
468
- export type WorkspaceEditApplyDetails = Static<typeof WorkspaceEditApplyDetailsSchema>;
@@ -32,6 +32,24 @@ export function formatLspToolValue(value: LSPAny): string {
32
32
  return text === undefined ? "null" : text;
33
33
  }
34
34
 
35
+ /** Truncate model-visible text against Pi limits, spilling the complete text when truncated. */
36
+ export async function truncateLspOutputText(
37
+ text: string,
38
+ sessionFiles: LspSessionFiles,
39
+ subject: string,
40
+ ): Promise<{ readonly text: string; readonly spillPath?: string }> {
41
+ const truncation = truncateHead(text, {
42
+ maxBytes: DEFAULT_MAX_BYTES,
43
+ maxLines: DEFAULT_MAX_LINES,
44
+ });
45
+ if (!truncation.truncated) return { text };
46
+ const spillPath = await sessionFiles.writeResultSpill(text);
47
+ return {
48
+ text: `${truncation.content}\n\n[Pi LSP: ${subject} truncated; complete Result Spill: ${spillPath}]`,
49
+ spillPath,
50
+ };
51
+ }
52
+
35
53
  /** Validate normalized details, truncate model-visible text, and spill every complete oversized result. */
36
54
  export async function createLspToolOutput(
37
55
  text: string,
@@ -39,26 +57,20 @@ export async function createLspToolOutput(
39
57
  sessionFiles: LspSessionFiles,
40
58
  ): Promise<AgentToolResult<LspToolResultDetails>> {
41
59
  const normalizedDetails = Value.Parse(LspToolResultDetailsSchema, details);
42
- const truncated = truncateHead(text, {
43
- maxBytes: DEFAULT_MAX_BYTES,
44
- maxLines: DEFAULT_MAX_LINES,
45
- });
46
- if (!truncated.truncated) {
60
+ const truncated = await truncateLspOutputText(text, sessionFiles, "output");
61
+ if (truncated.spillPath === undefined) {
47
62
  return { content: [{ type: "text", text }], details: normalizedDetails };
48
63
  }
49
64
 
50
- const spillPath = await sessionFiles.writeResultSpill(text);
51
- const notice = `\n\n[Pi LSP: output truncated; complete Result Spill: ${spillPath}]`;
52
- const visibleText = `${truncated.content}${notice}`;
53
65
  const detailsWithSpill =
54
66
  normalizedDetails.kind === "operation"
55
67
  ? Value.Parse(LspToolResultDetailsSchema, {
56
68
  ...normalizedDetails,
57
- spill_path: spillPath,
69
+ spill_path: truncated.spillPath,
58
70
  })
59
71
  : normalizedDetails;
60
72
  return {
61
- content: [{ type: "text", text: visibleText }],
73
+ content: [{ type: "text", text: truncated.text }],
62
74
  details: detailsWithSpill,
63
75
  };
64
76
  }
@@ -16,6 +16,7 @@ import {
16
16
  type LspToolResultDetails,
17
17
  type ServerOperationOutcome,
18
18
  } from "./lsp-tool-contract.js";
19
+ import { pluralizedCount } from "./lsp-post-edit-diagnostics-rendering.js";
19
20
 
20
21
  /** Theme operations used by Pi LSP tool transcript rendering. */
21
22
  export type LspRenderTheme = Pick<Theme, "bold" | "fg">;
@@ -139,10 +140,6 @@ function semanticLspOperationMetric(
139
140
  return pluralizedCount(count, noun);
140
141
  }
141
142
 
142
- function pluralizedCount(count: number, noun: string): string {
143
- return `${count} ${noun}${count === 1 ? "" : "s"}`;
144
- }
145
-
146
143
  function outcomeColor(outcome: ServerOperationOutcome["outcome"]): ThemeColor {
147
144
  switch (outcome) {
148
145
  case "success":
package/src/lsp-tool.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import type { ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
+ import type {
5
+ AgentToolResult,
6
+ ExtensionContext,
7
+ ToolDefinition,
8
+ } from "@earendil-works/pi-coding-agent";
5
9
  import { type Static, Type } from "typebox";
6
10
  import { Value } from "typebox/value";
7
11
  import {
@@ -227,6 +231,23 @@ async function createLspToolOutput(
227
231
  );
228
232
  }
229
233
 
234
+ async function readOutput(
235
+ operation: LspToolParameters["operation"],
236
+ result: Promise<LspServerReadResult<LSPAny>>,
237
+ dependencies: LspToolDependencies,
238
+ ) {
239
+ const resolved = await result;
240
+ requireReadSuccess(resolved);
241
+ return createLspToolOutput(
242
+ formatLspToolValue({
243
+ results: readOperationValue(resolved),
244
+ warnings: resolved.failures.map(({ message }) => message),
245
+ }),
246
+ operationDetails(operation, readOperationOutcomes(resolved)),
247
+ dependencies,
248
+ );
249
+ }
250
+
230
251
  function parseLspToolParameters(input: LSPAny): LspToolParameters {
231
252
  try {
232
253
  return Value.Parse(LspToolParametersSchema, input);
@@ -440,15 +461,6 @@ async function normalizeProtocolResult(
440
461
  return Object.fromEntries(entries);
441
462
  }
442
463
 
443
- async function requestDocumentMethod(
444
- prepared: PreparedDocument,
445
- method: string,
446
- parameters: LSPAny,
447
- signal: AbortSignal | undefined,
448
- ): Promise<LSPAny> {
449
- return prepared.client.request<LSPAny>(method, parameters, signal);
450
- }
451
-
452
464
  function supportsResolveProvider(value: LSPAny): boolean {
453
465
  return protocolRecord(value)?.resolveProvider === true;
454
466
  }
@@ -545,9 +557,7 @@ async function workspacePreviewOutput(
545
557
  serverId: string,
546
558
  edit: WorkspaceEdit,
547
559
  positionEncoding: PositionEncodingKind,
548
- ): Promise<
549
- ReturnType<typeof createLspToolOutput> extends Promise<infer TResult> ? TResult : never
550
- > {
560
+ ): Promise<AgentToolResult<LspToolResultDetails>> {
551
561
  const preview = await dependencies.workspaceEdits.createPreview({
552
562
  edit,
553
563
  serverId,
@@ -617,8 +627,7 @@ async function executePositionRead(
617
627
  context: { includeDeclaration: parameters.include_declaration ?? true },
618
628
  };
619
629
  }
620
- let value = await requestDocumentMethod(
621
- prepared,
630
+ let value = await prepared.client.request<LSPAny>(
622
631
  capabilityMethod,
623
632
  requestParameters,
624
633
  signal,
@@ -1015,7 +1024,6 @@ async function executeApplyPreview(
1015
1024
  preview_id: parameters.preview_id,
1016
1025
  mutation_manifest: canonicalManifest,
1017
1026
  changed_paths: [...cause.recoveryFailures].sort((left, right) => left.localeCompare(right)),
1018
- recovery_failure_paths: [...cause.recoveryFailures],
1019
1027
  state: "partial_failure",
1020
1028
  },
1021
1029
  dependencies,
@@ -1127,72 +1135,42 @@ export function createLspToolDefinition(
1127
1135
  case "type_hierarchy":
1128
1136
  case "supertypes":
1129
1137
  case "subtypes":
1130
- case "prepare_rename": {
1131
- const result = await executePositionRead(dependencies, parameters, context, signal);
1132
- requireReadSuccess(result);
1133
- return createLspToolOutput(
1134
- formatLspToolValue({
1135
- results: readOperationValue(result),
1136
- warnings: result.failures.map(({ message }) => message),
1137
- }),
1138
- operationDetails(parameters.operation, readOperationOutcomes(result)),
1138
+ case "prepare_rename":
1139
+ return readOutput(
1140
+ parameters.operation,
1141
+ executePositionRead(dependencies, parameters, context, signal),
1139
1142
  dependencies,
1140
1143
  );
1141
- }
1142
1144
  case "diagnostics":
1143
1145
  case "document_symbols":
1144
1146
  case "document_links":
1145
1147
  case "folding_ranges":
1146
1148
  case "code_lenses":
1147
- case "document_colors": {
1148
- const result = await executeFileRead(dependencies, parameters, context, signal);
1149
- requireReadSuccess(result);
1150
- return createLspToolOutput(
1151
- formatLspToolValue({
1152
- results: readOperationValue(result),
1153
- warnings: result.failures.map(({ message }) => message),
1154
- }),
1155
- operationDetails(parameters.operation, readOperationOutcomes(result)),
1149
+ case "document_colors":
1150
+ return readOutput(
1151
+ parameters.operation,
1152
+ executeFileRead(dependencies, parameters, context, signal),
1156
1153
  dependencies,
1157
1154
  );
1158
- }
1159
1155
  case "workspace_diagnostics":
1160
- case "workspace_symbols": {
1161
- const result = await executeWorkspaceRead(dependencies, parameters, context, signal);
1162
- requireReadSuccess(result);
1163
- return createLspToolOutput(
1164
- formatLspToolValue({
1165
- results: readOperationValue(result),
1166
- warnings: result.failures.map(({ message }) => message),
1167
- }),
1168
- operationDetails(parameters.operation, readOperationOutcomes(result)),
1156
+ case "workspace_symbols":
1157
+ return readOutput(
1158
+ parameters.operation,
1159
+ executeWorkspaceRead(dependencies, parameters, context, signal),
1169
1160
  dependencies,
1170
1161
  );
1171
- }
1172
- case "selection_ranges": {
1173
- const result = await executeSelectionRanges(dependencies, parameters, context, signal);
1174
- requireReadSuccess(result);
1175
- return createLspToolOutput(
1176
- formatLspToolValue({
1177
- results: readOperationValue(result),
1178
- warnings: result.failures.map(({ message }) => message),
1179
- }),
1180
- operationDetails(parameters.operation, readOperationOutcomes(result)),
1162
+ case "selection_ranges":
1163
+ return readOutput(
1164
+ parameters.operation,
1165
+ executeSelectionRanges(dependencies, parameters, context, signal),
1181
1166
  dependencies,
1182
1167
  );
1183
- }
1184
- case "inlay_hints": {
1185
- const result = await executeInlayHints(dependencies, parameters, context, signal);
1186
- requireReadSuccess(result);
1187
- return createLspToolOutput(
1188
- formatLspToolValue({
1189
- results: readOperationValue(result),
1190
- warnings: result.failures.map(({ message }) => message),
1191
- }),
1192
- operationDetails(parameters.operation, readOperationOutcomes(result)),
1168
+ case "inlay_hints":
1169
+ return readOutput(
1170
+ parameters.operation,
1171
+ executeInlayHints(dependencies, parameters, context, signal),
1193
1172
  dependencies,
1194
1173
  );
1195
- }
1196
1174
  case "format_document":
1197
1175
  case "format_range":
1198
1176
  case "format_on_type":