@@ -828,6 +828,148 @@ describe("summarizeConversation", () => {
828828 expect ( result . messages ) . toHaveLength ( 1 + 1 + N_MESSAGES_TO_KEEP ) // first + summary + last 3
829829 expect ( result . error ) . toBeUndefined ( )
830830 } )
831+
832+ it ( "should include user tool_result message in summarize request when preserving tool_use blocks" , async ( ) => {
833+ const toolUseBlock = {
834+ type : "tool_use" as const ,
835+ id : "toolu_history_fix" ,
836+ name : "read_file" ,
837+ input : { path : "sample.txt" } ,
838+ }
839+ const toolResultBlock = {
840+ type : "tool_result" as const ,
841+ tool_use_id : "toolu_history_fix" ,
842+ content : "file contents" ,
843+ }
844+
845+ const messages : ApiMessage [ ] = [
846+ { role : "user" , content : "Hello" , ts : 1 } ,
847+ { role : "assistant" , content : "Let me help" , ts : 2 } ,
848+ {
849+ role : "assistant" ,
850+ content : [ { type : "text" as const , text : "Running tool..." } , toolUseBlock ] ,
851+ ts : 3 ,
852+ } ,
853+ {
854+ role : "user" ,
855+ content : [ toolResultBlock , { type : "text" as const , text : "Thanks" } ] ,
856+ ts : 4 ,
857+ } ,
858+ { role : "assistant" , content : "Anything else?" , ts : 5 } ,
859+ { role : "user" , content : "Nope" , ts : 6 } ,
860+ ]
861+
862+ let capturedRequestMessages : any [ ] | undefined
863+ const customStream = ( async function * ( ) {
864+ yield { type : "text" as const , text : "Summary of conversation" }
865+ yield { type : "usage" as const , totalCost : 0.05 , outputTokens : 100 }
866+ } ) ( )
867+
868+ mockApiHandler . createMessage = vi . fn ( ) . mockImplementation ( ( _prompt , requestMessagesParam ) => {
869+ capturedRequestMessages = requestMessagesParam
870+ return customStream
871+ } ) as any
872+
873+ const result = await summarizeConversation (
874+ messages ,
875+ mockApiHandler ,
876+ defaultSystemPrompt ,
877+ taskId ,
878+ DEFAULT_PREV_CONTEXT_TOKENS ,
879+ false ,
880+ undefined ,
881+ undefined ,
882+ true ,
883+ )
884+
885+ expect ( result . error ) . toBeUndefined ( )
886+ expect ( capturedRequestMessages ) . toBeDefined ( )
887+
888+ const requestMessages = capturedRequestMessages !
889+ expect ( requestMessages [ requestMessages . length - 1 ] ) . toEqual ( {
890+ role : "user" ,
891+ content : "Summarize the conversation so far, as described in the prompt instructions." ,
892+ } )
893+
894+ const historyMessages = requestMessages . slice ( 0 , - 1 )
895+ expect ( historyMessages . length ) . toBeGreaterThanOrEqual ( 2 )
896+
897+ const assistantMessage = historyMessages [ historyMessages . length - 2 ]
898+ const userMessage = historyMessages [ historyMessages . length - 1 ]
899+
900+ expect ( assistantMessage . role ) . toBe ( "assistant" )
901+ expect ( Array . isArray ( assistantMessage . content ) ) . toBe ( true )
902+ expect (
903+ ( assistantMessage . content as any [ ] ) . some (
904+ ( block ) => block . type === "tool_use" && block . id === toolUseBlock . id ,
905+ ) ,
906+ ) . toBe ( true )
907+
908+ expect ( userMessage . role ) . toBe ( "user" )
909+ expect ( Array . isArray ( userMessage . content ) ) . toBe ( true )
910+ expect (
911+ ( userMessage . content as any [ ] ) . some (
912+ ( block ) => block . type === "tool_result" && block . tool_use_id === toolUseBlock . id ,
913+ ) ,
914+ ) . toBe ( true )
915+ } )
916+
917+ it ( "should append multiple tool_use blocks for parallel tool calls" , async ( ) => {
918+ const toolUseBlockA = {
919+ type : "tool_use" as const ,
920+ id : "toolu_parallel_1" ,
921+ name : "search" ,
922+ input : { query : "foo" } ,
923+ }
924+ const toolUseBlockB = {
925+ type : "tool_use" as const ,
926+ id : "toolu_parallel_2" ,
927+ name : "search" ,
928+ input : { query : "bar" } ,
929+ }
930+
931+ const messages : ApiMessage [ ] = [
932+ { role : "user" , content : "Start" , ts : 1 } ,
933+ { role : "assistant" , content : "Working..." , ts : 2 } ,
934+ {
935+ role : "assistant" ,
936+ content : [ { type : "text" as const , text : "Launching parallel tools" } , toolUseBlockA , toolUseBlockB ] ,
937+ ts : 3 ,
938+ } ,
939+ {
940+ role : "user" ,
941+ content : [
942+ { type : "tool_result" as const , tool_use_id : "toolu_parallel_1" , content : "result A" } ,
943+ { type : "tool_result" as const , tool_use_id : "toolu_parallel_2" , content : "result B" } ,
944+ { type : "text" as const , text : "Continue" } ,
945+ ] ,
946+ ts : 4 ,
947+ } ,
948+ { role : "assistant" , content : "Processing results" , ts : 5 } ,
949+ { role : "user" , content : "Thanks" , ts : 6 } ,
950+ ]
951+
952+ const result = await summarizeConversation (
953+ messages ,
954+ mockApiHandler ,
955+ defaultSystemPrompt ,
956+ taskId ,
957+ DEFAULT_PREV_CONTEXT_TOKENS ,
958+ false ,
959+ undefined ,
960+ undefined ,
961+ true ,
962+ )
963+
964+ const summaryMessage = result . messages [ 1 ]
965+ expect ( Array . isArray ( summaryMessage . content ) ) . toBe ( true )
966+ const summaryContent = summaryMessage . content as any [ ]
967+ expect ( summaryContent [ 0 ] ) . toEqual ( { type : "text" , text : "This is a summary" } )
968+
969+ const preservedToolUses = summaryContent . filter ( ( block ) => block . type === "tool_use" )
970+ expect ( preservedToolUses ) . toHaveLength ( 2 )
971+ expect ( preservedToolUses . map ( ( block ) => block . id ) ) . toEqual ( [ "toolu_parallel_1" , "toolu_parallel_2" ] )
972+ } )
831973} )
832974
833975describe ( "summarizeConversation with custom settings" , ( ) => {
0 commit comments