@oml/server 0.14.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.
@@ -0,0 +1,423 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ import { DocumentState, URI } from 'langium';
4
+ import { startLanguageServer } from 'langium/lsp';
5
+ import type { DefaultSharedModuleContext } from 'langium/lsp';
6
+ import type { Connection } from 'vscode-languageserver';
7
+ import {
8
+ collectOntologyMembers,
9
+ getIriForNode,
10
+ getOntologyModelIndex,
11
+ iriFragment,
12
+ isDescription,
13
+ isOntology,
14
+ type OmlFuzzyIndexedEntry,
15
+ isVocabulary,
16
+ registerOmlCandidatesRequests,
17
+ registerOmlEditRequests,
18
+ tokenizeForFuzzy,
19
+ } from '@oml/language';
20
+ import { createOwlServices, detectSparqlKind, registerShaclValidationRequests } from '@oml/owl';
21
+ import uFuzzy from '@leeoniya/ufuzzy';
22
+ import {
23
+ MarkdownExecutionRequest,
24
+ MarkdownExecutor,
25
+ type MdExecuteBlocksParams,
26
+ type MdExecuteBlocksResult
27
+ } from '@oml/markdown';
28
+ import { registerDiagramRequests } from './diagram-server.js';
29
+ import {
30
+ ReasoningNotifications,
31
+ ReasoningRequests,
32
+ type ActiveDocumentParams,
33
+ type FuzzySearchParams,
34
+ type FuzzySearchResult,
35
+ type ResolveModelUriParams,
36
+ type ResolveModelUriResult,
37
+ type SemanticChangedParams,
38
+ type SparqlQueryParams,
39
+ type SparqlQueryResult,
40
+ type SparqlTermDto,
41
+ } from './protocol/reasoner-protocol.js';
42
+
43
+ export interface OmlLanguageServerStartOptions {
44
+ fileSystem: Omit<DefaultSharedModuleContext, 'connection'>;
45
+ registerDiagramHandlers?: boolean;
46
+ suppressTransientDiagnostics?: boolean;
47
+ installNodeProcessHandlers?: boolean;
48
+ }
49
+
50
+ export interface OmlLanguageServerRuntime {
51
+ shared: any;
52
+ Oml: any;
53
+ }
54
+
55
+ const workspacePreloadState = new WeakMap<object, { completed: boolean; promise?: Promise<void> }>();
56
+
57
+ function formatError(error: unknown, depth = 0): string {
58
+ if (!(error instanceof Error)) {
59
+ return String(error);
60
+ }
61
+
62
+ const indent = depth > 0 ? '\n' + ' '.repeat(depth) : '';
63
+ const stack = error.stack ? `${indent}${error.stack}` : `${indent}${error.message}`;
64
+ const cause = (error as any).cause;
65
+ if (cause === undefined) {
66
+ return stack;
67
+ }
68
+ return `${stack}\n${' '.repeat(depth)}cause: ${formatError(cause, depth + 1)}`;
69
+ }
70
+
71
+ export function startOmlLanguageServer(connection: Connection, options: OmlLanguageServerStartOptions): OmlLanguageServerRuntime {
72
+ const logServerError = (scope: string, error: unknown): void => {
73
+ connection.console.error(`[oml] ${scope}: ${formatError(error)}`);
74
+ };
75
+
76
+ if (options.installNodeProcessHandlers) {
77
+ process.on('unhandledRejection', (reason) => {
78
+ logServerError('Unhandled rejection', reason);
79
+ });
80
+ process.on('uncaughtException', (error) => {
81
+ logServerError('Uncaught exception', error);
82
+ });
83
+ }
84
+
85
+ const sendDiagnostics = connection.sendDiagnostics.bind(connection);
86
+ connection.sendDiagnostics = ((params: any): Promise<void> => {
87
+ if (!params?.uri) {
88
+ return sendDiagnostics(params);
89
+ }
90
+ if (options.suppressTransientDiagnostics !== false && isTransientDiagnosticsUri(params.uri)) {
91
+ return sendDiagnostics({ ...params, diagnostics: [] });
92
+ }
93
+ return sendDiagnostics(params);
94
+ }) as any;
95
+
96
+ const { shared, Oml } = createOwlServices({ connection, ...options.fileSystem });
97
+
98
+ if (options.registerDiagramHandlers !== false) {
99
+ registerDiagramRequests(connection as any, shared, Oml);
100
+ }
101
+ registerOmlCandidatesRequests(connection as any, shared);
102
+ registerOmlEditRequests(connection as any, shared);
103
+ registerShaclValidationRequests(connection as any, Oml);
104
+
105
+ {
106
+ const reasoningService = Oml.reasoning.ReasoningService as any;
107
+ reasoningService.onSemanticChanged((modelUris: string[]) => {
108
+ const params: SemanticChangedParams = { modelUris };
109
+ connection.sendNotification(ReasoningNotifications.semanticChanged, params);
110
+ });
111
+ }
112
+ connection.onNotification(ReasoningNotifications.markDocumentActive, ({ modelUri }: ActiveDocumentParams) => {
113
+ const reasoningService = Oml.reasoning.ReasoningService as any;
114
+ reasoningService.markDocumentActive(modelUri);
115
+ });
116
+ connection.onNotification(ReasoningNotifications.markDocumentInactive, ({ modelUri }: ActiveDocumentParams) => {
117
+ const reasoningService = Oml.reasoning.ReasoningService as any;
118
+ reasoningService.markDocumentInactive(modelUri);
119
+ });
120
+ connection.onRequest(MarkdownExecutionRequest, async (params: MdExecuteBlocksParams): Promise<MdExecuteBlocksResult> => {
121
+ try {
122
+ const reasoningService = Oml.reasoning.ReasoningService as any;
123
+ const ontologyIndex = getOntologyModelIndex(shared as any);
124
+ const knownNamespaceCache = new Map<string, boolean>();
125
+ const isKnownOntologyIriNamespace = (ontologyIri: string): boolean => {
126
+ const normalized = ontologyIri.trim().replace(/[\/#]+$/, '');
127
+ if (!normalized) {
128
+ return false;
129
+ }
130
+ const cached = knownNamespaceCache.get(normalized);
131
+ if (cached !== undefined) {
132
+ return cached;
133
+ }
134
+ if (ontologyIndex.resolveModelUri(normalized)) {
135
+ knownNamespaceCache.set(normalized, true);
136
+ return true;
137
+ }
138
+ const exists = ontologyIndex.getDuplicateWorkspaceModelUris(normalized).length > 0;
139
+ knownNamespaceCache.set(normalized, exists);
140
+ return exists;
141
+ };
142
+ const executor = new MarkdownExecutor({
143
+ ensureContext: (modelUri) => reasoningService.ensureQueryContext(modelUri),
144
+ resolveContextIri: (modelUri) => reasoningService.getContextIri(modelUri),
145
+ countContextQuads: (modelUri) => reasoningService.countContextDatasetQuads(modelUri),
146
+ query: (modelUri, sparql) => reasoningService.getSparqlService().query(modelUri, sparql),
147
+ construct: (modelUri, sparql) => reasoningService.getSparqlService().construct(modelUri, sparql),
148
+ isKnownOntologyIriNamespace,
149
+ });
150
+ return await executor.executeBlocks(params);
151
+ } catch (error) {
152
+ logServerError('MarkdownExecutionRequest failed', error);
153
+ return {
154
+ results: params.blocks.map((block) => ({
155
+ blockId: block.id,
156
+ kind: block.kind,
157
+ status: 'error',
158
+ format: 'message',
159
+ message: error instanceof Error ? error.message : String(error),
160
+ })),
161
+ };
162
+ }
163
+ });
164
+ connection.onRequest(ReasoningRequests.sparqlQuery, async (params: SparqlQueryParams): Promise<SparqlQueryResult> => {
165
+ try {
166
+ const reasoningService = Oml.reasoning.ReasoningService as any;
167
+ const kind = detectSparqlKind(params.sparql);
168
+ await reasoningService.ensureQueryContext(params.modelUri);
169
+ const sparqlService = reasoningService.getSparqlService();
170
+ if (kind === 'select') {
171
+ const result = await sparqlService.query(params.modelUri, params.sparql);
172
+ return {
173
+ success: result.success,
174
+ kind,
175
+ warnings: result.warnings ?? [],
176
+ rows: result.rows.map((row: Map<string, any>) => toRowDto(row)),
177
+ error: result.error,
178
+ };
179
+ }
180
+ if (kind === 'ask') {
181
+ const result = await sparqlService.ask(params.modelUri, params.sparql);
182
+ return {
183
+ success: result.success,
184
+ kind,
185
+ warnings: result.warnings ?? [],
186
+ result: result.result,
187
+ error: result.error,
188
+ };
189
+ }
190
+ if (kind === 'construct' || kind === 'describe') {
191
+ const result = await sparqlService.construct(params.modelUri, params.sparql);
192
+ return {
193
+ success: result.success,
194
+ kind,
195
+ warnings: result.warnings ?? [],
196
+ quads: result.quads.map((quad: any) => ({
197
+ subject: quad.subject.value,
198
+ predicate: quad.predicate.value,
199
+ object: quad.object.value,
200
+ graph: quad.graph.value,
201
+ })),
202
+ error: result.error,
203
+ };
204
+ }
205
+ return {
206
+ success: false,
207
+ kind: 'unknown',
208
+ warnings: [],
209
+ error: 'Unsupported or unknown SPARQL query kind.',
210
+ };
211
+ } catch (error) {
212
+ logServerError('oml/query failed', error);
213
+ return {
214
+ success: false,
215
+ kind: detectSparqlKind(params.sparql),
216
+ warnings: [],
217
+ error: error instanceof Error ? error.message : String(error),
218
+ };
219
+ }
220
+ });
221
+
222
+ connection.onRequest(ReasoningRequests.fuzzySearch, async (params: FuzzySearchParams): Promise<FuzzySearchResult> => {
223
+ try {
224
+ const text = (params.text ?? '').trim();
225
+ if (!text) {
226
+ return { success: true, candidates: [] };
227
+ }
228
+ const limit = Math.max(1, Math.min(50, params.limit ?? 12));
229
+ const ontologyIndex = getOntologyModelIndex(shared as any);
230
+ const langiumDocuments: any = shared.workspace.LangiumDocuments;
231
+ const allDocs = langiumDocuments.all ?? [];
232
+ const iterable: any[] = Array.isArray(allDocs)
233
+ ? allDocs
234
+ : (typeof allDocs?.toArray === 'function' ? allDocs.toArray() : Array.from(allDocs as Iterable<any>));
235
+ const modelUris: string[] = [];
236
+ const candidateByIri = new Map<string, { iri: string; label?: string }>();
237
+ for (const doc of iterable) {
238
+ const root = doc?.parseResult?.value;
239
+ if (!root || !isOntology(root) || (!isVocabulary(root) && !isDescription(root))) {
240
+ continue;
241
+ }
242
+ modelUris.push(doc.uri.toString());
243
+ for (const member of collectOntologyMembers(root)) {
244
+ const iri = getIriForNode(member);
245
+ if (!iri) {
246
+ continue;
247
+ }
248
+ candidateByIri.set(iri, { iri });
249
+ }
250
+ }
251
+ const labelSnapshot = ontologyIndex.getMemberLabelSnapshot(modelUris);
252
+ for (const [iri, label] of Object.entries(labelSnapshot)) {
253
+ const entry = candidateByIri.get(iri) ?? { iri };
254
+ entry.label = label;
255
+ candidateByIri.set(iri, entry);
256
+ }
257
+
258
+ const indexedEntries: OmlFuzzyIndexedEntry[] = [...candidateByIri.values()].map((entry) => ({
259
+ iri: entry.iri,
260
+ label: entry.label,
261
+ fragment: iriFragment(entry.iri),
262
+ fragmentTokens: tokenizeForFuzzy(iriFragment(entry.iri)),
263
+ labelTokens: tokenizeForFuzzy(entry.label ?? ''),
264
+ }));
265
+ const haystack = indexedEntries.map((entry) => `${entry.fragment}\n${entry.label ?? ''}\n${entry.iri}`);
266
+ const uf = new uFuzzy({});
267
+ const filtered = uf.filter(haystack, text);
268
+ if (!filtered || filtered.length === 0) {
269
+ return { success: true, candidates: [] };
270
+ }
271
+ const info = uf.info(filtered, haystack, text);
272
+ const order = info ? uf.sort(info, haystack, text) : null;
273
+ const ranked = (order ?? filtered.map((_, index) => index))
274
+ .map((index) => filtered[index]!)
275
+ .slice(0, limit);
276
+ const queryTokens = tokenizeForFuzzy(text).slice(0, 6);
277
+ const candidates = ranked.map((entryIndex, index) => {
278
+ const entry = indexedEntries[entryIndex]!;
279
+ const fragment = entry.fragment.toLowerCase();
280
+ const lowerLabel = (entry.label ?? '').toLowerCase();
281
+ const lowerInput = text.toLowerCase();
282
+ const fragmentTokenSet = new Set(entry.fragmentTokens);
283
+ const labelTokenSet = new Set(entry.labelTokens);
284
+ let fragmentTokenHits = 0;
285
+ let labelTokenHits = 0;
286
+ for (const token of queryTokens) {
287
+ if (fragmentTokenSet.has(token)) {
288
+ fragmentTokenHits += 1;
289
+ }
290
+ if (labelTokenSet.has(token)) {
291
+ labelTokenHits += 1;
292
+ }
293
+ }
294
+ return {
295
+ iri: entry.iri,
296
+ label: entry.label,
297
+ score: ranked.length - index,
298
+ diagnostics: {
299
+ fragment,
300
+ label: lowerLabel,
301
+ exactFragment: fragment === lowerInput,
302
+ exactLabel: lowerLabel === lowerInput,
303
+ containsInputInFragment: fragment.includes(lowerInput),
304
+ containsInputInLabel: lowerLabel.includes(lowerInput),
305
+ fragmentTokenHits,
306
+ labelTokenHits,
307
+ },
308
+ };
309
+ });
310
+ return { success: true, candidates };
311
+ } catch (error) {
312
+ logServerError('oml/fuzzysearch failed', error);
313
+ return { success: false, candidates: [], error: error instanceof Error ? error.message : String(error) };
314
+ }
315
+ });
316
+
317
+ connection.onRequest(ReasoningRequests.resolveModelUri, async (params: ResolveModelUriParams): Promise<ResolveModelUriResult> => {
318
+ await ensureWorkspaceIndexed(shared);
319
+ const ontologyIri = typeof params?.ontologyIri === 'string' ? params.ontologyIri.trim() : '';
320
+ if (!ontologyIri) {
321
+ return {};
322
+ }
323
+ const referencingUri = typeof params?.referencingUri === 'string'
324
+ ? params.referencingUri.trim()
325
+ : undefined;
326
+ const normalized = normalizeOntologyIri(ontologyIri);
327
+ const ontologyIndex = getOntologyModelIndex(shared as any);
328
+ const modelUri = ontologyIndex.resolveModelUri(normalized, referencingUri);
329
+ if (modelUri) {
330
+ await ensureResolvedModelDocumentLoaded(shared, modelUri);
331
+ }
332
+ return modelUri ? { modelUri } : {};
333
+ });
334
+
335
+ startLanguageServer(shared, {
336
+ CodeActionProvider: DocumentState.IndexedReferences
337
+ });
338
+ return { shared, Oml };
339
+ }
340
+
341
+ function normalizeOntologyIri(value: string): string {
342
+ return value.replace(/^<|>$/g, '').replace(/[\/#]+$/, '');
343
+ }
344
+
345
+ function isTransientDiagnosticsUri(uri: string): boolean {
346
+ try {
347
+ const scheme = URI.parse(uri).scheme;
348
+ return scheme === 'git'
349
+ || scheme === 'chat-editing-text-model'
350
+ || scheme === 'chat-editing-snapshot-text-model'
351
+ || scheme === 'vscode-chat-code-block';
352
+ } catch {
353
+ return false;
354
+ }
355
+ }
356
+
357
+ async function ensureWorkspaceIndexed(sharedServices: any): Promise<void> {
358
+ const key = sharedServices as object;
359
+ const existing = workspacePreloadState.get(key);
360
+ if (existing?.completed) {
361
+ return;
362
+ }
363
+ if (existing?.promise) {
364
+ await existing.promise;
365
+ return;
366
+ }
367
+ const state = existing ?? { completed: false };
368
+ const promise = (async () => {
369
+ const workspace = sharedServices.workspace.WorkspaceManager;
370
+ const documents = sharedServices.workspace.LangiumDocuments;
371
+ const builder = sharedServices.workspace.DocumentBuilder;
372
+ await workspace.ready;
373
+ const folderUris = workspace.workspaceFolders ?? [];
374
+ const omlUris = new Set<string>();
375
+ for (const folder of folderUris) {
376
+ const entries = await workspace.searchFolder(URI.parse(folder.uri));
377
+ for (const entry of entries) {
378
+ const entryUri = entry.toString();
379
+ if (entryUri.toLowerCase().endsWith('.oml')) {
380
+ omlUris.add(entryUri);
381
+ }
382
+ }
383
+ }
384
+ if (omlUris.size > 0) {
385
+ const docs = await Promise.all(
386
+ [...omlUris].map((uri) => documents.getOrCreateDocument(URI.parse(uri)))
387
+ );
388
+ await builder.build(docs, { validation: { categories: ['built-in', 'fast'] } });
389
+ }
390
+ state.completed = true;
391
+ })().finally(() => {
392
+ state.promise = undefined;
393
+ });
394
+ state.promise = promise;
395
+ workspacePreloadState.set(key, state);
396
+ await promise;
397
+ }
398
+
399
+ async function ensureResolvedModelDocumentLoaded(sharedServices: any, modelUri: string): Promise<void> {
400
+ try {
401
+ const documents = sharedServices.workspace.LangiumDocuments;
402
+ const uri = URI.parse(modelUri);
403
+ if (!documents.getDocument(uri)) {
404
+ await documents.getOrCreateDocument(uri);
405
+ }
406
+ } catch {
407
+ // Resolution must stay deterministic and non-throwing;
408
+ // loading failures are surfaced later by query/edit operations.
409
+ }
410
+ }
411
+
412
+ function toRowDto(row: Map<string, any>): Record<string, SparqlTermDto> {
413
+ const result: Record<string, SparqlTermDto> = {};
414
+ for (const [name, term] of row.entries()) {
415
+ result[name] = {
416
+ termType: term.termType,
417
+ value: term.value,
418
+ datatype: term.datatype,
419
+ language: term.language,
420
+ };
421
+ }
422
+ return result;
423
+ }
@@ -0,0 +1,21 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ export const FsRequests = {
4
+ readFile: 'oml/fs/readFile',
5
+ stat: 'oml/fs/stat',
6
+ readDirectory: 'oml/fs/readDirectory',
7
+ // Must match GET_WORKSPACE_FOLDERS_REQUEST in oml-workspace.ts
8
+ getWorkspaceFolders: 'oml/fs/getWorkspaceFolders'
9
+ } as const;
10
+
11
+ export type FsNodeType = 'file' | 'directory' | 'unknown';
12
+
13
+ export interface FsStatResult {
14
+ type: FsNodeType;
15
+ }
16
+
17
+ export interface FsDirectoryEntry {
18
+ uri: string;
19
+ type: FsNodeType;
20
+ }
21
+
@@ -0,0 +1,86 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ export const ReasoningNotifications = {
4
+ markDocumentActive: 'oml/reasoning/markDocumentActive',
5
+ markDocumentInactive: 'oml/reasoning/markDocumentInactive',
6
+ semanticChanged: 'oml/reasoning/semanticChanged',
7
+ } as const;
8
+
9
+ export const ReasoningRequests = {
10
+ sparqlQuery: 'oml/query',
11
+ fuzzySearch: 'oml/fuzzysearch',
12
+ resolveModelUri: 'oml/resolveModelUri',
13
+ } as const;
14
+
15
+ export interface ActiveDocumentParams {
16
+ modelUri: string;
17
+ }
18
+
19
+ export interface SemanticChangedParams {
20
+ modelUris: string[];
21
+ }
22
+
23
+ export interface ResolveModelUriParams {
24
+ ontologyIri: string;
25
+ referencingUri?: string;
26
+ }
27
+
28
+ export interface ResolveModelUriResult {
29
+ modelUri?: string;
30
+ }
31
+
32
+ export interface SparqlQueryParams {
33
+ modelUri: string;
34
+ sparql: string;
35
+ }
36
+
37
+ export interface FuzzySearchParams {
38
+ text: string;
39
+ limit?: number;
40
+ }
41
+
42
+ export interface FuzzySearchCandidate {
43
+ iri: string;
44
+ label?: string;
45
+ score: number;
46
+ diagnostics?: {
47
+ fragment: string;
48
+ label: string;
49
+ exactFragment: boolean;
50
+ exactLabel: boolean;
51
+ containsInputInFragment: boolean;
52
+ containsInputInLabel: boolean;
53
+ fragmentTokenHits: number;
54
+ labelTokenHits: number;
55
+ };
56
+ }
57
+
58
+ export interface FuzzySearchResult {
59
+ success: boolean;
60
+ candidates: FuzzySearchCandidate[];
61
+ error?: string;
62
+ }
63
+
64
+ export interface SparqlTermDto {
65
+ termType: 'NamedNode' | 'Literal' | 'BlankNode';
66
+ value: string;
67
+ datatype?: string;
68
+ language?: string;
69
+ }
70
+
71
+ export type SparqlRowDto = Record<string, SparqlTermDto>;
72
+
73
+ export interface SparqlQueryResult {
74
+ success: boolean;
75
+ kind: 'select' | 'ask' | 'construct' | 'describe' | 'unknown';
76
+ warnings: string[];
77
+ rows?: SparqlRowDto[];
78
+ result?: boolean;
79
+ quads?: Array<{
80
+ subject: string;
81
+ predicate: string;
82
+ object: string;
83
+ graph: string;
84
+ }>;
85
+ error?: string;
86
+ }
@@ -0,0 +1,85 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ import type { FileSystemNode, FileSystemProvider } from 'langium';
4
+ import { URI } from 'langium';
5
+ import type { Connection } from 'vscode-languageserver';
6
+ import { FsRequests, type FsDirectoryEntry, type FsStatResult } from '../protocol/browser-fs-protocol.js';
7
+
8
+ const textDecoder = new TextDecoder('utf-8');
9
+
10
+ class BrowserFileSystemProvider implements FileSystemProvider {
11
+ constructor(private readonly connection: Connection) {}
12
+
13
+ async stat(uri: URI): Promise<FileSystemNode> {
14
+ const result = await this.connection.sendRequest<FsStatResult | null>(FsRequests.stat, { uri: uri.toString() });
15
+ if (!result) {
16
+ throw new Error(`File not found: ${uri.toString()}`);
17
+ }
18
+ return {
19
+ uri,
20
+ isFile: result.type === 'file',
21
+ isDirectory: result.type === 'directory'
22
+ };
23
+ }
24
+
25
+ statSync(): FileSystemNode {
26
+ throw new Error('Synchronous file system access is unavailable in browser mode.');
27
+ }
28
+
29
+ async exists(uri: URI): Promise<boolean> {
30
+ try {
31
+ await this.stat(uri);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ existsSync(): boolean {
39
+ return false;
40
+ }
41
+
42
+ async readBinary(uri: URI): Promise<Uint8Array> {
43
+ const content = await this.readFile(uri);
44
+ return new TextEncoder().encode(content);
45
+ }
46
+
47
+ readBinarySync(): Uint8Array {
48
+ throw new Error('Synchronous file system access is unavailable in browser mode.');
49
+ }
50
+
51
+ async readFile(uri: URI): Promise<string> {
52
+ const content = await this.connection.sendRequest<string | Uint8Array | ArrayBuffer | null>(FsRequests.readFile, { uri: uri.toString() });
53
+ if (typeof content === 'string') {
54
+ return content;
55
+ }
56
+ if (content instanceof Uint8Array) {
57
+ return textDecoder.decode(content);
58
+ }
59
+ if (content instanceof ArrayBuffer) {
60
+ return textDecoder.decode(new Uint8Array(content));
61
+ }
62
+ throw new Error(`Unexpected readFile response for ${uri.toString()}`);
63
+ }
64
+
65
+ readFileSync(): string {
66
+ throw new Error('Synchronous file system access is unavailable in browser mode.');
67
+ }
68
+
69
+ async readDirectory(uri: URI): Promise<FileSystemNode[]> {
70
+ const entries = await this.connection.sendRequest<FsDirectoryEntry[]>(FsRequests.readDirectory, { uri: uri.toString() }) ?? [];
71
+ return entries.map((entry) => ({
72
+ uri: URI.parse(entry.uri),
73
+ isFile: entry.type === 'file',
74
+ isDirectory: entry.type === 'directory'
75
+ }));
76
+ }
77
+
78
+ readDirectorySync(): FileSystemNode[] {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ export const BrowserFileSystem = (connection: Connection) => ({
84
+ fileSystemProvider: () => new BrowserFileSystemProvider(connection)
85
+ });