@@ -180,6 +180,7 @@ export class RealtimeSession<
180180 #transcribedTextDeltas: Record < string , string > = { } ;
181181 #history: RealtimeItem [ ] = [ ] ;
182182 #shouldIncludeAudioData: boolean ;
183+ #interruptedByGuardrail: Record < string , boolean > = { } ;
183184
184185 constructor (
185186 public readonly initialAgent :
@@ -446,7 +447,7 @@ export class RealtimeSession<
446447 }
447448 }
448449
449- async #runOutputGuardrails( output : string ) {
450+ async #runOutputGuardrails( output : string , responseId : string ) {
450451 if ( this . #outputGuardrails. length === 0 ) {
451452 return ;
452453 }
@@ -460,24 +461,28 @@ export class RealtimeSession<
460461 this . #outputGuardrails. map ( ( guardrail ) => guardrail . run ( guardrailArgs ) ) ,
461462 ) ;
462463
463- for ( const result of results ) {
464- if ( result . output . tripwireTriggered ) {
465- const error = new OutputGuardrailTripwireTriggered (
466- `Output guardrail triggered: ${ JSON . stringify ( result . output . outputInfo ) } ` ,
467- result ,
468- ) ;
469- this . emit (
470- 'guardrail_tripped' ,
471- this . #context,
472- this . #currentAgent,
473- error ,
474- ) ;
475- this . interrupt ( ) ;
476-
477- const feedbackText = getRealtimeGuardrailFeedbackMessage ( result ) ;
478- this . sendMessage ( feedbackText ) ;
479- break ;
464+ const firstTripwireTriggered = results . find (
465+ ( result ) => result . output . tripwireTriggered ,
466+ ) ;
467+ if ( firstTripwireTriggered ) {
468+ // this ensures that if one guardrail already trips and we are in the middle of another
469+ // guardrail run, we don't trip again
470+ if ( this . #interruptedByGuardrail[ responseId ] ) {
471+ return ;
480472 }
473+ this . #interruptedByGuardrail[ responseId ] = true ;
474+ const error = new OutputGuardrailTripwireTriggered (
475+ `Output guardrail triggered: ${ JSON . stringify ( firstTripwireTriggered . output . outputInfo ) } ` ,
476+ firstTripwireTriggered ,
477+ ) ;
478+ this . emit ( 'guardrail_tripped' , this . #context, this . #currentAgent, error ) ;
479+ this . interrupt ( ) ;
480+
481+ const feedbackText = getRealtimeGuardrailFeedbackMessage (
482+ firstTripwireTriggered ,
483+ ) ;
484+ this . sendMessage ( feedbackText ) ;
485+ return ;
481486 }
482487 }
483488
@@ -498,7 +503,7 @@ export class RealtimeSession<
498503 this . emit ( 'agent_end' , this . #context, this . #currentAgent, textOutput ) ;
499504 this . #currentAgent. emit ( 'agent_end' , this . #context, textOutput ) ;
500505
501- this . #runOutputGuardrails( textOutput ) ;
506+ this . #runOutputGuardrails( textOutput , event . response . id ) ;
502507 } ) ;
503508
504509 this . #transport. on ( 'audio_done' , ( ) => {
@@ -511,6 +516,7 @@ export class RealtimeSession<
511516 try {
512517 const delta = event . delta ;
513518 const itemId = event . itemId ;
519+ const responseId = event . responseId ;
514520 if ( lastItemId !== itemId ) {
515521 lastItemId = itemId ;
516522 lastRunIndex = 0 ;
@@ -531,7 +537,7 @@ export class RealtimeSession<
531537 // We don't cancel existing runs because we want the first one to fail to fail
532538 // The transport layer should upon failure handle the interruption and stop the model
533539 // from generating further
534- this . #runOutputGuardrails( newText ) ;
540+ this . #runOutputGuardrails( newText , responseId ) ;
535541 }
536542 } catch ( err ) {
537543 this . emit ( 'error' , {
@@ -672,6 +678,7 @@ export class RealtimeSession<
672678 * Disconnect from the session.
673679 */
674680 close ( ) {
681+ this . #interruptedByGuardrail = { } ;
675682 this . #transport. close ( ) ;
676683 }
677684
0 commit comments