@askdkc/dsh-lsp-server 0.1.11 → 0.1.13

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 (36) hide show
  1. package/README.md +37 -0
  2. package/docs/usage.md +2 -0
  3. package/lib/code-intelligence-DvhxPnpu.js +890 -0
  4. package/lib/code-intelligence-DvhxPnpu.js.map +1 -0
  5. package/lib/code-intelligence-contracts-CWXTofdI.js +8 -0
  6. package/lib/code-intelligence-contracts-CWXTofdI.js.map +1 -0
  7. package/lib/code-intelligence-contracts.js +3 -0
  8. package/lib/code-intelligence.js +5 -0
  9. package/lib/errors-DtyWAUi0.js.map +1 -1
  10. package/lib/extra-tool.js +136 -8
  11. package/lib/extra-tool.js.map +1 -1
  12. package/lib/launch-ByUvgkVv.js.map +1 -1
  13. package/lib/provider.js +653 -40
  14. package/lib/provider.js.map +1 -1
  15. package/lib/structure/grammars/manifest.json +40 -0
  16. package/lib/structure/grammars/tree-sitter-javascript.wasm +0 -0
  17. package/lib/structure/grammars/tree-sitter-tsx.wasm +0 -0
  18. package/lib/structure/grammars/tree-sitter-typescript.wasm +0 -0
  19. package/lib/structure/parser-worker.mjs +200 -0
  20. package/lib/types/code-intelligence-contracts.d.ts +113 -0
  21. package/lib/types/code-intelligence.d.ts +5 -0
  22. package/lib/types/errors.d.ts +1 -1
  23. package/lib/types/extra-tool.d.ts +2 -1
  24. package/lib/types/provider.d.ts +4 -0
  25. package/lib/types/routing/classifier.d.ts +2 -0
  26. package/lib/types/runtime/webstack-runtime.d.ts +13 -1
  27. package/lib/types/service.d.ts +23 -0
  28. package/lib/types/structure/code-intelligence.d.ts +23 -0
  29. package/lib/types/structure/coordinates.d.ts +14 -0
  30. package/lib/types/structure/engine.d.ts +49 -0
  31. package/lib/types/structure/protocol.d.ts +19 -0
  32. package/lib/types/structure/queries.d.ts +4 -0
  33. package/lib/types/structure/snapshots.d.ts +37 -0
  34. package/lib/types/structure/types.d.ts +147 -0
  35. package/lib/types/structure/worker-pool.d.ts +66 -0
  36. package/package.json +17 -4
package/lib/provider.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { WebstackLspError } from "./errors-DtyWAUi0.js";
2
2
  import { SERVER_IDS, createWebstackLspService, initializationOptions, resolveConfig, resolveLaunch, serverConfiguration } from "./launch-ByUvgkVv.js";
3
3
  import { mergeCompletions, mergeDiagnostics } from "./completion-B7NPVCEN.js";
4
+ import { DocumentSnapshotStore, createCodeIntelligenceToolServiceV1 } from "./code-intelligence-DvhxPnpu.js";
4
5
  import { LspProviderId } from "@deepseek-ai/dsh-lsp";
6
+ import { isAbsolute } from "node:path";
5
7
  import { TextDecoder } from "node:util";
8
+ import { randomUUID } from "node:crypto";
9
+ import { symbols } from "@deepseek-ai/cordis";
6
10
 
7
11
  //#region src/runtime/cancellation.ts
8
12
  function abortError() {
@@ -25,11 +29,11 @@ function onAbort(signal, callback) {
25
29
  async function withAbort(promise, signal) {
26
30
  throwIfAborted(signal);
27
31
  if (!signal) return promise;
28
- return new Promise((resolve, reject) => {
32
+ return new Promise((resolve$1, reject) => {
29
33
  const remove = onAbort(signal, () => reject(abortError()));
30
34
  promise.then((value) => {
31
35
  remove();
32
- resolve(value);
36
+ resolve$1(value);
33
37
  }, (error) => {
34
38
  remove();
35
39
  reject(error);
@@ -143,21 +147,25 @@ const CLASSIFIERS = [
143
147
  ];
144
148
  function classifyFile(filePath, bladeSuffixes = [".blade.php"]) {
145
149
  const normalizedPath = filePath.replaceAll("\\", "/");
146
- const basename = normalizedPath.slice(normalizedPath.lastIndexOf("/") + 1).toLowerCase();
147
- if (!basename || basename === "." || basename === ".." || basename.startsWith(".")) return void 0;
150
+ const basename$1 = normalizedPath.slice(normalizedPath.lastIndexOf("/") + 1).toLowerCase();
151
+ if (!basename$1 || basename$1 === "." || basename$1 === ".." || basename$1.startsWith(".")) return void 0;
148
152
  const customBlade = bladeSuffixes.map((suffix) => suffix.toLowerCase()).sort((left, right) => right.length - left.length);
149
153
  const match = [...customBlade.map((suffix) => ({
150
154
  suffix,
151
155
  kind: "blade"
152
- })), ...CLASSIFIERS.filter((item) => !customBlade.includes(item.suffix))].find((item) => basename.endsWith(item.suffix));
156
+ })), ...CLASSIFIERS.filter((item) => !customBlade.includes(item.suffix))].find((item) => basename$1.endsWith(item.suffix));
153
157
  if (!match) return void 0;
154
158
  return {
155
159
  path: normalizedPath,
156
- basename,
160
+ basename: basename$1,
157
161
  suffix: match.suffix,
158
162
  kind: match.kind
159
163
  };
160
164
  }
165
+ /** Return the supported suffixes, including configured Blade suffixes. */
166
+ function supportedFileSuffixes(bladeSuffixes = [".blade.php"]) {
167
+ return [...new Set([...bladeSuffixes.map((suffix) => suffix.toLowerCase()), ...CLASSIFIERS.map((item) => item.suffix)])];
168
+ }
161
169
 
162
170
  //#endregion
163
171
  //#region src/routing/route-table.ts
@@ -397,8 +405,8 @@ var JsonRpcConnection = class {
397
405
  throwIfAborted(signal);
398
406
  if (this.closed) throw new Error("connection is closed");
399
407
  const id = this.nextId++;
400
- const promise = new Promise((resolve, reject) => this.pending.set(id, {
401
- resolve,
408
+ const promise = new Promise((resolve$1, reject) => this.pending.set(id, {
409
+ resolve: resolve$1,
402
410
  reject,
403
411
  method
404
412
  }));
@@ -447,7 +455,7 @@ var JsonRpcConnection = class {
447
455
  for (const message of messages) this.dispatch(message);
448
456
  }
449
457
  dispatch(value) {
450
- if (!isRecord$4(value) || value.jsonrpc !== "2.0") return this.close(new JsonRpcFrameError("invalid JSON-RPC message"));
458
+ if (!isRecord$5(value) || value.jsonrpc !== "2.0") return this.close(new JsonRpcFrameError("invalid JSON-RPC message"));
451
459
  if ("method" in value && typeof value.method === "string") {
452
460
  if ("id" in value) {
453
461
  const request = value;
@@ -476,12 +484,12 @@ var JsonRpcConnection = class {
476
484
  }
477
485
  }
478
486
  write(message) {
479
- return new Promise((resolve, reject) => {
480
- this.output.write(encodeJsonRpc(message), (error) => error ? reject(error) : resolve());
487
+ return new Promise((resolve$1, reject) => {
488
+ this.output.write(encodeJsonRpc(message), (error) => error ? reject(error) : resolve$1());
481
489
  });
482
490
  }
483
491
  };
484
- function isRecord$4(value) {
492
+ function isRecord$5(value) {
485
493
  return typeof value === "object" && value !== null && !Array.isArray(value);
486
494
  }
487
495
 
@@ -646,8 +654,8 @@ var ServerPool = class {
646
654
  async serialize(key, task, signal) {
647
655
  const previous = this.queues.get(key) ?? Promise.resolve();
648
656
  let release;
649
- const current = new Promise((resolve) => {
650
- release = resolve;
657
+ const current = new Promise((resolve$1) => {
658
+ release = resolve$1;
651
659
  });
652
660
  this.queues.set(key, current);
653
661
  try {
@@ -714,29 +722,29 @@ function normalizeHover(value) {
714
722
  function normalizeContents(value) {
715
723
  if (typeof value === "string") return value;
716
724
  if (Array.isArray(value)) {
717
- const parts = value.map((item) => typeof item === "string" ? item : isRecord$3(item) && typeof item.value === "string" ? item.value : void 0);
725
+ const parts = value.map((item) => typeof item === "string" ? item : isRecord$4(item) && typeof item.value === "string" ? item.value : void 0);
718
726
  return parts.every((item) => item !== void 0) ? parts.join("\n") : void 0;
719
727
  }
720
- if (isRecord$3(value) && typeof value.value === "string") return value.value;
728
+ if (isRecord$4(value) && typeof value.value === "string") return value.value;
721
729
  }
722
730
  function isRange$2(value) {
723
- return isRecord$3(value) && isPosition$2(value.start) && isPosition$2(value.end);
731
+ return isRecord$4(value) && isPosition$2(value.start) && isPosition$2(value.end);
724
732
  }
725
733
  function isPosition$2(value) {
726
- return isRecord$3(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
734
+ return isRecord$4(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
727
735
  }
728
- function isRecord$3(value) {
736
+ function isRecord$4(value) {
729
737
  return typeof value === "object" && value !== null && !Array.isArray(value);
730
738
  }
731
739
 
732
740
  //#endregion
733
741
  //#region src/protocol/normalize-location.ts
734
742
  function normalizeLocation(value) {
735
- if (isRecord$2(value) && typeof value.targetUri === "string" && isRange$1(value.targetSelectionRange)) return {
743
+ if (isRecord$3(value) && typeof value.targetUri === "string" && isRange$1(value.targetSelectionRange)) return {
736
744
  uri: value.targetUri,
737
745
  range: value.targetSelectionRange
738
746
  };
739
- if (!isRecord$2(value) || typeof value.uri !== "string" || !isRange$1(value.range)) return void 0;
747
+ if (!isRecord$3(value) || typeof value.uri !== "string" || !isRange$1(value.range)) return void 0;
740
748
  return {
741
749
  uri: value.uri,
742
750
  range: value.range
@@ -746,19 +754,19 @@ function normalizeLocations(value) {
746
754
  return (Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value]).map(normalizeLocation).filter((location) => location !== void 0);
747
755
  }
748
756
  function isRange$1(value) {
749
- return isRecord$2(value) && isPosition$1(value.start) && isPosition$1(value.end);
757
+ return isRecord$3(value) && isPosition$1(value.start) && isPosition$1(value.end);
750
758
  }
751
759
  function isPosition$1(value) {
752
- return isRecord$2(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
760
+ return isRecord$3(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
753
761
  }
754
- function isRecord$2(value) {
762
+ function isRecord$3(value) {
755
763
  return typeof value === "object" && value !== null && !Array.isArray(value);
756
764
  }
757
765
 
758
766
  //#endregion
759
767
  //#region src/protocol/normalize-diagnostic.ts
760
768
  function normalizeDiagnostic(value, server, fallbackUri) {
761
- if (!isRecord$1(value) || !isRange(value.range) || typeof value.message !== "string") return void 0;
769
+ if (!isRecord$2(value) || !isRange(value.range) || typeof value.message !== "string") return void 0;
762
770
  const uri = typeof value.uri === "string" ? value.uri : fallbackUri;
763
771
  const severity = typeof value.severity === "number" && value.severity >= 1 && value.severity <= 4 ? value.severity : void 0;
764
772
  const code = typeof value.code === "string" || typeof value.code === "number" ? value.code : void 0;
@@ -776,19 +784,19 @@ function normalizeDiagnostics(value, server, fallbackUri) {
776
784
  return (Array.isArray(value) ? value : []).map((item) => normalizeDiagnostic(item, server, fallbackUri)).filter((item) => item !== void 0);
777
785
  }
778
786
  function isRange(value) {
779
- return isRecord$1(value) && isPosition(value.start) && isPosition(value.end);
787
+ return isRecord$2(value) && isPosition(value.start) && isPosition(value.end);
780
788
  }
781
789
  function isPosition(value) {
782
- return isRecord$1(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
790
+ return isRecord$2(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
783
791
  }
784
- function isRecord$1(value) {
792
+ function isRecord$2(value) {
785
793
  return typeof value === "object" && value !== null && !Array.isArray(value);
786
794
  }
787
795
 
788
796
  //#endregion
789
797
  //#region src/protocol/normalize-completion.ts
790
798
  function normalizeCompletionItem(value, server) {
791
- if (!isRecord(value) || typeof value.label !== "string" || value.label.length === 0) return void 0;
799
+ if (!isRecord$1(value) || typeof value.label !== "string" || value.label.length === 0) return void 0;
792
800
  return {
793
801
  server,
794
802
  label: value.label,
@@ -803,9 +811,9 @@ function normalizeCompletionItem(value, server) {
803
811
  };
804
812
  }
805
813
  function normalizeCompletion(value, server) {
806
- return (isRecord(value) && Array.isArray(value.items) ? value.items : Array.isArray(value) ? value : []).map((item) => normalizeCompletionItem(item, server)).filter((item) => item !== void 0);
814
+ return (isRecord$1(value) && Array.isArray(value.items) ? value.items : Array.isArray(value) ? value : []).map((item) => normalizeCompletionItem(item, server)).filter((item) => item !== void 0);
807
815
  }
808
- function isRecord(value) {
816
+ function isRecord$1(value) {
809
817
  return typeof value === "object" && value !== null && !Array.isArray(value);
810
818
  }
811
819
 
@@ -827,6 +835,451 @@ function sameRange(left, right) {
827
835
  return JSON.stringify(left) === JSON.stringify(right);
828
836
  }
829
837
 
838
+ //#endregion
839
+ //#region src/structure/protocol.ts
840
+ const CAPABILITIES = [
841
+ "snapshots",
842
+ "structure",
843
+ "semantic"
844
+ ];
845
+ const MAX_FILES = 100;
846
+ const MAX_INPUT_BYTES = 16 * 1024 * 1024;
847
+ const MAX_OUTPUT_BYTES = 64 * 1024;
848
+ const MAX_CALLS = 100;
849
+ const MAX_BATCH_MS = 3e4;
850
+ const RESPONSE_BYTES = 32 * 1024;
851
+ /** Host-bound V1 facade. Each lease owns its handles and cumulative evaluation budget. */
852
+ var CodeIntelligenceProtocolService = class {
853
+ version = 1;
854
+ capabilities = CAPABILITIES;
855
+ leases = /* @__PURE__ */ new Set();
856
+ closed = false;
857
+ constructor(options) {
858
+ this.options = options;
859
+ }
860
+ async bind(binding, signal) {
861
+ if (this.closed) throw new WebstackLspError("LSP_DISPOSED", "code-intelligence service is disposed");
862
+ validateBinding(binding);
863
+ signal.throwIfAborted();
864
+ await binding.assertCurrent();
865
+ const contextFs = binding.context.get("fs", false);
866
+ const contextSubprocess = binding.context.get("subprocess", false);
867
+ if (identity(contextFs) !== identity(this.options.fs) || identity(contextSubprocess) !== identity(this.options.subprocess)) throw new WebstackLspError("LSP_CODE_INTELLIGENCE_UNAVAILABLE", "host services do not match the provider scope");
868
+ const scope = {
869
+ ownerId: `${binding.owner.agentId}:code:${randomUUID()}`,
870
+ sessionId: binding.owner.sessionId,
871
+ workspaceRoot: binding.workspaceRoot
872
+ };
873
+ const lease = new CodeLease(binding, scope, this.options.backend, this.options.capabilities, identity(this.options.fs), identity(this.options.subprocess), () => this.leases.delete(lease));
874
+ this.leases.add(lease);
875
+ try {
876
+ await lease.assertCurrent(signal);
877
+ if (this.closed) throw new WebstackLspError("LSP_DISPOSED", "code-intelligence service is disposed");
878
+ return lease;
879
+ } catch (error) {
880
+ await lease.dispose();
881
+ throw error;
882
+ }
883
+ }
884
+ async dispose() {
885
+ if (this.closed) return;
886
+ this.closed = true;
887
+ await Promise.all([...this.leases].map((lease) => lease.dispose()));
888
+ }
889
+ };
890
+ var CodeLease = class {
891
+ lifetime = new AbortController();
892
+ snapshots = /* @__PURE__ */ new Map();
893
+ nodes = /* @__PURE__ */ new Map();
894
+ calls = 0;
895
+ files = 0;
896
+ inputBytes = 0;
897
+ outputBytes = 0;
898
+ started = performance.now();
899
+ disposed = false;
900
+ disposal;
901
+ constructor(binding, scope, backend, capabilities, fsIdentity, subprocessIdentity, onDispose) {
902
+ this.binding = binding;
903
+ this.scope = scope;
904
+ this.backend = backend;
905
+ this.capabilities = capabilities;
906
+ this.fsIdentity = fsIdentity;
907
+ this.subprocessIdentity = subprocessIdentity;
908
+ this.onDispose = onDispose;
909
+ }
910
+ async assertCurrent(signal) {
911
+ signal.throwIfAborted();
912
+ if (this.disposed) throw new WebstackLspError("LSP_STALE_SNAPSHOT", "code evaluation has ended");
913
+ await this.binding.assertCurrent();
914
+ if (identity(this.binding.context.get("fs", false)) !== this.fsIdentity || identity(this.binding.context.get("subprocess", false)) !== this.subprocessIdentity) throw new WebstackLspError("LSP_STALE_SNAPSHOT", "host service scope changed");
915
+ signal.throwIfAborted();
916
+ }
917
+ async request(input, caller) {
918
+ const release = input?.method === "snapshot.release";
919
+ if (!release) this.calls++;
920
+ if (this.disposed) return this.account(outcome("stale", "evaluation_closed"));
921
+ if (!validCodeRequest(input)) return this.account(outcome("unsupported", "invalid_request"));
922
+ if (caller.aborted) return this.account(outcome(caller.reason?.name === "TimeoutError" ? "timeout" : "cancelled", caller.reason?.name === "TimeoutError" ? "caller_deadline" : "caller_cancelled"));
923
+ if (release) return this.release(input.handle);
924
+ if (this.calls > MAX_CALLS || this.outputBytes >= MAX_OUTPUT_BYTES || this.files >= MAX_FILES && input.method === "snapshot.open" || this.inputBytes >= MAX_INPUT_BYTES && input.method === "snapshot.open") return this.account(outcome("limit_exceeded", "evaluation_budget"));
925
+ const remaining = MAX_BATCH_MS - (performance.now() - this.started);
926
+ if (remaining <= 0) return this.account(outcome("timeout", "batch_deadline"));
927
+ if ("handle" in input && !this.snapshots.has(input.handle) || "nodeHandle" in input && !this.nodes.has(input.nodeHandle)) return this.account(outcome("stale", "unknown_handle"));
928
+ const duration = input.method === "semantic.query" ? 1e4 : 2e3;
929
+ const timeout = AbortSignal.timeout(Math.max(1, Math.floor(Math.min(remaining, duration))));
930
+ const signal = AbortSignal.any([
931
+ caller,
932
+ this.lifetime.signal,
933
+ timeout
934
+ ]);
935
+ try {
936
+ await this.assertCurrent(signal);
937
+ let response;
938
+ if (input.method === "capabilities") response = this.capabilitiesResponse(await this.capabilities(signal));
939
+ else if (input.method === "snapshot.open") response = await this.open(input, signal);
940
+ else response = await this.query(input, signal);
941
+ await this.assertCurrent(signal);
942
+ return this.account(boundResponse(response));
943
+ } catch (error) {
944
+ if (signal.aborted) return this.account(outcome(this.lifetime.signal.aborted || caller.aborted && caller.reason?.name !== "TimeoutError" ? "cancelled" : "timeout", caller.aborted ? caller.reason?.name === "TimeoutError" ? "caller_deadline" : "caller_cancelled" : "operation_deadline"));
945
+ if (error instanceof WebstackLspError) return this.account(mapError(error));
946
+ return this.account(outcome("unavailable", "provider_operation_failed"));
947
+ }
948
+ }
949
+ async open(input, signal) {
950
+ this.files++;
951
+ const opened = await this.backend.snapshot.open(this.scope, input.path, signal);
952
+ const version = `${opened.version}:${opened.contentHash}`;
953
+ const response = {
954
+ protocol: "code-intelligence/v1",
955
+ status: "ok",
956
+ sourceKind: "disk",
957
+ freshness: "pinned",
958
+ snapshotVersion: version,
959
+ truncated: false,
960
+ omitted: 0,
961
+ inputBytes: opened.sourceBytes,
962
+ data: {
963
+ handle: opened.handle,
964
+ path: opened.filePath,
965
+ language: opened.language ?? "unsupported",
966
+ parseErrors: opened.parseErrors
967
+ }
968
+ };
969
+ if (this.inputBytes + opened.sourceBytes > MAX_INPUT_BYTES) {
970
+ await this.backend.snapshot.release(this.scope, opened.handle).catch(() => {});
971
+ return outcome("limit_exceeded", "input_budget");
972
+ }
973
+ this.inputBytes += opened.sourceBytes;
974
+ this.snapshots.set(opened.handle, {
975
+ version,
976
+ path: opened.filePath
977
+ });
978
+ return response;
979
+ }
980
+ async query(input, signal) {
981
+ const handle = "handle" in input ? input.handle : this.nodes.get(input.nodeHandle);
982
+ const snapshot = this.snapshots.get(handle);
983
+ if (!snapshot) return outcome("stale", "unknown_handle");
984
+ const metadata = {
985
+ snapshotVersion: snapshot.version,
986
+ freshness: "current"
987
+ };
988
+ if (input.method === "structure.outline") {
989
+ const result = await this.backend.structure.outline(this.scope, input.handle, {
990
+ ...input.range ? { range: input.range } : {},
991
+ ...input.limit ? { limit: input.limit } : {}
992
+ }, signal);
993
+ const items = result.declarations.map(projectNode);
994
+ this.rememberNodes(handle, items);
995
+ const status = result.truncated && result.status === "ok" ? "partial" : result.status;
996
+ return responseFor(status, result.status === "unsupported" ? "grammar_missing" : result.errors.hasError ? "parse_errors" : void 0, metadata, { items }, result.truncated, result.truncated ? 1 : 0);
997
+ }
998
+ if (input.method === "structure.enclosing") {
999
+ const result = await this.backend.structure.enclosing(this.scope, input.handle, input.position, input.kinds ? { kinds: input.kinds } : void 0, signal);
1000
+ const items = [...result.node ? [result.node] : [], ...result.ancestors].map(projectNode);
1001
+ this.rememberNodes(handle, items);
1002
+ return responseFor(result.status, result.status === "unsupported" ? "grammar_missing" : result.errors?.hasError ? "parse_errors" : void 0, metadata, { items });
1003
+ }
1004
+ if (input.method === "structure.query") {
1005
+ const result = await this.backend.structure.query(this.scope, input.handle, input.queryId, {
1006
+ ...input.range ? { range: input.range } : {},
1007
+ ...input.limit ? { limit: input.limit } : {}
1008
+ }, signal);
1009
+ const items = result.captures.map((capture) => ({
1010
+ handle: capture.nodeHandle,
1011
+ kind: capture.kind,
1012
+ range: capture.range,
1013
+ ...capture.name ? { name: capture.name } : {}
1014
+ }));
1015
+ this.rememberNodes(handle, items);
1016
+ return responseFor(result.truncated && result.status === "ok" ? "partial" : result.status, void 0, metadata, {
1017
+ items,
1018
+ queryId: result.queryId
1019
+ }, result.truncated, result.truncated ? 1 : 0);
1020
+ }
1021
+ if (input.method === "structure.span") {
1022
+ const result = await this.backend.structure.span(this.scope, input.nodeHandle, input.maxChars, signal);
1023
+ return responseFor(result.status, void 0, metadata, {
1024
+ text: result.text,
1025
+ range: result.range
1026
+ }, result.truncated, result.truncated ? 1 : 0);
1027
+ }
1028
+ return this.semantic(input, metadata, signal);
1029
+ }
1030
+ async semantic(input, metadata, signal) {
1031
+ const result = await this.backend.semantic.query(this.scope, input.handle, {
1032
+ operation: input.kind,
1033
+ ...input.position ? { position: input.position } : {},
1034
+ ...input.limit ? { maxResults: input.limit } : {}
1035
+ }, signal);
1036
+ if (result.status === "unsupported" || result.status === "unavailable" || result.status === "stale" || result.status === "timeout" || result.status === "cancelled" || result.status === "limit_exceeded") return responseFor(result.status, `semantic_${result.status}`, metadata);
1037
+ const value = result.result;
1038
+ if (input.kind === "hover") {
1039
+ if (!isRecord(value) || value.kind !== "hover") return outcome("unavailable", "semantic_result_invalid");
1040
+ const hover = value.hover;
1041
+ if (hover === null) return responseFor(result.status, void 0, metadata, { hover: null });
1042
+ if (!isRecord(hover) || typeof hover.contents !== "string") return outcome("unavailable", "semantic_result_invalid");
1043
+ const contents = trimUtf16(hover.contents, 16e3);
1044
+ const truncated = contents !== hover.contents;
1045
+ return responseFor(result.status, void 0, metadata, { hover: {
1046
+ contents,
1047
+ ...hover.range ? { range: hover.range } : {}
1048
+ } }, truncated, truncated ? hover.contents.length - contents.length : 0);
1049
+ }
1050
+ if (input.kind === "completion") {
1051
+ if (!isRecord(value) || !Array.isArray(value.items)) return outcome("unavailable", "semantic_result_invalid");
1052
+ let truncated = Boolean(result.truncated);
1053
+ const items = value.items.map((item) => ({
1054
+ label: typeof item.label === "string" ? item.label.slice(0, 1e3) : "",
1055
+ ...typeof item.detail === "string" ? { detail: item.detail.slice(0, 4e3) } : {},
1056
+ ...typeof item.documentation === "string" ? { documentation: item.documentation.slice(0, 8e3) } : {},
1057
+ ...Number.isInteger(item.kind) ? { kind: item.kind } : {},
1058
+ ...typeof item.insertText === "string" ? { insertText: item.insertText.slice(0, 8e3) } : {}
1059
+ }));
1060
+ if (items.some((item, index) => item.label !== value.items[index]?.label || item.detail !== value.items[index]?.detail || item.documentation !== value.items[index]?.documentation || item.insertText !== value.items[index]?.insertText)) truncated = true;
1061
+ const limit = input.limit ?? 200;
1062
+ const omitted = (result.omitted ?? 0) + Math.max(0, items.length - limit);
1063
+ return responseFor(truncated || omitted ? "partial" : result.status, void 0, metadata, { items: items.slice(0, limit) }, truncated || omitted > 0, omitted);
1064
+ }
1065
+ if (input.kind === "diagnostics") {
1066
+ if (!isRecord(value) || !Array.isArray(value.diagnostics)) return outcome("unavailable", "semantic_result_invalid");
1067
+ if (value.skipped) return responseFor("unsupported", "unsupported_file_type", metadata);
1068
+ let truncated = Boolean(result.truncated);
1069
+ const items = value.diagnostics.map((item) => {
1070
+ const message = typeof item.message === "string" ? item.message.slice(0, 8e3) : "";
1071
+ if (message !== item.message) truncated = true;
1072
+ return {
1073
+ uri: item.uri,
1074
+ range: item.range,
1075
+ message,
1076
+ ...Number.isInteger(item.severity) ? { severity: item.severity } : {}
1077
+ };
1078
+ });
1079
+ const limit = input.limit ?? 200;
1080
+ const omitted = (result.omitted ?? 0) + Math.max(0, items.length - limit);
1081
+ return responseFor(truncated || omitted || result.status === "partial" ? "partial" : result.status, void 0, metadata, { items: items.slice(0, limit) }, truncated || omitted > 0, omitted);
1082
+ }
1083
+ if (!isRecord(value) || value.kind !== "locations" || !Array.isArray(value.locations)) return outcome("unavailable", "semantic_result_invalid");
1084
+ return responseFor(result.status, void 0, metadata, { items: value.locations }, Boolean(result.truncated), result.omitted ?? 0);
1085
+ }
1086
+ async release(handle) {
1087
+ const known = this.snapshots.delete(handle);
1088
+ for (const [node, snapshot] of this.nodes) if (snapshot === handle) this.nodes.delete(node);
1089
+ if (!known) return responseFor("ok", "released", {
1090
+ sourceKind: "disk",
1091
+ freshness: "unknown"
1092
+ }, { released: false });
1093
+ const result = await this.backend.snapshot.release(this.scope, handle);
1094
+ return responseFor("ok", "released", {
1095
+ sourceKind: "disk",
1096
+ freshness: "unknown"
1097
+ }, result);
1098
+ }
1099
+ capabilitiesResponse(capabilities) {
1100
+ return responseFor("ok", "capabilities", {
1101
+ sourceKind: "disk",
1102
+ freshness: "unknown"
1103
+ }, capabilities);
1104
+ }
1105
+ rememberNodes(snapshotHandle, items) {
1106
+ for (const item of items) this.nodes.set(item.handle, snapshotHandle);
1107
+ }
1108
+ account(response) {
1109
+ if (Buffer.byteLength(JSON.stringify(response)) > RESPONSE_BYTES) response = outcome("limit_exceeded", "response_limit");
1110
+ this.outputBytes += Buffer.byteLength(JSON.stringify(response));
1111
+ if (this.outputBytes > MAX_OUTPUT_BYTES) return outcome("limit_exceeded", "response_budget");
1112
+ return response;
1113
+ }
1114
+ dispose() {
1115
+ if (this.disposal) return this.disposal;
1116
+ this.disposed = true;
1117
+ this.lifetime.abort();
1118
+ this.snapshots.clear();
1119
+ this.nodes.clear();
1120
+ this.onDispose();
1121
+ this.disposal = Promise.resolve(this.backend.releaseOwner?.(this.scope)).then(() => void 0, () => void 0);
1122
+ return this.disposal;
1123
+ }
1124
+ };
1125
+ function outcome(status, reason) {
1126
+ return {
1127
+ protocol: "code-intelligence/v1",
1128
+ status,
1129
+ reason,
1130
+ sourceKind: "disk",
1131
+ freshness: status === "stale" ? "stale" : "unknown",
1132
+ truncated: false,
1133
+ omitted: 0
1134
+ };
1135
+ }
1136
+ function responseFor(status, reason, metadata, data, truncated = false, omitted = 0) {
1137
+ const successful = status === "ok" || status === "partial";
1138
+ return {
1139
+ protocol: "code-intelligence/v1",
1140
+ status,
1141
+ ...reason ? { reason } : {},
1142
+ sourceKind: "disk",
1143
+ freshness: metadata.freshness,
1144
+ ...metadata.snapshotVersion ? { snapshotVersion: metadata.snapshotVersion } : {},
1145
+ truncated,
1146
+ omitted,
1147
+ ...successful && data !== void 0 ? { data } : {}
1148
+ };
1149
+ }
1150
+ function boundResponse(response) {
1151
+ if (Buffer.byteLength(JSON.stringify(response), "utf8") <= RESPONSE_BYTES) return response;
1152
+ if (isRecord(response.data) && Array.isArray(response.data.items)) {
1153
+ const items = [...response.data.items];
1154
+ let omitted = response.omitted;
1155
+ while (items.length && Buffer.byteLength(JSON.stringify({
1156
+ ...response,
1157
+ data: {
1158
+ ...response.data,
1159
+ items
1160
+ }
1161
+ }), "utf8") > RESPONSE_BYTES) {
1162
+ items.pop();
1163
+ omitted++;
1164
+ }
1165
+ const shortened = {
1166
+ ...response,
1167
+ status: "partial",
1168
+ reason: response.reason ?? "response_truncated",
1169
+ truncated: true,
1170
+ omitted,
1171
+ data: {
1172
+ ...response.data,
1173
+ items
1174
+ }
1175
+ };
1176
+ if (Buffer.byteLength(JSON.stringify(shortened), "utf8") <= RESPONSE_BYTES) return shortened;
1177
+ }
1178
+ return outcome("limit_exceeded", "response_limit");
1179
+ }
1180
+ function mapError(error) {
1181
+ if (error.code === "LSP_STALE_SNAPSHOT") return outcome("stale", "source_changed");
1182
+ if (error.code === "LSP_STRUCTURE_TIMEOUT") return outcome("timeout", "parser_deadline");
1183
+ if (error.code === "LSP_STRUCTURE_LIMIT" || error.code === "LSP_SNAPSHOT_LIMIT") return outcome("limit_exceeded", "provider_limit");
1184
+ if (error.code === "LSP_STRUCTURE_UNSUPPORTED" || error.code === "LSP_UNSUPPORTED_OPERATION") return outcome("unsupported", "capability_missing");
1185
+ if (error.code === "LSP_STRUCTURE_BUSY") return outcome("unavailable", "parser_busy");
1186
+ if (error.code === "LSP_CODE_INTELLIGENCE_UNAVAILABLE" || error.code === "LSP_STRUCTURE_UNAVAILABLE" || error.code === "LSP_UNAVAILABLE") return outcome("unavailable", "provider_unavailable");
1187
+ if (error.code === "LSP_WORKSPACE_REQUIRED") return outcome("unavailable", "invalid_path");
1188
+ return outcome("unavailable", "provider_operation_failed");
1189
+ }
1190
+ function projectNode(node) {
1191
+ return {
1192
+ handle: node.handle,
1193
+ kind: node.kind,
1194
+ range: node.range,
1195
+ ...node.name ? { name: node.name } : {}
1196
+ };
1197
+ }
1198
+ function trimUtf16(value, limit) {
1199
+ if (value.length <= limit) return value;
1200
+ const end = value.charCodeAt(limit - 1) >= 55296 && value.charCodeAt(limit - 1) <= 56319 ? limit - 1 : limit;
1201
+ return value.slice(0, end);
1202
+ }
1203
+ function validCodeRequest(value) {
1204
+ if (!isRecord(value) || typeof value.method !== "string") return false;
1205
+ const allowed = {
1206
+ capabilities: ["method"],
1207
+ "snapshot.open": ["method", "path"],
1208
+ "snapshot.release": ["method", "handle"],
1209
+ "structure.outline": [
1210
+ "method",
1211
+ "handle",
1212
+ "range",
1213
+ "limit"
1214
+ ],
1215
+ "structure.enclosing": [
1216
+ "method",
1217
+ "handle",
1218
+ "position",
1219
+ "kinds"
1220
+ ],
1221
+ "structure.query": [
1222
+ "method",
1223
+ "handle",
1224
+ "queryId",
1225
+ "range",
1226
+ "limit"
1227
+ ],
1228
+ "structure.span": [
1229
+ "method",
1230
+ "nodeHandle",
1231
+ "maxChars"
1232
+ ],
1233
+ "semantic.query": [
1234
+ "method",
1235
+ "handle",
1236
+ "kind",
1237
+ "position",
1238
+ "limit"
1239
+ ]
1240
+ }[value.method];
1241
+ if (!allowed || Object.keys(value).some((key) => !allowed.includes(key))) return false;
1242
+ const handle = "handle" in value ? value.handle : "nodeHandle" in value ? value.nodeHandle : void 0;
1243
+ if (handle !== void 0 && (typeof handle !== "string" || !handle.length || handle.length > 256)) return false;
1244
+ if (value.method === "snapshot.open") return typeof value.path === "string" && value.path.length > 0 && value.path.length <= 4096;
1245
+ if (value.method === "capabilities" || value.method === "snapshot.release") return true;
1246
+ if ("limit" in value && value.limit !== void 0 && (!Number.isSafeInteger(value.limit) || value.limit < 1 || value.limit > 200)) return false;
1247
+ if ("range" in value && value.range !== void 0 && !validRange(value.range)) return false;
1248
+ if (value.method === "structure.outline") return value.limit === void 0 || value.limit <= 100;
1249
+ if (value.method === "structure.enclosing") return validPosition(value.position) && (value.kinds === void 0 || Array.isArray(value.kinds) && value.kinds.length <= 30 && value.kinds.every((kind) => typeof kind === "string" && kind.length <= 100));
1250
+ if (value.method === "structure.query") return [
1251
+ "declarations",
1252
+ "calls",
1253
+ "imports"
1254
+ ].includes(String(value.queryId));
1255
+ if (value.method === "structure.span") return value.maxChars === void 0 || Number.isSafeInteger(value.maxChars) && value.maxChars >= 1 && value.maxChars <= 8e3;
1256
+ if (value.method === "semantic.query") return [
1257
+ "definition",
1258
+ "references",
1259
+ "implementation",
1260
+ "hover",
1261
+ "diagnostics",
1262
+ "completion"
1263
+ ].includes(String(value.kind)) && (value.kind === "diagnostics" || validPosition(value.position));
1264
+ return false;
1265
+ }
1266
+ function validRange(value) {
1267
+ if (!isRecord(value) || !validPosition(value.start) || !validPosition(value.end)) return false;
1268
+ return value.end.line > value.start.line || value.end.line === value.start.line && value.end.character >= value.start.character;
1269
+ }
1270
+ function validPosition(value) {
1271
+ return isRecord(value) && Number.isSafeInteger(value.line) && Number.isSafeInteger(value.character) && value.line >= 0 && value.character >= 0;
1272
+ }
1273
+ function isRecord(value) {
1274
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1275
+ }
1276
+ function identity(value) {
1277
+ return value && typeof value === "object" ? value[symbols.original] ?? value : value;
1278
+ }
1279
+ function validateBinding(binding) {
1280
+ if (!binding || !binding.owner || typeof binding.owner.agentId !== "string" || typeof binding.owner.sessionId !== "string" || typeof binding.workspaceRoot !== "string" || !isAbsolute(binding.workspaceRoot) || !binding.scope || typeof binding.assertCurrent !== "function" || !binding.context || typeof binding.context.get !== "function") throw new WebstackLspError("LSP_CODE_INTELLIGENCE_UNAVAILABLE", "invalid trusted code host binding");
1281
+ }
1282
+
830
1283
  //#endregion
831
1284
  //#region src/runtime/webstack-runtime.ts
832
1285
  const methods = {
@@ -858,6 +1311,10 @@ const clientCapabilities = {
858
1311
  };
859
1312
  var WebstackLspRuntime = class {
860
1313
  pool;
1314
+ snapshots;
1315
+ codeIntelligence;
1316
+ codeIntelligenceRuntime;
1317
+ semanticOperations = /* @__PURE__ */ new Map();
861
1318
  workspaces = /* @__PURE__ */ new Map();
862
1319
  lifetime = new AbortController();
863
1320
  live = /* @__PURE__ */ new Map();
@@ -867,6 +1324,17 @@ var WebstackLspRuntime = class {
867
1324
  this.config = config;
868
1325
  this.fs = fs;
869
1326
  this.subprocess = subprocess;
1327
+ this.snapshots = new DocumentSnapshotStore((workspaceRoot, filePath, maxBytes, signal) => readSource(this.fs, workspaceRoot, filePath, maxBytes, signal), config.limits.maxDocumentBytes);
1328
+ this.codeIntelligenceRuntime = createCodeIntelligenceToolServiceV1({
1329
+ snapshots: this.snapshots,
1330
+ semanticQuery: (scope, snapshot, request, signal) => this.semanticFromSnapshot(scope, snapshot, request, signal)
1331
+ });
1332
+ this.codeIntelligence = new CodeIntelligenceProtocolService({
1333
+ backend: this.codeIntelligenceRuntime,
1334
+ fs: this.fs,
1335
+ subprocess: this.subprocess,
1336
+ capabilities: (signal) => this.codeCapabilities(signal)
1337
+ });
870
1338
  this.pool = new ServerPool(async (rawId, key, signal) => {
871
1339
  const id = rawId, source = this.workspaces.get(key);
872
1340
  const spec = await resolveLaunch(id, config, source.workspace);
@@ -917,6 +1385,9 @@ var WebstackLspRuntime = class {
917
1385
  async navigation(request, signal) {
918
1386
  const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
919
1387
  const [source, route] = await this.prepare(request, deadline);
1388
+ return this.navigationFromSource(request, source, route, deadline, signal);
1389
+ }
1390
+ async navigationFromSource(request, source, route, deadline, signal) {
920
1391
  this.validatePosition(source.text, request.position);
921
1392
  const result = await this.document(route.primary.server, source, route.primary.languageId, deadline, async (session, connection) => {
922
1393
  if (!supportsOperation(session.capabilities, request.operation)) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${route.primary.server} does not support ${request.operation}`);
@@ -965,6 +1436,9 @@ var WebstackLspRuntime = class {
965
1436
  async completion(request, signal) {
966
1437
  const deadline = this.signal(this.config.timeouts.completionMs, signal);
967
1438
  const [source, route] = await this.prepare(request, deadline);
1439
+ return this.completionFromSource(request, source, route, deadline, signal);
1440
+ }
1441
+ async completionFromSource(request, source, route, deadline, signal) {
968
1442
  this.validatePosition(source.text, request.position);
969
1443
  const query = (id, language) => this.document(id, source, language, deadline, (session, connection) => {
970
1444
  if (!session.capabilities.completionProvider) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${id} does not support completion`);
@@ -995,7 +1469,20 @@ var WebstackLspRuntime = class {
995
1469
  }
996
1470
  async diagnostics(request, signal) {
997
1471
  const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
1472
+ if (!classifyFile(request.filePath, this.config.routing.bladeSuffixes)) {
1473
+ await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, deadline);
1474
+ return {
1475
+ diagnostics: [],
1476
+ skipped: {
1477
+ reason: "unsupported_file_type",
1478
+ filePath: request.filePath
1479
+ }
1480
+ };
1481
+ }
998
1482
  const [source, route] = await this.prepare(request, deadline);
1483
+ return this.diagnosticsFromSource(request, source, route, deadline, signal);
1484
+ }
1485
+ async diagnosticsFromSource(request, source, route, deadline, signal) {
999
1486
  const selected = [route.primary, ...route.auxiliary && this.config.tailwind.enabled ? [route.auxiliary] : []].filter((item) => this.config.servers[item.server].enabled && (!request.servers || request.servers.includes(item.server)));
1000
1487
  if (!selected.length) throw new WebstackLspError("LSP_UNAVAILABLE", "no selected diagnostic server supports this file");
1001
1488
  const omittedServers = [];
@@ -1007,10 +1494,10 @@ var WebstackLspRuntime = class {
1007
1494
  return (await connection.request("textDocument/diagnostic", { textDocument: { uri: source.uri } }, deadline))?.items ?? [];
1008
1495
  }
1009
1496
  let remove = () => {};
1010
- const published = new Promise((resolve) => {
1497
+ const published = new Promise((resolve$1) => {
1011
1498
  remove = connection.onNotification((message) => {
1012
1499
  const p = message.params;
1013
- if (message.method === "textDocument/publishDiagnostics" && p?.uri === source.uri && (p.version === void 0 || p.version === 1)) resolve(p.diagnostics);
1500
+ if (message.method === "textDocument/publishDiagnostics" && p?.uri === source.uri && (p.version === void 0 || p.version === 1)) resolve$1(p.diagnostics);
1014
1501
  });
1015
1502
  });
1016
1503
  try {
@@ -1037,15 +1524,71 @@ var WebstackLspRuntime = class {
1037
1524
  ...omittedServers.length ? { omittedServers: omittedServers.sort() } : {}
1038
1525
  };
1039
1526
  }
1040
- async status() {
1527
+ async semanticFromSnapshot(scope, snapshot, request, signal) {
1528
+ const source = snapshot.source;
1529
+ this.rememberWorkspace(source);
1530
+ const file = classifyFile(snapshot.descriptor.filePath, this.config.routing.bladeSuffixes);
1531
+ if (!file) {
1532
+ if (request.operation === "diagnostics") return {
1533
+ diagnostics: [],
1534
+ skipped: {
1535
+ reason: "unsupported_file_type",
1536
+ filePath: snapshot.descriptor.filePath
1537
+ }
1538
+ };
1539
+ throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
1540
+ }
1541
+ const route = routeFor(file);
1542
+ let result;
1543
+ if (request.operation === "diagnostics") {
1544
+ const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
1545
+ result = await this.diagnosticsFromSource({
1546
+ workspaceRoot: scope.workspaceRoot,
1547
+ filePath: snapshot.descriptor.filePath,
1548
+ ...request.severity ? { severity: request.severity } : {},
1549
+ ...request.servers ? { servers: request.servers } : {}
1550
+ }, source, route, deadline, signal);
1551
+ } else if (request.operation === "completion") {
1552
+ const deadline = this.signal(this.config.timeouts.completionMs, signal);
1553
+ result = await this.completionFromSource({
1554
+ workspaceRoot: scope.workspaceRoot,
1555
+ filePath: snapshot.descriptor.filePath,
1556
+ position: request.position
1557
+ }, source, route, deadline, signal);
1558
+ } else {
1559
+ const operation = {
1560
+ definition: "goToDefinition",
1561
+ references: "findReferences",
1562
+ implementation: "goToImplementation",
1563
+ hover: "hover"
1564
+ }[request.operation];
1565
+ const position = request.position;
1566
+ const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
1567
+ result = await this.navigationFromSource({
1568
+ workspaceRoot: scope.workspaceRoot,
1569
+ filePath: snapshot.descriptor.filePath,
1570
+ languageId: route.primary.languageId,
1571
+ operation,
1572
+ position
1573
+ }, source, route, deadline, signal);
1574
+ }
1575
+ if (!("skipped" in result && result.skipped)) {
1576
+ const operations = this.semanticOperations.get(route.primary.languageId) ?? /* @__PURE__ */ new Set();
1577
+ operations.add(request.operation);
1578
+ this.semanticOperations.set(route.primary.languageId, operations);
1579
+ }
1580
+ return result;
1581
+ }
1582
+ async status(signal) {
1041
1583
  return {
1042
1584
  providerId: this.config.providerId,
1585
+ supportedFileSuffixes: supportedFileSuffixes(this.config.routing.bladeSuffixes),
1043
1586
  servers: await Promise.all(SERVER_IDS.map(async (id) => {
1044
1587
  const config = this.config.servers[id];
1045
1588
  let available = false;
1046
1589
  try {
1047
1590
  const launch = await resolveLaunch(id, this.config, ".");
1048
- await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3));
1591
+ await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3, signal));
1049
1592
  available = true;
1050
1593
  } catch {}
1051
1594
  return {
@@ -1059,11 +1602,69 @@ var WebstackLspRuntime = class {
1059
1602
  }))
1060
1603
  };
1061
1604
  }
1605
+ async codeCapabilities(signal) {
1606
+ signal.throwIfAborted();
1607
+ const [provider, lsp] = await Promise.all([Promise.resolve(this.codeIntelligenceRuntime.capabilities()), this.status(signal)]);
1608
+ const server = lsp.servers.find((item) => item.id === "typescript");
1609
+ const languageCapabilities = provider.parsers.map((parser) => {
1610
+ const semanticIds = parser.language === "javascript" ? ["javascript", "javascriptreact"] : parser.language === "tsx" ? ["typescriptreact"] : ["typescript"];
1611
+ const semantic = [...new Set(semanticIds.flatMap((id) => [...this.semanticOperations.get(id) ?? []]))];
1612
+ const semanticReady = semantic.length > 0 && Boolean(server?.enabled && server.available);
1613
+ const reason = !parser.available ? "grammar_unavailable" : !server?.enabled || !server.available ? "server_unavailable" : !semanticReady ? "not_yet_verified" : void 0;
1614
+ return {
1615
+ language: parser.language,
1616
+ structure: parser.available,
1617
+ semantic,
1618
+ semanticReady,
1619
+ ...reason ? { reason } : {}
1620
+ };
1621
+ });
1622
+ signal.throwIfAborted();
1623
+ return {
1624
+ version: 1,
1625
+ capabilities: [
1626
+ "snapshots",
1627
+ "structure",
1628
+ "semantic"
1629
+ ],
1630
+ languages: provider.parsers.filter((parser) => parser.available).map((parser) => parser.language),
1631
+ queryIds: [
1632
+ "declarations",
1633
+ "calls",
1634
+ "imports"
1635
+ ],
1636
+ semanticReady: languageCapabilities.some((language) => language.semanticReady),
1637
+ sourceKinds: ["disk"],
1638
+ languageCapabilities,
1639
+ limits: {
1640
+ documentBytes: provider.limits.maxDocumentBytes,
1641
+ captureItems: provider.limits.maxCaptures,
1642
+ outlineItems: provider.limits.maxOutline,
1643
+ spanChars: provider.limits.maxSpanChars,
1644
+ responseBytes: provider.limits.maxResponseBytes,
1645
+ files: 100,
1646
+ inputBytes: 16777216,
1647
+ outputBytes: 65536,
1648
+ calls: 100,
1649
+ batchMs: 3e4,
1650
+ structureMs: 2e3,
1651
+ semanticMs: 1e4
1652
+ }
1653
+ };
1654
+ }
1062
1655
  async dispose() {
1063
1656
  this.lifetime.abort();
1064
- await this.pool.disposeAll();
1065
- this.workspaces.clear();
1066
- this.live.clear();
1657
+ try {
1658
+ await this.codeIntelligence.dispose?.();
1659
+ } finally {
1660
+ try {
1661
+ await this.pool.disposeAll();
1662
+ } finally {
1663
+ await this.codeIntelligenceRuntime.dispose?.();
1664
+ this.workspaces.clear();
1665
+ this.live.clear();
1666
+ }
1667
+ }
1067
1668
  }
1068
1669
  signal(ms, signal) {
1069
1670
  return AbortSignal.any([
@@ -1076,15 +1677,18 @@ var WebstackLspRuntime = class {
1076
1677
  throwIfAborted(signal);
1077
1678
  const file = classifyFile(request.filePath, this.config.routing.bladeSuffixes);
1078
1679
  if (!file) throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
1079
- const source = await readSource(this.fs, request.workspaceRoot, request.filePath, this.config.limits.maxDocumentBytes, signal).catch((error) => {
1680
+ const source = await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, signal).catch((error) => {
1080
1681
  throwIfAborted(signal);
1081
1682
  throw error;
1082
1683
  });
1684
+ this.rememberWorkspace(source);
1685
+ return [source, routeFor(file)];
1686
+ }
1687
+ rememberWorkspace(source) {
1083
1688
  this.workspaces.set(source.key, {
1084
1689
  ...source,
1085
1690
  text: ""
1086
1691
  });
1087
- return [source, routeFor(file)];
1088
1692
  }
1089
1693
  validatePosition(text, position) {
1090
1694
  const line = text.split(/\r?\n/)[position.line];
@@ -1117,6 +1721,13 @@ var WebstackLspRuntime = class {
1117
1721
  } : void 0);
1118
1722
  }
1119
1723
  };
1724
+ function providerScope(workspaceRoot) {
1725
+ return {
1726
+ ownerId: "lsp-provider",
1727
+ sessionId: "lsp-provider",
1728
+ workspaceRoot
1729
+ };
1730
+ }
1120
1731
 
1121
1732
  //#endregion
1122
1733
  //#region src/provider.ts
@@ -1160,6 +1771,8 @@ async function apply(ctx, config = {}) {
1160
1771
  }
1161
1772
  yield ctx.lsp.registerProvider(createCompositeProvider(runtime, resolved));
1162
1773
  yield ctx.provide("webstackLsp", createWebstackLspService(runtime));
1774
+ yield ctx.provide("codeIntelligence", runtime.codeIntelligence);
1775
+ yield ctx.provide("codeIntelligenceRuntime", runtime.codeIntelligenceRuntime);
1163
1776
  }, "webstack LSP provider");
1164
1777
  }
1165
1778