File tree Expand file tree Collapse file tree 2 files changed +68
-1
lines changed
Expand file tree Collapse file tree 2 files changed +68
-1
lines changed Original file line number Diff line number Diff line change @@ -2801,6 +2801,55 @@ describe('stateSnapshotToUIMessages', () => {
28012801 } ) ;
28022802 } ) ;
28032803
2804+ it ( 'should deduplicate interrupt parts matching existing tool calls' , ( ) => {
2805+ const snapshot = {
2806+ values : {
2807+ messages : [
2808+ new HumanMessage ( { content : 'Send email' , id : 'h-1' } ) ,
2809+ new AIMessage ( {
2810+ content : '' ,
2811+ id : 'ai-1' ,
2812+ tool_calls : [
2813+ {
2814+ id : 'call-email-1' ,
2815+ name : 'send_email' ,
2816+ args : { to : 'user@example.com' } ,
2817+ } ,
2818+ ] ,
2819+ } ) ,
2820+ ] ,
2821+ } ,
2822+ tasks : [
2823+ {
2824+ id : 'task-1' ,
2825+ name : 'agent' ,
2826+ interrupts : [
2827+ {
2828+ value : {
2829+ action_requests : [
2830+ {
2831+ name : 'send_email' ,
2832+ arguments : { to : 'user@example.com' } ,
2833+ id : 'call-email-1' ,
2834+ } ,
2835+ ] ,
2836+ } ,
2837+ } ,
2838+ ] ,
2839+ } ,
2840+ ] ,
2841+ } ;
2842+ const result = stateSnapshotToUIMessages ( snapshot ) ;
2843+
2844+ // Should have only one dynamic-tool part, not duplicated
2845+ const toolParts = result [ 1 ] . parts . filter ( p => p . type === 'dynamic-tool' ) ;
2846+ expect ( toolParts ) . toHaveLength ( 1 ) ;
2847+ expect ( toolParts [ 0 ] ) . toMatchObject ( {
2848+ toolCallId : 'call-email-1' ,
2849+ toolName : 'send_email' ,
2850+ } ) ;
2851+ } ) ;
2852+
28042853 it ( 'should ignore tasks without interrupts' , ( ) => {
28052854 const snapshot = {
28062855 values : {
Original file line number Diff line number Diff line change @@ -957,7 +957,25 @@ export function stateSnapshotToUIMessages(
957957 } ;
958958 uiMessages . push ( lastAssistant ) ;
959959 }
960- lastAssistant . parts . push ( ...interruptParts ) ;
960+ // Deduplicate: skip interrupt parts that already exist on the assistant
961+ // (e.g., when the AI message's tool_calls match the interrupt's actionRequests)
962+ const existingToolCallIds = new Set < string > ( ) ;
963+ const existingToolParts = new Set < string > ( ) ;
964+ for ( const part of lastAssistant . parts ) {
965+ if ( part . type === 'dynamic-tool' ) {
966+ existingToolCallIds . add ( part . toolCallId ) ;
967+ existingToolParts . add ( `${ part . toolName } :${ JSON . stringify ( part . input ) } ` ) ;
968+ }
969+ }
970+ for ( const interruptPart of interruptParts ) {
971+ const toolCallKey = `${ interruptPart . toolName } :${ JSON . stringify ( interruptPart . input ) } ` ;
972+ if (
973+ ! existingToolCallIds . has ( interruptPart . toolCallId ) &&
974+ ! existingToolParts . has ( toolCallKey )
975+ ) {
976+ lastAssistant . parts . push ( interruptPart ) ;
977+ }
978+ }
961979 }
962980
963981 return uiMessages ;
You can’t perform that action at this time.
0 commit comments