33import cors from "cors" ;
44import { parseArgs } from "node:util" ;
55import { parse as shellParseArgs } from "shell-quote" ;
6+ import nodeFetch , { Headers as NodeHeaders } from "node-fetch" ;
7+
8+ // Type-compatible wrappers for node-fetch to work with browser-style types
9+ const fetch = nodeFetch ;
10+ const Headers = NodeHeaders ;
611
712import {
813 SSEClientTransport ,
@@ -231,13 +236,37 @@ const authMiddleware = (
231236 next ( ) ;
232237} ;
233238
239+ /**
240+ * Converts a Node.js ReadableStream to a web-compatible ReadableStream
241+ * This is necessary for the EventSource polyfill which expects web streams
242+ */
243+ const createWebReadableStream = ( nodeStream : any ) : ReadableStream => {
244+ return new ReadableStream ( {
245+ start ( controller ) {
246+ nodeStream . on ( "data" , ( chunk : any ) => {
247+ controller . enqueue ( chunk ) ;
248+ } ) ;
249+ nodeStream . on ( "end" , ( ) => {
250+ controller . close ( ) ;
251+ } ) ;
252+ nodeStream . on ( "error" , ( err : any ) => {
253+ controller . error ( err ) ;
254+ } ) ;
255+ } ,
256+ } ) ;
257+ } ;
258+
234259/**
235260 * Creates a `fetch` function that merges dynamic session headers with the
236261 * headers from the actual request, ensuring that request-specific headers like
237- * `Content-Type` are preserved.
262+ * `Content-Type` are preserved. For SSE requests, it also converts Node.js
263+ * streams to web-compatible streams.
238264 */
239265const createCustomFetch = ( headerHolder : { headers : HeadersInit } ) => {
240- return ( input : RequestInfo | URL , init ? : RequestInit ) : Promise < Response > => {
266+ return async (
267+ input : RequestInfo | URL ,
268+ init ?: RequestInit ,
269+ ) : Promise < Response > => {
241270 // Determine the headers from the original request/init.
242271 // The SDK may pass a Request object or a URL and an init object.
243272 const originalHeaders =
@@ -252,8 +281,43 @@ const createCustomFetch = (headerHolder: { headers: HeadersInit }) => {
252281 finalHeaders . set ( key , value ) ;
253282 } ) ;
254283
255- // This works for both `fetch(url, init)` and `fetch(request)` style calls.
256- return fetch ( input , { ...init , headers : finalHeaders } ) ;
284+ // Convert Headers to a plain object for node-fetch compatibility
285+ const headersObject : Record < string , string> = { } ;
286+ finalHeaders . forEach ( ( value , key ) => {
287+ headersObject [ key ] = value ;
288+ } ) ;
289+
290+ // Get the response from node-fetch (cast input and init to handle type differences)
291+ const response = await fetch (
292+ input as any ,
293+ { ...init , headers : headersObject } as any ,
294+ ) ;
295+
296+ // Check if this is an SSE request by looking at the Accept header
297+ const acceptHeader = finalHeaders . get ( "Accept" ) ;
298+ const isSSE = acceptHeader ?. includes ( "text/event-stream" ) ;
299+
300+ if ( isSSE && response . body ) {
301+ // For SSE requests, we need to convert the Node.js stream to a web ReadableStream
302+ // because the EventSource polyfill expects web-compatible streams
303+ const webStream = createWebReadableStream ( response . body ) ;
304+
305+ // Create a new response with the web-compatible stream
306+ // Convert node-fetch headers to plain object for web Response compatibility
307+ const responseHeaders : Record < string , string> = { } ;
308+ response . headers . forEach ( ( value : string , key : string ) => {
309+ responseHeaders [ key ] = value ;
310+ } ) ;
311+
312+ return new Response ( webStream , {
313+ status : response . status ,
314+ statusText : response . statusText ,
315+ headers : responseHeaders ,
316+ } ) as Response ;
317+ }
318+
319+ // For non-SSE requests, return the response as-is (cast to handle type differences)
320+ return response as unknown as Response ;
257321 } ;
258322} ;
259323
0 commit comments