@@ -2,7 +2,7 @@ import { Transport } from "../shared/transport.js";
2
2
import { JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
3
3
import { auth , AuthResult , OAuthClientProvider , UnauthorizedError } from "./auth.js" ;
4
4
import { type ErrorEvent } from "eventsource" ;
5
-
5
+ import { EventSourceParserStream } from 'eventsource-parser/stream' ;
6
6
export class StreamableHTTPError extends Error {
7
7
constructor (
8
8
public readonly code : number | undefined ,
@@ -45,7 +45,7 @@ export type StreamableHTTPClientTransportOptions = {
45
45
* for receiving messages.
46
46
*/
47
47
export class StreamableHTTPClientTransport implements Transport {
48
- private _activeStreams : Map < string , ReadableStreamDefaultReader < Uint8Array > > = new Map ( ) ;
48
+ private _activeStreams : Map < string , ReadableStreamDefaultReader < any > > = new Map ( ) ;
49
49
private _abortController ?: AbortController ;
50
50
private _url : URL ;
51
51
private _requestInit ?: RequestInit ;
@@ -186,30 +186,6 @@ export class StreamableHTTPClientTransport implements Transport {
186
186
// Abort any pending requests
187
187
this . _abortController ?. abort ( ) ;
188
188
189
- // If we have a session ID, send a DELETE request to explicitly terminate the session
190
- if ( this . _sessionId ) {
191
- try {
192
- const commonHeaders = await this . _commonHeaders ( ) ;
193
- const response = await fetch ( this . _url , {
194
- method : "DELETE" ,
195
- headers : commonHeaders ,
196
- signal : this . _abortController ?. signal ,
197
- } ) ;
198
-
199
- if ( ! response . ok ) {
200
- // Server might respond with 405 if it doesn't support explicit session termination
201
- // We don't throw an error in that case
202
- if ( response . status !== 405 ) {
203
- const text = await response . text ( ) . catch ( ( ) => null ) ;
204
- throw new Error ( `Error terminating session (HTTP ${ response . status } ): ${ text } ` ) ;
205
- }
206
- }
207
- } catch ( error ) {
208
- // We still want to invoke onclose even if the session termination fails
209
- this . onerror ?.( error as Error ) ;
210
- }
211
- }
212
-
213
189
this . onclose ?.( ) ;
214
190
}
215
191
@@ -300,62 +276,36 @@ export class StreamableHTTPClientTransport implements Transport {
300
276
return ;
301
277
}
302
278
303
- // Set up stream handling for server-sent events
304
- const reader = stream . getReader ( ) ;
279
+ // Create a pipeline: binary stream -> text decoder -> SSE parser
280
+ const eventStream = stream
281
+ . pipeThrough ( new TextDecoderStream ( ) )
282
+ . pipeThrough ( new EventSourceParserStream ( ) ) ;
283
+
284
+ const reader = eventStream . getReader ( ) ;
305
285
this . _activeStreams . set ( streamId , reader ) ;
306
- const decoder = new TextDecoder ( ) ;
307
- let buffer = '' ;
308
286
309
287
const processStream = async ( ) => {
310
288
try {
311
289
while ( true ) {
312
- const { done, value } = await reader . read ( ) ;
290
+ const { done, value : event } = await reader . read ( ) ;
313
291
if ( done ) {
314
- // Stream closed by server
315
292
this . _activeStreams . delete ( streamId ) ;
316
293
break ;
317
294
}
318
295
319
- buffer += decoder . decode ( value , { stream : true } ) ;
320
-
321
- // Process SSE messages in the buffer
322
- const events = buffer . split ( '\n\n' ) ;
323
- buffer = events . pop ( ) || '' ;
324
-
325
- for ( const event of events ) {
326
- const lines = event . split ( '\n' ) ;
327
- let id : string | undefined ;
328
- let eventType : string | undefined ;
329
- let data : string | undefined ;
330
-
331
- // Parse SSE message according to the format
332
- for ( const line of lines ) {
333
- if ( line . startsWith ( 'id:' ) ) {
334
- id = line . slice ( 3 ) . trim ( ) ;
335
- } else if ( line . startsWith ( 'event:' ) ) {
336
- eventType = line . slice ( 6 ) . trim ( ) ;
337
- } else if ( line . startsWith ( 'data:' ) ) {
338
- data = line . slice ( 5 ) . trim ( ) ;
339
- }
340
- }
341
-
342
- // Update last event ID if provided by server
343
- // As per spec: the ID MUST be globally unique across all streams within that session
344
- if ( id ) {
345
- this . _lastEventId = id ;
346
- }
296
+ // Update last event ID if provided
297
+ if ( event . id ) {
298
+ this . _lastEventId = event . id ;
299
+ }
347
300
348
- // Handle message event
349
- if ( data ) {
350
- // Default event type is 'message' per SSE spec if not specified
351
- if ( ! eventType || eventType === 'message' ) {
352
- try {
353
- const message = JSONRPCMessageSchema . parse ( JSON . parse ( data ) ) ;
354
- this . onmessage ?.( message ) ;
355
- } catch ( error ) {
356
- this . onerror ?.( error as Error ) ;
357
- }
358
- }
301
+ // Handle message events (default event type is undefined per docs)
302
+ // or explicit 'message' event type
303
+ if ( ! event . event || event . event === 'message' ) {
304
+ try {
305
+ const message = JSONRPCMessageSchema . parse ( JSON . parse ( event . data ) ) ;
306
+ this . onmessage ?.( message ) ;
307
+ } catch ( error ) {
308
+ this . onerror ?.( error as Error ) ;
359
309
}
360
310
}
361
311
}
0 commit comments