@@ -73,6 +73,8 @@ const errorWithClassificationSchema = z.object({
7373
7474type MessageCallback = ( message : unknown ) => void ;
7575
76+ export const SSE_KEEPALIVE_INTERVAL_MS = 25_000 ;
77+
7678class NdJsonTap {
7779 private decoder = new TextDecoder ( ) ;
7880 private buffer = "" ;
@@ -329,41 +331,73 @@ export class AgentServer {
329331 ) ;
330332 }
331333
334+ let keepaliveInterval : ReturnType < typeof setInterval > | null = null ;
335+ const clearKeepalive = ( ) : void => {
336+ if ( keepaliveInterval ) {
337+ clearInterval ( keepaliveInterval ) ;
338+ keepaliveInterval = null ;
339+ }
340+ } ;
341+
332342 const stream = new ReadableStream ( {
333343 start : async ( controller ) => {
334- const sseController : SseController = {
344+ let sseController : SseController | null = null ;
345+ const encoder = new TextEncoder ( ) ;
346+ const detachCurrentSseController = ( ) : void => {
347+ if ( sseController ) {
348+ this . detachSseController ( sseController ) ;
349+ }
350+ } ;
351+ const enqueueSseFrame = ( frame : string ) : void => {
352+ try {
353+ controller . enqueue ( encoder . encode ( frame ) ) ;
354+ } catch {
355+ clearKeepalive ( ) ;
356+ detachCurrentSseController ( ) ;
357+ }
358+ } ;
359+
360+ sseController = {
335361 send : ( data : unknown ) => {
336- try {
337- controller . enqueue (
338- new TextEncoder ( ) . encode ( `data: ${ JSON . stringify ( data ) } \n\n` ) ,
339- ) ;
340- } catch {
341- this . detachSseController ( sseController ) ;
342- }
362+ enqueueSseFrame ( `data: ${ JSON . stringify ( data ) } \n\n` ) ;
343363 } ,
344364 close : ( ) => {
345365 try {
366+ clearKeepalive ( ) ;
346367 controller . close ( ) ;
347368 } catch {
348- this . detachSseController ( sseController ) ;
369+ detachCurrentSseController ( ) ;
349370 }
350371 } ,
351372 } ;
352373
353- if ( ! this . session || this . session . payload . run_id !== payload . run_id ) {
354- await this . initializeSession ( payload , sseController ) ;
355- } else {
356- this . session . sseController = sseController ;
357- this . session . hasDesktopConnected = true ;
358- this . replayPendingEvents ( ) ;
359- }
374+ keepaliveInterval = setInterval ( ( ) => {
375+ enqueueSseFrame ( ": keepalive\n\n" ) ;
376+ } , SSE_KEEPALIVE_INTERVAL_MS ) ;
377+
378+ try {
379+ if (
380+ ! this . session ||
381+ this . session . payload . run_id !== payload . run_id
382+ ) {
383+ await this . initializeSession ( payload , sseController ) ;
384+ } else {
385+ this . session . sseController = sseController ;
386+ this . session . hasDesktopConnected = true ;
387+ this . replayPendingEvents ( ) ;
388+ }
360389
361- this . sendSseEvent ( sseController , {
362- type : "connected" ,
363- run_id : payload . run_id ,
364- } ) ;
390+ this . sendSseEvent ( sseController , {
391+ type : "connected" ,
392+ run_id : payload . run_id ,
393+ } ) ;
394+ } catch ( error ) {
395+ clearKeepalive ( ) ;
396+ throw error ;
397+ }
365398 } ,
366399 cancel : ( ) => {
400+ clearKeepalive ( ) ;
367401 this . logger . debug ( "SSE connection closed" ) ;
368402 if ( this . session ?. sseController ) {
369403 this . session . sseController = null ;
0 commit comments