@arnilo/prism-coding-agent 0.0.24 → 0.0.26

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.
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Host-selected language intelligence over one bounded in-package LSP client.
3
+ * Servers spawn only on first use; URIs confined to workspaceRoot; renames gated by ExecutionPolicy.
4
+ */
5
+ import { readFile } from "node:fs/promises";
6
+ import { extname, isAbsolute, relative, resolve } from "node:path";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import { assertExecutionAllowed, ExecutionDeniedError } from "@arnilo/prism";
9
+ import { atomicWriteUtf8File } from "../atomic-write.js";
10
+ import { withFileMutationQueue } from "../file-mutation-queue.js";
11
+ import { resolveContainedMutationPath } from "../mutation-path.js";
12
+ import { LspClient } from "./client.js";
13
+ import { LanguageIntelligenceError, resolveLanguageIntelligenceLimits, } from "./types.js";
14
+ const EXT_LANGUAGE = {
15
+ ".ts": "typescript",
16
+ ".tsx": "typescriptreact",
17
+ ".mts": "typescript",
18
+ ".cts": "typescript",
19
+ ".js": "javascript",
20
+ ".jsx": "javascriptreact",
21
+ ".mjs": "javascript",
22
+ ".cjs": "javascript",
23
+ ".py": "python",
24
+ ".go": "go",
25
+ ".rs": "rust",
26
+ ".java": "java",
27
+ ".json": "json",
28
+ ".md": "markdown",
29
+ ".css": "css",
30
+ ".html": "html",
31
+ };
32
+ export function createLanguageIntelligence(options) {
33
+ const workspaceRoot = resolve(options.workspaceRoot);
34
+ const limits = resolveLanguageIntelligenceLimits(options.limits);
35
+ const serverEntries = Object.entries(options.servers);
36
+ if (serverEntries.length === 0) {
37
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "servers map must not be empty");
38
+ }
39
+ if (serverEntries.length > limits.maxServers) {
40
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_LIMIT", `servers map has ${serverEntries.length} entries; maxServers is ${limits.maxServers}`);
41
+ }
42
+ for (const [name, spec] of serverEntries) {
43
+ assertHostServerSpec(name, spec);
44
+ }
45
+ const rootUri = pathToFileURL(workspaceRoot).href;
46
+ const clients = new Map();
47
+ const opened = new Map(); // serverName → opened URIs
48
+ const crashCounts = new Map();
49
+ let disposed = false;
50
+ function getOrCreateClient(serverName, spec) {
51
+ let client = clients.get(serverName);
52
+ if (client)
53
+ return client;
54
+ const crashes = crashCounts.get(serverName) ?? 0;
55
+ if (crashes > limits.maxRestartsPerServer) {
56
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_SERVER", `LSP server ${serverName} exceeded restart budget (${limits.maxRestartsPerServer})`);
57
+ }
58
+ client = new LspClient({
59
+ name: serverName,
60
+ command: spec.command,
61
+ args: spec.args ?? [],
62
+ env: spec.env,
63
+ cwd: workspaceRoot,
64
+ rootUri,
65
+ }, limits, {
66
+ onUnexpectedExit: () => {
67
+ clients.delete(serverName);
68
+ opened.delete(serverName);
69
+ crashCounts.set(serverName, (crashCounts.get(serverName) ?? 0) + 1);
70
+ },
71
+ });
72
+ clients.set(serverName, client);
73
+ return client;
74
+ }
75
+ function findServerForLanguage(languageId) {
76
+ for (const [name, spec] of serverEntries) {
77
+ if (spec.languages.includes(languageId))
78
+ return { name, spec };
79
+ }
80
+ return undefined;
81
+ }
82
+ async function clientForFile(file, signal) {
83
+ assertNotDisposed();
84
+ const candidate = resolveWorkspacePathLoose(workspaceRoot, file);
85
+ const languageId = languageIdForPath(candidate);
86
+ const match = findServerForLanguage(languageId);
87
+ if (!match) {
88
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", `No host server registered for language ${languageId} (file ${file})`);
89
+ }
90
+ const abs = await resolveWorkspaceFile(workspaceRoot, file);
91
+ const client = getOrCreateClient(match.name, match.spec);
92
+ try {
93
+ await client.ensureStarted(signal);
94
+ }
95
+ catch (error) {
96
+ clients.delete(match.name);
97
+ opened.delete(match.name);
98
+ crashCounts.set(match.name, (crashCounts.get(match.name) ?? 0) + 1);
99
+ try {
100
+ const retry = getOrCreateClient(match.name, match.spec);
101
+ await retry.ensureStarted(signal);
102
+ const uri = fileUriFor(workspaceRoot, abs);
103
+ await ensureOpen(retry, match.name, uri, abs, languageId, signal);
104
+ return { client: retry, abs, uri, languageId };
105
+ }
106
+ catch {
107
+ throw mapError(error);
108
+ }
109
+ }
110
+ const uri = fileUriFor(workspaceRoot, abs);
111
+ await ensureOpen(client, match.name, uri, abs, languageId, signal);
112
+ return { client, abs, uri, languageId };
113
+ }
114
+ async function ensureOpen(client, serverName, uri, abs, languageId, signal) {
115
+ let set = opened.get(serverName);
116
+ if (!set) {
117
+ set = new Set();
118
+ opened.set(serverName, set);
119
+ }
120
+ if (set.has(uri))
121
+ return;
122
+ const text = await readFile(abs, "utf8");
123
+ if (signal?.aborted) {
124
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_TIMEOUT", "LSP open aborted");
125
+ }
126
+ client.notify("textDocument/didOpen", {
127
+ textDocument: { uri, languageId, version: 1, text },
128
+ });
129
+ set.add(uri);
130
+ }
131
+ async function ensureAllStarted(signal) {
132
+ assertNotDisposed();
133
+ const out = [];
134
+ for (const [name, spec] of serverEntries) {
135
+ const client = getOrCreateClient(name, spec);
136
+ await client.ensureStarted(signal);
137
+ out.push(client);
138
+ }
139
+ return out;
140
+ }
141
+ function assertNotDisposed() {
142
+ if (disposed) {
143
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_SERVER", "LanguageIntelligence is disposed");
144
+ }
145
+ }
146
+ const api = {
147
+ async workspaceSymbols(query, opts) {
148
+ const clientsList = await ensureAllStarted(opts?.signal);
149
+ const symbols = [];
150
+ for (const client of clientsList) {
151
+ if (!client.hasCapability("workspaceSymbolProvider"))
152
+ continue;
153
+ const result = await client.request("workspace/symbol", { query }, opts?.signal);
154
+ for (const item of asArray(result)) {
155
+ const sym = normalizeSymbol(workspaceRoot, item);
156
+ if (sym)
157
+ symbols.push(sym);
158
+ if (symbols.length >= limits.maxResultsPerQuery) {
159
+ return symbols.slice(0, limits.maxResultsPerQuery);
160
+ }
161
+ }
162
+ }
163
+ return symbols;
164
+ },
165
+ async definitions(loc, opts) {
166
+ const { client, uri } = await clientForFile(loc.file, opts?.signal);
167
+ if (!client.hasCapability("definitionProvider")) {
168
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "Server does not advertise definitionProvider");
169
+ }
170
+ const result = await client.request("textDocument/definition", { textDocument: { uri }, position: { line: loc.line, character: loc.character } }, opts?.signal);
171
+ return takeLocations(workspaceRoot, result, limits.maxResultsPerQuery);
172
+ },
173
+ async references(loc, opts) {
174
+ const { client, uri } = await clientForFile(loc.file, opts?.signal);
175
+ if (!client.hasCapability("referencesProvider")) {
176
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "Server does not advertise referencesProvider");
177
+ }
178
+ const result = await client.request("textDocument/references", {
179
+ textDocument: { uri },
180
+ position: { line: loc.line, character: loc.character },
181
+ context: { includeDeclaration: true },
182
+ }, opts?.signal);
183
+ return takeLocations(workspaceRoot, result, limits.maxResultsPerQuery);
184
+ },
185
+ async diagnostics(file, opts) {
186
+ assertNotDisposed();
187
+ if (file) {
188
+ const { client, uri } = await clientForFile(file, opts?.signal);
189
+ return normalizeDiagnostics(workspaceRoot, uri, client.diagnosticsByUri.get(uri), limits.maxDiagnosticsPerFile);
190
+ }
191
+ await ensureAllStarted(opts?.signal);
192
+ const out = [];
193
+ for (const client of clients.values()) {
194
+ for (const [uri, diags] of client.diagnosticsByUri) {
195
+ out.push(...normalizeDiagnostics(workspaceRoot, uri, diags, limits.maxDiagnosticsPerFile));
196
+ if (out.length >= limits.maxResultsPerQuery) {
197
+ return out.slice(0, limits.maxResultsPerQuery);
198
+ }
199
+ }
200
+ }
201
+ return out;
202
+ },
203
+ async hover(loc, opts) {
204
+ const { client, uri } = await clientForFile(loc.file, opts?.signal);
205
+ if (!client.hasCapability("hoverProvider")) {
206
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "Server does not advertise hoverProvider");
207
+ }
208
+ const result = await client.request("textDocument/hover", { textDocument: { uri }, position: { line: loc.line, character: loc.character } }, opts?.signal);
209
+ if (!result || typeof result !== "object")
210
+ return undefined;
211
+ const contents = result.contents;
212
+ const text = hoverToText(contents);
213
+ return text === undefined ? undefined : { text };
214
+ },
215
+ async rename(loc, opts) {
216
+ if (!loc.newName || typeof loc.newName !== "string") {
217
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "newName is required");
218
+ }
219
+ const { client, uri } = await clientForFile(loc.file, opts?.signal);
220
+ if (!client.hasCapability("renameProvider")) {
221
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", "Server does not advertise renameProvider");
222
+ }
223
+ const result = await client.request("textDocument/rename", {
224
+ textDocument: { uri },
225
+ position: { line: loc.line, character: loc.character },
226
+ newName: loc.newName,
227
+ }, opts?.signal);
228
+ const edit = normalizeWorkspaceEdit(workspaceRoot, result, limits.maxResultsPerQuery);
229
+ await applyWorkspaceEdit(workspaceRoot, edit, options.policy, opts?.signal);
230
+ return edit;
231
+ },
232
+ async dispose() {
233
+ disposed = true;
234
+ const all = [...clients.values()];
235
+ clients.clear();
236
+ opened.clear();
237
+ await Promise.all(all.map((c) => c.dispose()));
238
+ },
239
+ };
240
+ return api;
241
+ }
242
+ function assertHostServerSpec(name, spec) {
243
+ if (!spec || typeof spec.command !== "string" || spec.command.length === 0) {
244
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", `Server ${name}: command is required`);
245
+ }
246
+ if (!Array.isArray(spec.languages) || spec.languages.length === 0) {
247
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", `Server ${name}: languages must be non-empty`);
248
+ }
249
+ if (spec.args !== undefined && !Array.isArray(spec.args)) {
250
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", `Server ${name}: args must be an array`);
251
+ }
252
+ }
253
+ function languageIdForPath(absPath) {
254
+ const ext = extname(absPath).toLowerCase();
255
+ return EXT_LANGUAGE[ext] ?? "plaintext";
256
+ }
257
+ function resolveWorkspacePathLoose(workspaceRoot, file) {
258
+ const root = resolve(workspaceRoot);
259
+ const candidate = resolve(root, file);
260
+ const rel = relative(root, candidate);
261
+ if (rel.startsWith("..") || isAbsolute(rel)) {
262
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `path escapes workspace root: ${file}`);
263
+ }
264
+ return candidate;
265
+ }
266
+ async function resolveWorkspaceFile(workspaceRoot, file) {
267
+ try {
268
+ return await resolveContainedMutationPath(workspaceRoot, file);
269
+ }
270
+ catch (error) {
271
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", error instanceof Error ? error.message : String(error));
272
+ }
273
+ }
274
+ function fileUriFor(workspaceRoot, absPath) {
275
+ const rootReal = resolve(workspaceRoot);
276
+ const abs = resolve(absPath);
277
+ const rel = relative(rootReal, abs);
278
+ if (rel.startsWith("..") || isAbsolute(rel)) {
279
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `URI escapes workspace: ${absPath}`);
280
+ }
281
+ return pathToFileURL(abs).href;
282
+ }
283
+ function uriToWorkspaceFile(workspaceRoot, uri) {
284
+ let abs;
285
+ try {
286
+ abs = resolve(fileUrlToPathSafe(uri));
287
+ }
288
+ catch {
289
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `Invalid file URI: ${uri}`);
290
+ }
291
+ const root = resolve(workspaceRoot);
292
+ const rel = relative(root, abs);
293
+ if (rel.startsWith("..") || isAbsolute(rel)) {
294
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `URI outside workspace: ${uri}`);
295
+ }
296
+ return rel.split("\\").join("/");
297
+ }
298
+ function fileUrlToPathSafe(uri) {
299
+ if (!uri.startsWith("file:")) {
300
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `Non-file URI rejected: ${uri}`);
301
+ }
302
+ try {
303
+ return fileURLToPath(uri);
304
+ }
305
+ catch {
306
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_WORKSPACE", `Invalid file URI: ${uri}`);
307
+ }
308
+ }
309
+ function asArray(value) {
310
+ if (value == null)
311
+ return [];
312
+ return Array.isArray(value) ? value : [value];
313
+ }
314
+ function takeLocations(workspaceRoot, result, max) {
315
+ const out = [];
316
+ for (const item of asArray(result)) {
317
+ const loc = normalizeLocation(workspaceRoot, item);
318
+ if (loc)
319
+ out.push(loc);
320
+ if (out.length >= max)
321
+ break;
322
+ }
323
+ return out;
324
+ }
325
+ function normalizeLocation(workspaceRoot, item) {
326
+ if (!item || typeof item !== "object")
327
+ return undefined;
328
+ const obj = item;
329
+ const uri = obj.uri ?? obj.targetUri;
330
+ if (typeof uri !== "string")
331
+ return undefined;
332
+ const start = obj.range?.start ?? obj.targetSelectionRange?.start ?? obj.targetRange?.start ?? { line: 0, character: 0 };
333
+ return {
334
+ file: uriToWorkspaceFile(workspaceRoot, uri),
335
+ line: Number(start.line ?? 0),
336
+ character: Number(start.character ?? 0),
337
+ };
338
+ }
339
+ function normalizeSymbol(workspaceRoot, item) {
340
+ if (!item || typeof item !== "object")
341
+ return undefined;
342
+ const obj = item;
343
+ if (typeof obj.name !== "string" || typeof obj.location?.uri !== "string")
344
+ return undefined;
345
+ const start = obj.location.range?.start ?? { line: 0, character: 0 };
346
+ return {
347
+ name: obj.name,
348
+ kind: typeof obj.kind === "number" ? obj.kind : 0,
349
+ file: uriToWorkspaceFile(workspaceRoot, obj.location.uri),
350
+ line: Number(start.line ?? 0),
351
+ character: Number(start.character ?? 0),
352
+ containerName: typeof obj.containerName === "string" ? obj.containerName : undefined,
353
+ };
354
+ }
355
+ function normalizeDiagnostics(workspaceRoot, uri, raw, maxPerFile) {
356
+ let file;
357
+ try {
358
+ file = uriToWorkspaceFile(workspaceRoot, uri);
359
+ }
360
+ catch {
361
+ return [];
362
+ }
363
+ const list = Array.isArray(raw) ? raw : [];
364
+ const out = [];
365
+ for (const d of list) {
366
+ if (!d || typeof d !== "object")
367
+ continue;
368
+ const diag = d;
369
+ if (typeof diag.message !== "string")
370
+ continue;
371
+ const start = diag.range?.start ?? { line: 0, character: 0 };
372
+ const end = diag.range?.end ?? start;
373
+ out.push({
374
+ file,
375
+ line: Number(start.line ?? 0),
376
+ character: Number(start.character ?? 0),
377
+ endLine: Number(end.line ?? 0),
378
+ endCharacter: Number(end.character ?? 0),
379
+ severity: severityName(diag.severity),
380
+ message: diag.message,
381
+ source: typeof diag.source === "string" ? diag.source : undefined,
382
+ code: typeof diag.code === "string" || typeof diag.code === "number" ? diag.code : undefined,
383
+ });
384
+ if (out.length >= maxPerFile)
385
+ break;
386
+ }
387
+ return out;
388
+ }
389
+ function severityName(severity) {
390
+ switch (severity) {
391
+ case 1:
392
+ return "error";
393
+ case 2:
394
+ return "warning";
395
+ case 3:
396
+ return "info";
397
+ case 4:
398
+ return "hint";
399
+ default:
400
+ return "error";
401
+ }
402
+ }
403
+ function hoverToText(contents) {
404
+ if (contents == null)
405
+ return undefined;
406
+ if (typeof contents === "string")
407
+ return contents;
408
+ if (Array.isArray(contents)) {
409
+ return (contents
410
+ .map((c) => hoverToText(c) ?? "")
411
+ .filter(Boolean)
412
+ .join("\n") || undefined);
413
+ }
414
+ if (typeof contents === "object") {
415
+ const o = contents;
416
+ if (typeof o.value === "string")
417
+ return o.value;
418
+ }
419
+ return undefined;
420
+ }
421
+ function normalizeWorkspaceEdit(workspaceRoot, result, maxEdits) {
422
+ if (!result || typeof result !== "object") {
423
+ return { edits: [] };
424
+ }
425
+ const edits = [];
426
+ const obj = result;
427
+ if (obj.changes) {
428
+ for (const [uri, changeList] of Object.entries(obj.changes)) {
429
+ const file = uriToWorkspaceFile(workspaceRoot, uri);
430
+ for (const c of changeList ?? []) {
431
+ if (!c?.range || typeof c.newText !== "string")
432
+ continue;
433
+ edits.push({ file, range: c.range, newText: c.newText });
434
+ if (edits.length >= maxEdits)
435
+ return { edits };
436
+ }
437
+ }
438
+ }
439
+ if (Array.isArray(obj.documentChanges)) {
440
+ for (const dc of obj.documentChanges) {
441
+ if (!dc || typeof dc !== "object")
442
+ continue;
443
+ const doc = dc;
444
+ if (typeof doc.textDocument?.uri !== "string" || !Array.isArray(doc.edits))
445
+ continue;
446
+ const file = uriToWorkspaceFile(workspaceRoot, doc.textDocument.uri);
447
+ for (const c of doc.edits) {
448
+ if (!c?.range || typeof c.newText !== "string")
449
+ continue;
450
+ edits.push({ file, range: c.range, newText: c.newText });
451
+ if (edits.length >= maxEdits)
452
+ return { edits };
453
+ }
454
+ }
455
+ }
456
+ return { edits };
457
+ }
458
+ async function applyWorkspaceEdit(workspaceRoot, edit, policy, signal) {
459
+ if (edit.edits.length === 0)
460
+ return;
461
+ const paths = [...new Set(edit.edits.map((e) => resolve(workspaceRoot, e.file)))];
462
+ try {
463
+ await assertExecutionAllowed(policy, {
464
+ kind: "edit",
465
+ operation: "rename",
466
+ paths,
467
+ risk: "high",
468
+ metadata: { editCount: edit.edits.length, signal },
469
+ });
470
+ }
471
+ catch (error) {
472
+ if (error instanceof ExecutionDeniedError) {
473
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_UNSUPPORTED", error.message);
474
+ }
475
+ throw error;
476
+ }
477
+ const byFile = new Map();
478
+ for (const e of edit.edits) {
479
+ const list = byFile.get(e.file) ?? [];
480
+ list.push(e);
481
+ byFile.set(e.file, list);
482
+ }
483
+ for (const [file, fileEdits] of byFile) {
484
+ const abs = await resolveContainedMutationPath(workspaceRoot, file);
485
+ await withFileMutationQueue(abs, async () => {
486
+ if (signal?.aborted) {
487
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_TIMEOUT", "Rename aborted");
488
+ }
489
+ const original = await readFile(abs, "utf8");
490
+ const next = applyTextEdits(original, fileEdits);
491
+ await atomicWriteUtf8File(abs, next, { signal });
492
+ });
493
+ }
494
+ }
495
+ /** Apply LSP text edits (0-based line/character) from end to start. */
496
+ export function applyTextEdits(content, edits) {
497
+ const sorted = [...edits].sort((a, b) => {
498
+ if (a.range.start.line !== b.range.start.line)
499
+ return b.range.start.line - a.range.start.line;
500
+ return b.range.start.character - a.range.start.character;
501
+ });
502
+ let text = content;
503
+ for (const e of sorted) {
504
+ const start = offsetAt(text, e.range.start.line, e.range.start.character);
505
+ const end = offsetAt(text, e.range.end.line, e.range.end.character);
506
+ text = text.slice(0, start) + e.newText + text.slice(end);
507
+ }
508
+ return text;
509
+ }
510
+ function offsetAt(text, line, character) {
511
+ let lineNo = 0;
512
+ let i = 0;
513
+ while (i < text.length && lineNo < line) {
514
+ if (text[i] === "\n")
515
+ lineNo += 1;
516
+ i += 1;
517
+ }
518
+ return Math.min(i + character, text.length);
519
+ }
520
+ function mapError(error) {
521
+ if (error instanceof LanguageIntelligenceError)
522
+ throw error;
523
+ throw new LanguageIntelligenceError("ERR_PRISM_LSP_SERVER", error instanceof Error ? error.message : String(error));
524
+ }
525
+ export { LanguageIntelligenceError, resolveLanguageIntelligenceLimits } from "./types.js";
526
+ //# sourceMappingURL=intelligence.js.map
@@ -0,0 +1,106 @@
1
+ import type { ExecutionPolicy } from "@arnilo/prism";
2
+ export type LanguageIntelligenceErrorCode = "ERR_PRISM_LSP_FRAMING" | "ERR_PRISM_LSP_SERVER" | "ERR_PRISM_LSP_TIMEOUT" | "ERR_PRISM_LSP_LIMIT" | "ERR_PRISM_LSP_UNSUPPORTED" | "ERR_PRISM_LSP_WORKSPACE";
3
+ export declare class LanguageIntelligenceError extends Error {
4
+ readonly code: LanguageIntelligenceErrorCode;
5
+ constructor(code: LanguageIntelligenceErrorCode, message: string);
6
+ }
7
+ /** Host allow-listed server command; never model-supplied. */
8
+ export interface LanguageServerSpec {
9
+ readonly command: string;
10
+ readonly args?: readonly string[];
11
+ readonly languages: readonly string[];
12
+ readonly env?: Readonly<Record<string, string>>;
13
+ }
14
+ /** LSP 0-based position + workspace-relative file path. */
15
+ export interface LanguageLocation {
16
+ readonly file: string;
17
+ readonly line: number;
18
+ readonly character: number;
19
+ }
20
+ export interface LanguageSymbol {
21
+ readonly name: string;
22
+ readonly kind: number;
23
+ readonly file: string;
24
+ readonly line: number;
25
+ readonly character: number;
26
+ readonly containerName?: string;
27
+ }
28
+ export interface LanguageDiagnostic {
29
+ readonly file: string;
30
+ readonly line: number;
31
+ readonly character: number;
32
+ readonly endLine: number;
33
+ readonly endCharacter: number;
34
+ readonly severity: "error" | "warning" | "info" | "hint";
35
+ readonly message: string;
36
+ readonly source?: string;
37
+ readonly code?: string | number;
38
+ }
39
+ export interface LanguageTextEdit {
40
+ readonly file: string;
41
+ readonly newText: string;
42
+ readonly range: {
43
+ readonly start: {
44
+ readonly line: number;
45
+ readonly character: number;
46
+ };
47
+ readonly end: {
48
+ readonly line: number;
49
+ readonly character: number;
50
+ };
51
+ };
52
+ }
53
+ export interface LanguageWorkspaceEdit {
54
+ readonly edits: readonly LanguageTextEdit[];
55
+ }
56
+ export interface LanguageIntelligenceLimits {
57
+ readonly maxMessageBytes?: number;
58
+ readonly maxDiagnosticsPerFile?: number;
59
+ readonly maxPendingRequests?: number;
60
+ readonly maxResultsPerQuery?: number;
61
+ readonly requestTimeoutMs?: number;
62
+ readonly maxServers?: number;
63
+ /** Cap restarts after unexpected exit (freeze: 3). */
64
+ readonly maxRestartsPerServer?: number;
65
+ }
66
+ export interface ResolvedLanguageIntelligenceLimits {
67
+ readonly maxMessageBytes: number;
68
+ readonly maxDiagnosticsPerFile: number;
69
+ readonly maxPendingRequests: number;
70
+ readonly maxResultsPerQuery: number;
71
+ readonly requestTimeoutMs: number;
72
+ readonly maxServers: number;
73
+ readonly maxRestartsPerServer: number;
74
+ }
75
+ export declare function resolveLanguageIntelligenceLimits(options?: LanguageIntelligenceLimits): ResolvedLanguageIntelligenceLimits;
76
+ export interface LanguageIntelligence {
77
+ workspaceSymbols(query: string, opts?: {
78
+ signal?: AbortSignal;
79
+ }): Promise<readonly LanguageSymbol[]>;
80
+ definitions(loc: LanguageLocation, opts?: {
81
+ signal?: AbortSignal;
82
+ }): Promise<readonly LanguageLocation[]>;
83
+ references(loc: LanguageLocation, opts?: {
84
+ signal?: AbortSignal;
85
+ }): Promise<readonly LanguageLocation[]>;
86
+ diagnostics(file?: string, opts?: {
87
+ signal?: AbortSignal;
88
+ }): Promise<readonly LanguageDiagnostic[]>;
89
+ hover(loc: LanguageLocation, opts?: {
90
+ signal?: AbortSignal;
91
+ }): Promise<{
92
+ text: string;
93
+ } | undefined>;
94
+ rename(loc: LanguageLocation & {
95
+ newName: string;
96
+ }, opts?: {
97
+ signal?: AbortSignal;
98
+ }): Promise<LanguageWorkspaceEdit>;
99
+ dispose(): Promise<void>;
100
+ }
101
+ export interface CreateLanguageIntelligenceOptions {
102
+ readonly workspaceRoot: string;
103
+ readonly servers: Readonly<Record<string, LanguageServerSpec>>;
104
+ readonly limits?: LanguageIntelligenceLimits;
105
+ readonly policy?: ExecutionPolicy;
106
+ }
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_MAX_LSP_DIAGNOSTICS_PER_FILE, DEFAULT_MAX_LSP_MESSAGE_BYTES, DEFAULT_MAX_LSP_PENDING_REQUESTS, DEFAULT_MAX_LSP_RESULTS_PER_QUERY, DEFAULT_MAX_LSP_SERVERS, DEFAULT_MAX_LSP_TIMEOUT_MS, HARD_MAX_LSP_DIAGNOSTICS_PER_FILE, HARD_MAX_LSP_MESSAGE_BYTES, HARD_MAX_LSP_PENDING_REQUESTS, HARD_MAX_LSP_RESULTS_PER_QUERY, HARD_MAX_LSP_SERVERS, HARD_MAX_LSP_TIMEOUT_MS, LSP_RESTARTS_PER_SERVER, validateCodingLimit, } from "../limits.js";
2
+ export class LanguageIntelligenceError extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(message);
6
+ this.name = "LanguageIntelligenceError";
7
+ this.code = code;
8
+ }
9
+ }
10
+ export function resolveLanguageIntelligenceLimits(options) {
11
+ return {
12
+ maxMessageBytes: validateCodingLimit("maxMessageBytes", options?.maxMessageBytes ?? DEFAULT_MAX_LSP_MESSAGE_BYTES, HARD_MAX_LSP_MESSAGE_BYTES),
13
+ maxDiagnosticsPerFile: validateCodingLimit("maxDiagnosticsPerFile", options?.maxDiagnosticsPerFile ?? DEFAULT_MAX_LSP_DIAGNOSTICS_PER_FILE, HARD_MAX_LSP_DIAGNOSTICS_PER_FILE),
14
+ maxPendingRequests: validateCodingLimit("maxPendingRequests", options?.maxPendingRequests ?? DEFAULT_MAX_LSP_PENDING_REQUESTS, HARD_MAX_LSP_PENDING_REQUESTS),
15
+ maxResultsPerQuery: validateCodingLimit("maxResultsPerQuery", options?.maxResultsPerQuery ?? DEFAULT_MAX_LSP_RESULTS_PER_QUERY, HARD_MAX_LSP_RESULTS_PER_QUERY),
16
+ requestTimeoutMs: validateCodingLimit("requestTimeoutMs", options?.requestTimeoutMs ?? DEFAULT_MAX_LSP_TIMEOUT_MS, HARD_MAX_LSP_TIMEOUT_MS),
17
+ maxServers: validateCodingLimit("maxServers", options?.maxServers ?? DEFAULT_MAX_LSP_SERVERS, HARD_MAX_LSP_SERVERS),
18
+ maxRestartsPerServer: validateCodingLimit("maxRestartsPerServer", options?.maxRestartsPerServer ?? LSP_RESTARTS_PER_SERVER, LSP_RESTARTS_PER_SERVER),
19
+ };
20
+ }
21
+ //# sourceMappingURL=types.js.map