monaco-lsp-bridge 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/lib/cjs/error.js +46 -0
- package/lib/cjs/error.js.map +1 -0
- package/lib/cjs/index.js +594 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/protocol.js +21 -0
- package/lib/cjs/protocol.js.map +1 -0
- package/lib/cjs/transport.js +269 -0
- package/lib/cjs/transport.js.map +1 -0
- package/lib/cjs/util.js +264 -0
- package/lib/cjs/util.js.map +1 -0
- package/lib/esm/error.js +40 -0
- package/lib/esm/error.js.map +1 -0
- package/lib/esm/index.js +576 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/protocol.js +15 -0
- package/lib/esm/protocol.js.map +1 -0
- package/lib/esm/transport.js +265 -0
- package/lib/esm/transport.js.map +1 -0
- package/lib/esm/util.js +252 -0
- package/lib/esm/util.js.map +1 -0
- package/lib/ts/error.d.ts +26 -0
- package/lib/ts/error.d.ts.map +1 -0
- package/lib/ts/index.d.ts +78 -0
- package/lib/ts/index.d.ts.map +1 -0
- package/lib/ts/protocol.d.ts +80 -0
- package/lib/ts/protocol.d.ts.map +1 -0
- package/lib/ts/transport.d.ts +74 -0
- package/lib/ts/transport.d.ts.map +1 -0
- package/lib/ts/util.d.ts +21 -0
- package/lib/ts/util.d.ts.map +1 -0
- package/package.json +68 -0
- package/src/error.ts +48 -0
- package/src/index.ts +709 -0
- package/src/protocol.ts +129 -0
- package/src/transport.ts +337 -0
- package/src/util.ts +300 -0
- package/tsconfig.json +49 -0
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DidChangeTextDocumentNotification,
|
|
3
|
+
DidChangeTextDocumentParams,
|
|
4
|
+
DidCloseTextDocumentParams,
|
|
5
|
+
DidCloseTextDocumentNotification,
|
|
6
|
+
DidOpenTextDocumentNotification,
|
|
7
|
+
DidOpenTextDocumentParams,
|
|
8
|
+
CompletionRequest,
|
|
9
|
+
CompletionParams,
|
|
10
|
+
InitializeRequest,
|
|
11
|
+
InitializeParams,
|
|
12
|
+
InitializeResult,
|
|
13
|
+
CompletionList,
|
|
14
|
+
CompletionItem,
|
|
15
|
+
CompletionResolveRequest,
|
|
16
|
+
ProgressToken,
|
|
17
|
+
PublishDiagnosticsNotification,
|
|
18
|
+
PublishDiagnosticsParams,
|
|
19
|
+
LogMessageNotification,
|
|
20
|
+
LogMessageParams,
|
|
21
|
+
DocumentFormattingRequest,
|
|
22
|
+
DocumentFormattingParams,
|
|
23
|
+
DocumentRangeFormattingRequest,
|
|
24
|
+
DocumentRangeFormattingParams,
|
|
25
|
+
DocumentOnTypeFormattingRequest,
|
|
26
|
+
DocumentOnTypeFormattingParams,
|
|
27
|
+
TextEdit,
|
|
28
|
+
HoverRequest,
|
|
29
|
+
HoverParams,
|
|
30
|
+
Hover,
|
|
31
|
+
} from 'vscode-languageserver-protocol'
|
|
32
|
+
|
|
33
|
+
import { LspErrorShape } from './error.js'
|
|
34
|
+
|
|
35
|
+
export type Id = number | string
|
|
36
|
+
export const isId = (id: unknown): id is Id => typeof id === 'number' || typeof id === 'string'
|
|
37
|
+
|
|
38
|
+
// Requests (bidirectional, have responses)
|
|
39
|
+
export interface RequestMap {
|
|
40
|
+
[InitializeRequest.method]: [InitializeParams, InitializeResult]
|
|
41
|
+
[CompletionResolveRequest.method]: [CompletionItem, CompletionItem]
|
|
42
|
+
[CompletionRequest.method]: [CompletionParams, CompletionList | CompletionItem[] | null]
|
|
43
|
+
[DocumentFormattingRequest.method]: [DocumentFormattingParams, TextEdit[] | null]
|
|
44
|
+
[DocumentRangeFormattingRequest.method]: [DocumentRangeFormattingParams, TextEdit[] | null]
|
|
45
|
+
[DocumentOnTypeFormattingRequest.method]: [DocumentOnTypeFormattingParams, TextEdit[] | null]
|
|
46
|
+
[HoverRequest.method]: [HoverParams, Hover | null]
|
|
47
|
+
shutdown: [null, null]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Client-to-server notifications (no response expected)
|
|
51
|
+
export interface ClientNotifMap {
|
|
52
|
+
[DidOpenTextDocumentNotification.method]: [DidOpenTextDocumentParams, void]
|
|
53
|
+
[DidChangeTextDocumentNotification.method]: [DidChangeTextDocumentParams, void]
|
|
54
|
+
[DidCloseTextDocumentNotification.method]: [DidCloseTextDocumentParams, void]
|
|
55
|
+
'$/cancelRequest': [{ id: Id }, void]
|
|
56
|
+
exit: [null, void]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Server-to-client notifications (no response expected)
|
|
60
|
+
export interface ServerNotifMap {
|
|
61
|
+
'$/progress': [{ token: ProgressToken; value: any }, void]
|
|
62
|
+
[PublishDiagnosticsNotification.method]: [PublishDiagnosticsParams, void]
|
|
63
|
+
[LogMessageNotification.method]: [LogMessageParams, void]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const JSONRPC_VERSION = '2.0'
|
|
67
|
+
type JSONRPC = typeof JSONRPC_VERSION
|
|
68
|
+
|
|
69
|
+
export type JsonRpcRequest<K, T> = {
|
|
70
|
+
jsonrpc: JSONRPC
|
|
71
|
+
id: Id
|
|
72
|
+
params: T
|
|
73
|
+
method: K
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type JsonRpcResponse<T> =
|
|
77
|
+
| { jsonrpc: JSONRPC; id: Id; result: T; error?: undefined }
|
|
78
|
+
| { jsonrpc: JSONRPC; id: Id; result?: undefined; error: LspErrorShape }
|
|
79
|
+
|
|
80
|
+
export type JsonRpcNotification<K, T> = {
|
|
81
|
+
jsonrpc: JSONRPC
|
|
82
|
+
method: K
|
|
83
|
+
params: T
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const makeRequest = <K extends keyof RequestMap>(
|
|
87
|
+
id: Id,
|
|
88
|
+
method: K,
|
|
89
|
+
params: RequestMap[K][0],
|
|
90
|
+
): JsonRpcRequest<K, RequestMap[K][0]> => ({
|
|
91
|
+
jsonrpc: JSONRPC_VERSION,
|
|
92
|
+
id,
|
|
93
|
+
method,
|
|
94
|
+
params,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export const makeNotification = <K extends keyof ClientNotifMap>(
|
|
98
|
+
method: K,
|
|
99
|
+
params: ClientNotifMap[K][0],
|
|
100
|
+
): JsonRpcNotification<K, ClientNotifMap[K][0]> => ({
|
|
101
|
+
jsonrpc: JSONRPC_VERSION,
|
|
102
|
+
method,
|
|
103
|
+
params,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
export type ProgressPayload<T = any> = {
|
|
107
|
+
token: ProgressToken
|
|
108
|
+
value: T
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type WorkDoneProgressBegin = {
|
|
112
|
+
kind: 'begin'
|
|
113
|
+
title: string
|
|
114
|
+
cancellable?: boolean
|
|
115
|
+
message?: string
|
|
116
|
+
percentage?: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type WorkDoneProgressReport = {
|
|
120
|
+
kind: 'report'
|
|
121
|
+
cancellable?: boolean
|
|
122
|
+
message?: string
|
|
123
|
+
percentage?: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type WorkDoneProgressEnd = {
|
|
127
|
+
kind: 'end'
|
|
128
|
+
message?: string
|
|
129
|
+
}
|
package/src/transport.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type { ProgressToken } from 'vscode-languageserver-protocol'
|
|
2
|
+
import type * as monaco from 'monaco-editor'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
JSONRPC_VERSION,
|
|
6
|
+
makeRequest,
|
|
7
|
+
makeNotification,
|
|
8
|
+
JsonRpcRequest,
|
|
9
|
+
RequestMap,
|
|
10
|
+
ClientNotifMap,
|
|
11
|
+
ServerNotifMap,
|
|
12
|
+
JsonRpcResponse,
|
|
13
|
+
JsonRpcNotification,
|
|
14
|
+
ProgressPayload,
|
|
15
|
+
Id,
|
|
16
|
+
isId,
|
|
17
|
+
} from './protocol.js'
|
|
18
|
+
import { LspError, LspErrorCode, toLspError, isCancellationError } from './error.js'
|
|
19
|
+
|
|
20
|
+
export type Transport = PostMessagePort | LspTransport
|
|
21
|
+
|
|
22
|
+
export interface PostMessagePort {
|
|
23
|
+
postMessage(message: any): void
|
|
24
|
+
addEventListener(type: 'message', listener: (event: { data: any }) => void): void
|
|
25
|
+
removeEventListener(type: 'message', listener: (event: { data: any }) => void): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Disposable = () => void
|
|
29
|
+
|
|
30
|
+
export type Cancelled = {
|
|
31
|
+
cancelled: true
|
|
32
|
+
code: LspErrorCode.RequestCancelled | LspErrorCode.ServerCancelled
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type MaybeCancelled<T> = { cancelled: false; result: T } | Cancelled
|
|
36
|
+
|
|
37
|
+
export type ProgressHandler<T = any> = (value: T) => void
|
|
38
|
+
export type PartialResultHandler<T = any> = (partialResult: T) => void
|
|
39
|
+
|
|
40
|
+
export class LspTransport {
|
|
41
|
+
private nextId = 1
|
|
42
|
+
private nextTokenId = 1
|
|
43
|
+
private disposables: Set<Disposable> = new Set()
|
|
44
|
+
|
|
45
|
+
// Fire-and-forget requests handled by withHandlers()
|
|
46
|
+
private pending = new Map<Id, JsonRpcRequest<keyof RequestMap, any>>()
|
|
47
|
+
|
|
48
|
+
// Async bookkeeping
|
|
49
|
+
private timeouts = new Map<Id, ReturnType<typeof setTimeout>>()
|
|
50
|
+
private rejectors = new Map<Id, (err: LspError) => void>()
|
|
51
|
+
|
|
52
|
+
// Central dispatcher: subscribers + per-id waiters (for sendAsync)
|
|
53
|
+
private subs = new Set<(message: any) => void>()
|
|
54
|
+
private waiters = new Map<Id, (res: JsonRpcResponse<any>) => void>()
|
|
55
|
+
|
|
56
|
+
// Progress tracking (both work done progress and partial results use $/progress)
|
|
57
|
+
private progressCallbacks = new Map<ProgressToken, ProgressHandler>()
|
|
58
|
+
|
|
59
|
+
/** Construct a binding over a sender/receiver transport */
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly sender: (
|
|
62
|
+
message: JsonRpcRequest<keyof RequestMap, any> | JsonRpcNotification<any, any> | Array<any>,
|
|
63
|
+
) => void,
|
|
64
|
+
private readonly receiver: (cb: (message: any) => void) => Disposable,
|
|
65
|
+
private readonly defaultTimeoutMs: number = 15_000,
|
|
66
|
+
) {
|
|
67
|
+
// One receiver to rule them all — handles single or batch frames.
|
|
68
|
+
const unbind = this.receiver((raw) => this.ingest(raw))
|
|
69
|
+
this.disposables.add(unbind)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------- Constructors ----------------
|
|
73
|
+
/** Create a binding from a Worker-like endpoint */
|
|
74
|
+
static fromWorker(worker: PostMessagePort): LspTransport {
|
|
75
|
+
const sender = (message: any) => worker.postMessage(message)
|
|
76
|
+
const receiver = (cb: (message: any) => void) => {
|
|
77
|
+
const handler = (e: { data: any }) => cb(e.data)
|
|
78
|
+
worker.addEventListener('message', handler)
|
|
79
|
+
return () => worker.removeEventListener('message', handler)
|
|
80
|
+
}
|
|
81
|
+
return new LspTransport(sender, receiver)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Infer a binding from either a worker or an existing binding */
|
|
85
|
+
static infer(endpoint: Transport): LspTransport {
|
|
86
|
+
return endpoint instanceof LspTransport ? endpoint : LspTransport.fromWorker(endpoint)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -------- Helpers
|
|
90
|
+
/** Track a Monaco disposable for cleanup */
|
|
91
|
+
addDisposable(d: monaco.IDisposable) {
|
|
92
|
+
this.disposables.add(() => d.dispose())
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Listen for error responses and forward normalized errors */
|
|
97
|
+
onError(cb: (error: LspError) => void) {
|
|
98
|
+
const onMessage = (res: JsonRpcResponse<any>) => {
|
|
99
|
+
if (res?.jsonrpc !== JSONRPC_VERSION) return
|
|
100
|
+
if (res?.error) cb(toLspError(res.error))
|
|
101
|
+
}
|
|
102
|
+
this.disposables.add(this.subscribe(onMessage))
|
|
103
|
+
return this
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Register a progress callback for a specific token */
|
|
107
|
+
onProgress(token: ProgressToken, callback: ProgressHandler): Disposable {
|
|
108
|
+
this.progressCallbacks.set(token, callback)
|
|
109
|
+
return () => this.progressCallbacks.delete(token)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Register a callback for all $/progress notifications */
|
|
113
|
+
onProgressNotification(callback: (params: ProgressPayload) => void) {
|
|
114
|
+
const onMessage = (msg: any) => {
|
|
115
|
+
if (msg?.jsonrpc !== JSONRPC_VERSION || msg?.method !== '$/progress') return
|
|
116
|
+
if (msg.params) callback(msg.params)
|
|
117
|
+
}
|
|
118
|
+
this.disposables.add(this.subscribe(onMessage))
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Listen for a specific server notification method */
|
|
123
|
+
onServerNotification<K extends keyof ServerNotifMap>(method: K, cb: (params: ServerNotifMap[K][0]) => void) {
|
|
124
|
+
const onMessage = (msg: any) => {
|
|
125
|
+
if (msg?.jsonrpc !== JSONRPC_VERSION || msg?.method !== method) return
|
|
126
|
+
cb(msg.params as ServerNotifMap[K][0])
|
|
127
|
+
}
|
|
128
|
+
this.disposables.add(this.subscribe(onMessage))
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Dispose all resources and reject in-flight requests */
|
|
133
|
+
dispose() {
|
|
134
|
+
for (const rej of this.rejectors.values()) {
|
|
135
|
+
try {
|
|
136
|
+
rej(new LspError({ code: LspErrorCode.Disposed, message: 'Binding disposed before response' }))
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
this.rejectors.clear()
|
|
140
|
+
|
|
141
|
+
for (const t of this.timeouts.values()) clearTimeout(t)
|
|
142
|
+
this.timeouts.clear()
|
|
143
|
+
|
|
144
|
+
this.disposables.forEach((d) => d())
|
|
145
|
+
this.disposables.clear()
|
|
146
|
+
this.pending.clear()
|
|
147
|
+
this.waiters.clear()
|
|
148
|
+
this.subs.clear()
|
|
149
|
+
this.progressCallbacks.clear()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// -------- Core API
|
|
153
|
+
|
|
154
|
+
/** Send a client-to-server notification (no response expected) */
|
|
155
|
+
sendNotification<K extends keyof ClientNotifMap>(method: K, params: ClientNotifMap[K][0]) {
|
|
156
|
+
this.sender(makeNotification(method, params))
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Send an async request with timeout, cancellation and progress support */
|
|
161
|
+
sendRequest<K extends keyof RequestMap>(
|
|
162
|
+
method: K,
|
|
163
|
+
params: RequestMap[K][0],
|
|
164
|
+
opts?: {
|
|
165
|
+
timeoutMs?: number
|
|
166
|
+
signal?: AbortSignal
|
|
167
|
+
workDoneToken?: ProgressToken
|
|
168
|
+
partialResultToken?: ProgressToken
|
|
169
|
+
onProgress?: ProgressHandler
|
|
170
|
+
onPartialResult?: PartialResultHandler
|
|
171
|
+
},
|
|
172
|
+
): Promise<MaybeCancelled<RequestMap[K][1]>> {
|
|
173
|
+
const id = this.nextId++
|
|
174
|
+
|
|
175
|
+
// Generate tokens if callbacks provided but tokens not specified
|
|
176
|
+
const workDoneToken = opts?.workDoneToken ?? (opts?.onProgress ? `work-${this.nextTokenId++}` : undefined)
|
|
177
|
+
const partialResultToken =
|
|
178
|
+
opts?.partialResultToken ?? (opts?.onPartialResult ? `partial-${this.nextTokenId++}` : undefined)
|
|
179
|
+
|
|
180
|
+
// Augment params with tokens if provided
|
|
181
|
+
const augmentedParams: any = { ...params }
|
|
182
|
+
if (workDoneToken !== undefined) {
|
|
183
|
+
augmentedParams.workDoneToken = workDoneToken
|
|
184
|
+
}
|
|
185
|
+
if (partialResultToken !== undefined) {
|
|
186
|
+
augmentedParams.partialResultToken = partialResultToken
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const m = makeRequest(id, method, augmentedParams)
|
|
190
|
+
|
|
191
|
+
if (opts?.signal?.aborted) {
|
|
192
|
+
return Promise.resolve({ cancelled: true as const, code: LspErrorCode.RequestCancelled })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Register progress callbacks if provided
|
|
196
|
+
// Both work done progress and partial results use $/progress notifications
|
|
197
|
+
if (workDoneToken !== undefined && opts?.onProgress) {
|
|
198
|
+
this.progressCallbacks.set(workDoneToken, opts.onProgress)
|
|
199
|
+
}
|
|
200
|
+
if (partialResultToken !== undefined && opts?.onPartialResult) {
|
|
201
|
+
this.progressCallbacks.set(partialResultToken, opts.onPartialResult)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.sender(m)
|
|
205
|
+
|
|
206
|
+
return new Promise<MaybeCancelled<RequestMap[K][1]>>((resolve, reject) => {
|
|
207
|
+
const resolveCancelled = (code: LspErrorCode.RequestCancelled | LspErrorCode.ServerCancelled) => {
|
|
208
|
+
cleanup()
|
|
209
|
+
resolve({ cancelled: true, code })
|
|
210
|
+
}
|
|
211
|
+
const rejectAs = (err: LspError) => {
|
|
212
|
+
cleanup()
|
|
213
|
+
reject(err)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const onResponse = (res: JsonRpcResponse<any>) => {
|
|
217
|
+
if (res?.id !== id) return
|
|
218
|
+
if (res.error) {
|
|
219
|
+
const err = toLspError(res.error)
|
|
220
|
+
if (isCancellationError(err)) return resolveCancelled(err.code as any)
|
|
221
|
+
return rejectAs(err)
|
|
222
|
+
}
|
|
223
|
+
cleanup()
|
|
224
|
+
resolve({ cancelled: false, result: res.result })
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// route by id via central dispatcher
|
|
228
|
+
this.waiters.set(id, onResponse)
|
|
229
|
+
|
|
230
|
+
const cleanup = () => {
|
|
231
|
+
this.waiters.delete(id)
|
|
232
|
+
const t = this.timeouts.get(id)
|
|
233
|
+
if (t) clearTimeout(t)
|
|
234
|
+
this.timeouts.delete(id)
|
|
235
|
+
this.rejectors.delete(id)
|
|
236
|
+
// Clean up progress callbacks (on completion, cancellation, or error)
|
|
237
|
+
// Per LSP spec: workDoneToken is only valid until response/cancellation
|
|
238
|
+
if (workDoneToken !== undefined) {
|
|
239
|
+
this.progressCallbacks.delete(workDoneToken)
|
|
240
|
+
}
|
|
241
|
+
if (partialResultToken !== undefined) {
|
|
242
|
+
this.progressCallbacks.delete(partialResultToken)
|
|
243
|
+
}
|
|
244
|
+
if (abortCleanup) opts?.signal?.removeEventListener('abort', abortCleanup)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Timeout
|
|
248
|
+
const timeoutMs = opts?.timeoutMs ?? this.defaultTimeoutMs
|
|
249
|
+
if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {
|
|
250
|
+
const t = setTimeout(() => {
|
|
251
|
+
rejectAs(
|
|
252
|
+
new LspError({
|
|
253
|
+
code: LspErrorCode.Timeout,
|
|
254
|
+
message: `Request timed out: ${String(method)} (${id}) after ${timeoutMs}ms`,
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
}, timeoutMs)
|
|
258
|
+
this.timeouts.set(id, t)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Allow dispose() to reject in-flight requests
|
|
262
|
+
this.rejectors.set(id, rejectAs)
|
|
263
|
+
|
|
264
|
+
// AbortSignal → send $/cancelRequest and resolve immediately (eager cancel)
|
|
265
|
+
// Don't wait for server response - some servers don't echo promptly
|
|
266
|
+
let abortCleanup: (() => void) | undefined
|
|
267
|
+
if (opts?.signal) {
|
|
268
|
+
const onAbort = () => {
|
|
269
|
+
// Notify server of cancellation (best effort)
|
|
270
|
+
this.sendNotification('$/cancelRequest', { id })
|
|
271
|
+
// Resolve immediately for better UX (don't wait for server acknowledgement)
|
|
272
|
+
resolveCancelled(LspErrorCode.RequestCancelled)
|
|
273
|
+
}
|
|
274
|
+
opts.signal.addEventListener('abort', onAbort, { once: true })
|
|
275
|
+
abortCleanup = () => opts.signal?.removeEventListener('abort', onAbort)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Subscribe to all inbound JSON-RPC messages */
|
|
281
|
+
private subscribe(cb: (message: any) => void): Disposable {
|
|
282
|
+
this.subs.add(cb)
|
|
283
|
+
return () => this.subs.delete(cb)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Ingest an incoming frame and dispatch to listeners/waiters */
|
|
287
|
+
private ingest = (raw: any) => {
|
|
288
|
+
// Batch: process each frame independently
|
|
289
|
+
if (Array.isArray(raw)) {
|
|
290
|
+
for (const frame of raw) this.ingest(frame)
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Early drop anything that isn't a proper JSON-RPC 2.0 message
|
|
295
|
+
if (!raw || typeof raw !== 'object' || raw.jsonrpc !== JSONRPC_VERSION) return
|
|
296
|
+
|
|
297
|
+
// Handle $/progress notifications
|
|
298
|
+
if (raw.method === '$/progress' && raw.params) {
|
|
299
|
+
const params = raw.params as ProgressPayload
|
|
300
|
+
const callback = this.progressCallbacks.get(params.token)
|
|
301
|
+
if (callback) {
|
|
302
|
+
try {
|
|
303
|
+
callback(params.value)
|
|
304
|
+
} catch {}
|
|
305
|
+
|
|
306
|
+
// Auto-remove workDoneToken callbacks when receiving WorkDoneProgressEnd
|
|
307
|
+
if (params.value && typeof params.value === 'object' && params.value.kind === 'end') {
|
|
308
|
+
this.progressCallbacks.delete(params.token)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Still fan out to subscribers for withProgress handlers
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// If it's a response with an id and a waiting promise, resolve that first
|
|
315
|
+
if (Object.prototype.hasOwnProperty.call(raw, 'id')) {
|
|
316
|
+
const id = raw.id as Id
|
|
317
|
+
if (isId(id)) {
|
|
318
|
+
const waiter = this.waiters.get(id)
|
|
319
|
+
if (waiter) {
|
|
320
|
+
// Claim and stop propagation to generic handlers
|
|
321
|
+
this.waiters.delete(id)
|
|
322
|
+
try {
|
|
323
|
+
waiter(raw)
|
|
324
|
+
} catch {}
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Fan-out to generic subscribers (e.g. withHandlers / withError / withProgress)
|
|
331
|
+
for (const cb of Array.from(this.subs)) {
|
|
332
|
+
try {
|
|
333
|
+
cb(raw)
|
|
334
|
+
} catch {}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|