@@ -49,6 +49,7 @@ const LK_GOOGLE_DEBUG = Number(process.env.LK_GOOGLE_DEBUG ?? 0);
4949
5050// WebSocket close codes (RFC 6455)
5151const WS_CLOSE_NORMAL = 1000 ;
52+ const WS_CLOSE_CONTEXT_EXHAUSTED = 1007 ;
5253/**
5354 * Default image encoding options for Google Realtime API
5455 */
@@ -465,6 +466,7 @@ export class RealtimeSession extends llm.RealtimeSession {
465466 private inUserActivity = false ;
466467 private sessionLock = new Mutex ( ) ;
467468 private numRetries = 0 ;
469+ private sessionError ?: Error ;
468470 private hasReceivedAudioInput = false ;
469471 private pendingInterruptText = false ;
470472 private earlyCompletionPending = false ;
@@ -549,6 +551,20 @@ export class RealtimeSession extends llm.RealtimeSession {
549551 }
550552 }
551553
554+ private toError ( error : unknown ) : Error {
555+ return error instanceof Error ? error : new Error ( String ( error ) ) ;
556+ }
557+
558+ private isContextExhaustedError ( error : unknown ) : boolean {
559+ return (
560+ ( typeof error === 'object' &&
561+ error !== null &&
562+ 'statusCode' in error &&
563+ error . statusCode === WS_CLOSE_CONTEXT_EXHAUSTED ) ||
564+ String ( error ) . includes ( String ( WS_CLOSE_CONTEXT_EXHAUSTED ) )
565+ ) ;
566+ }
567+
552568 private isNonBlockingToolBehavior ( ) : boolean {
553569 return this . options . toolBehavior === types . Behavior . NON_BLOCKING ;
554570 }
@@ -1012,19 +1028,23 @@ export class RealtimeSession extends llm.RealtimeSession {
10121028 const errorMsg = event . reason || `WebSocket closed with code ${ event . code } ` ;
10131029 this . #logger. error ( `Gemini Live session error: ${ errorMsg } ${ truncationNote } ` ) ;
10141030
1015- this . emitError (
1016- new APIStatusError ( {
1017- message : `${ errorMsg } ${ truncationNote } ` ,
1018- options : {
1019- statusCode : event . code ,
1020- retryable : false ,
1021- body : event . reason
1022- ? { reason : event . reason , code : event . code , truncated : isTruncated }
1023- : null ,
1024- } ,
1025- } ) ,
1026- false ,
1027- ) ;
1031+ const error = new APIStatusError ( {
1032+ message : `${ errorMsg } ${ truncationNote } ` ,
1033+ options : {
1034+ statusCode : event . code ,
1035+ retryable : false ,
1036+ body : event . reason
1037+ ? { reason : event . reason , code : event . code , truncated : isTruncated }
1038+ : null ,
1039+ } ,
1040+ } ) ;
1041+
1042+ if ( event . code === WS_CLOSE_CONTEXT_EXHAUSTED ) {
1043+ this . sessionError = error ;
1044+ this . markRestartNeeded ( ) ;
1045+ } else {
1046+ this . emitError ( error , false ) ;
1047+ }
10281048 } else {
10291049 this . #logger. debug ( 'Gemini Live session closed:' , event . code , event . reason ) ;
10301050 }
@@ -1073,20 +1093,41 @@ export class RealtimeSession extends llm.RealtimeSession {
10731093 }
10741094
10751095 await cancelAndWait ( [ sendTask , restartWaitTask ] , 2000 ) ;
1096+
1097+ if ( this . sessionError ) {
1098+ const error = this . sessionError ;
1099+ this . sessionError = undefined ;
1100+ throw error ;
1101+ }
10761102 } catch ( error ) {
1077- this . #logger. error ( `Gemini Realtime API error: ${ error } ` ) ;
1103+ const err = this . toError ( error ) ;
1104+ this . #logger. error ( `Gemini Realtime API error: ${ err } ` ) ;
10781105
10791106 if ( this . #closed) break ;
10801107
1108+ // Gemini Live closes with 1007 when the session context is exhausted. Reconnecting
1109+ // would replay the same oversized context and fail again, so terminate the session.
1110+ if ( this . isContextExhaustedError ( err ) ) {
1111+ this . #logger. error (
1112+ err ,
1113+ 'Gemini Live closed the session: context exhausted (1007). Reconnecting would replay the same context and fail again; terminating the session.' ,
1114+ ) ;
1115+ this . emitError ( err , false ) ;
1116+ throw new APIConnectionError ( {
1117+ message : 'Gemini Live session context exhausted (1007)' ,
1118+ options : { retryable : false } ,
1119+ } ) ;
1120+ }
1121+
10811122 if ( maxRetries === 0 ) {
1082- this . emitError ( error as Error , false ) ;
1123+ this . emitError ( err , false ) ;
10831124 throw new APIConnectionError ( {
10841125 message : 'Failed to connect to Gemini Live' ,
10851126 } ) ;
10861127 }
10871128
10881129 if ( this . numRetries >= maxRetries ) {
1089- this . emitError ( error as Error , false ) ;
1130+ this . emitError ( err , false ) ;
10901131 throw new APIConnectionError ( {
10911132 message : `Failed to connect to Gemini Live after ${ maxRetries } attempts` ,
10921133 } ) ;
@@ -1179,6 +1220,7 @@ export class RealtimeSession extends llm.RealtimeSession {
11791220 } catch ( e ) {
11801221 if ( ! this . sessionShouldClose . isSet ) {
11811222 this . #logger. error ( `Error in send task: ${ e } ` ) ;
1223+ this . sessionError = this . toError ( e ) ;
11821224 this . markRestartNeeded ( ) ;
11831225 }
11841226 } finally {
@@ -1292,6 +1334,7 @@ export class RealtimeSession extends llm.RealtimeSession {
12921334 } catch ( e ) {
12931335 if ( ! this . sessionShouldClose . isSet ) {
12941336 this . #logger. error ( `Error in onReceiveMessage: ${ e } ` ) ;
1337+ this . sessionError = this . toError ( e ) ;
12951338 this . markRestartNeeded ( ) ;
12961339 }
12971340 }
0 commit comments