@@ -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 */
@@ -473,6 +474,7 @@ export class RealtimeSession extends llm.RealtimeSession {
473474 private inUserActivity = false ;
474475 private sessionLock = new Mutex ( ) ;
475476 private numRetries = 0 ;
477+ private sessionError ?: Error ;
476478 private hasReceivedAudioInput = false ;
477479 private pendingInterruptText = false ;
478480 private earlyCompletionPending = false ;
@@ -557,6 +559,20 @@ export class RealtimeSession extends llm.RealtimeSession {
557559 }
558560 }
559561
562+ private toError ( error : unknown ) : Error {
563+ return error instanceof Error ? error : new Error ( String ( error ) ) ;
564+ }
565+
566+ private isContextExhaustedError ( error : unknown ) : boolean {
567+ return (
568+ ( typeof error === 'object' &&
569+ error !== null &&
570+ 'statusCode' in error &&
571+ error . statusCode === WS_CLOSE_CONTEXT_EXHAUSTED ) ||
572+ String ( error ) . includes ( String ( WS_CLOSE_CONTEXT_EXHAUSTED ) )
573+ ) ;
574+ }
575+
560576 private isNonBlockingToolBehavior ( ) : boolean {
561577 return this . options . toolBehavior === types . Behavior . NON_BLOCKING ;
562578 }
@@ -1023,19 +1039,23 @@ export class RealtimeSession extends llm.RealtimeSession {
10231039 const errorMsg = event . reason || `WebSocket closed with code ${ event . code } ` ;
10241040 this . #logger. error ( `Gemini Live session error: ${ errorMsg } ${ truncationNote } ` ) ;
10251041
1026- this . emitError (
1027- new APIStatusError ( {
1028- message : `${ errorMsg } ${ truncationNote } ` ,
1029- options : {
1030- statusCode : event . code ,
1031- retryable : false ,
1032- body : event . reason
1033- ? { reason : event . reason , code : event . code , truncated : isTruncated }
1034- : null ,
1035- } ,
1036- } ) ,
1037- false ,
1038- ) ;
1042+ const error = new APIStatusError ( {
1043+ message : `${ errorMsg } ${ truncationNote } ` ,
1044+ options : {
1045+ statusCode : event . code ,
1046+ retryable : false ,
1047+ body : event . reason
1048+ ? { reason : event . reason , code : event . code , truncated : isTruncated }
1049+ : null ,
1050+ } ,
1051+ } ) ;
1052+
1053+ if ( event . code === WS_CLOSE_CONTEXT_EXHAUSTED ) {
1054+ this . sessionError = error ;
1055+ this . markRestartNeeded ( ) ;
1056+ } else {
1057+ this . emitError ( error , false ) ;
1058+ }
10391059 } else {
10401060 this . #logger. debug ( 'Gemini Live session closed:' , event . code , event . reason ) ;
10411061 }
@@ -1084,20 +1104,41 @@ export class RealtimeSession extends llm.RealtimeSession {
10841104 }
10851105
10861106 await cancelAndWait ( [ sendTask , restartWaitTask ] , 2000 ) ;
1107+
1108+ if ( this . sessionError ) {
1109+ const error = this . sessionError ;
1110+ this . sessionError = undefined ;
1111+ throw error ;
1112+ }
10871113 } catch ( error ) {
1088- this . #logger. error ( `Gemini Realtime API error: ${ error } ` ) ;
1114+ const err = this . toError ( error ) ;
1115+ this . #logger. error ( `Gemini Realtime API error: ${ err } ` ) ;
10891116
10901117 if ( this . #closed) break ;
10911118
1119+ // Gemini Live closes with 1007 when the session context is exhausted. Reconnecting
1120+ // would replay the same oversized context and fail again, so terminate the session.
1121+ if ( this . isContextExhaustedError ( err ) ) {
1122+ this . #logger. error (
1123+ err ,
1124+ 'Gemini Live closed the session: context exhausted (1007). Reconnecting would replay the same context and fail again; terminating the session.' ,
1125+ ) ;
1126+ this . emitError ( err , false ) ;
1127+ throw new APIConnectionError ( {
1128+ message : 'Gemini Live session context exhausted (1007)' ,
1129+ options : { retryable : false } ,
1130+ } ) ;
1131+ }
1132+
10921133 if ( maxRetries === 0 ) {
1093- this . emitError ( error as Error , false ) ;
1134+ this . emitError ( err , false ) ;
10941135 throw new APIConnectionError ( {
10951136 message : 'Failed to connect to Gemini Live' ,
10961137 } ) ;
10971138 }
10981139
10991140 if ( this . numRetries >= maxRetries ) {
1100- this . emitError ( error as Error , false ) ;
1141+ this . emitError ( err , false ) ;
11011142 throw new APIConnectionError ( {
11021143 message : `Failed to connect to Gemini Live after ${ maxRetries } attempts` ,
11031144 } ) ;
@@ -1190,6 +1231,7 @@ export class RealtimeSession extends llm.RealtimeSession {
11901231 } catch ( e ) {
11911232 if ( ! this . sessionShouldClose . isSet ) {
11921233 this . #logger. error ( `Error in send task: ${ e } ` ) ;
1234+ this . sessionError = this . toError ( e ) ;
11931235 this . markRestartNeeded ( ) ;
11941236 }
11951237 } finally {
@@ -1303,6 +1345,7 @@ export class RealtimeSession extends llm.RealtimeSession {
13031345 } catch ( e ) {
13041346 if ( ! this . sessionShouldClose . isSet ) {
13051347 this . #logger. error ( `Error in onReceiveMessage: ${ e } ` ) ;
1348+ this . sessionError = this . toError ( e ) ;
13061349 this . markRestartNeeded ( ) ;
13071350 }
13081351 }
0 commit comments