@@ -368,21 +368,21 @@ export class ChatDatabase {
368368 * 5. If the last message is from the assistant and it contains tool uses, and a next user
369369 * message is set without tool results, then the user message will have cancelled tool results.
370370 */
371- fixHistory ( tabId : string , newUserMessage : ChatMessage , conversationId : string ) : void {
371+ fixHistory ( tabId : string , newUserMessage : ChatMessage , conversationId : string ) : boolean {
372372 if ( ! this . #initialized) {
373- return
373+ return true
374374 }
375375 const historyId = this . #historyIdMapping. get ( tabId )
376376 this . #features. logging . info ( `Fixing history: tabId=${ tabId } , historyId=${ historyId || 'undefined' } ` )
377377
378378 if ( ! historyId ) {
379- return
379+ return true
380380 }
381381
382382 const tabCollection = this . #db. getCollection < Tab > ( TabCollection )
383383 const tabData = tabCollection . findOne ( { historyId } )
384384 if ( ! tabData ) {
385- return
385+ return true
386386 }
387387
388388 let allMessages = tabData . conversations . flatMap ( ( conversation : Conversation ) => conversation . messages )
@@ -401,7 +401,7 @@ export class ChatDatabase {
401401 this . ensureValidMessageSequence ( allMessages )
402402
403403 // If the last message is from the assistant and it contains tool uses, and a next user message is set without tool results, then the user message will have cancelled tool results.
404- this . handleToolUses ( allMessages , newUserMessage )
404+ const isValid = this . validateToolUses ( allMessages , newUserMessage )
405405 const clientType = this . #features. lsp . getClientInitializeParams ( ) ?. clientInfo ?. name || 'unknown'
406406
407407 tabData . conversations = [
@@ -414,6 +414,7 @@ export class ChatDatabase {
414414 tabData . updatedAt = new Date ( )
415415 tabCollection . update ( tabData )
416416 this . #features. logging . info ( `Updated tab data in collection` )
417+ return isValid
417418 }
418419
419420 private trimHistoryToMaxLength ( messages : Message [ ] ) : Message [ ] {
@@ -611,20 +612,57 @@ export class ChatDatabase {
611612 }
612613 }
613614
614- private handleToolUses ( messages : Message [ ] , newUserMessage : ChatMessage ) : void {
615+ private validateToolUses ( messages : Message [ ] , newUserMessage : ChatMessage ) : boolean {
615616 if ( messages . length === 0 ) {
616617 if ( newUserMessage . userInputMessage ?. userInputMessageContext ?. toolResults ) {
617618 this . #features. logging . debug ( 'No history message found, but new user message has tool results.' )
618619 newUserMessage . userInputMessage . userInputMessageContext . toolResults = undefined
619620 // tool results are empty, so content must not be empty
620621 newUserMessage . userInputMessage . content = 'Conversation history was too large, so it was cleared.'
621622 }
622- return
623+ return true
623624 }
624625
625626 const lastMsg = messages [ messages . length - 1 ]
627+ const toolResults = newUserMessage . userInputMessage ?. userInputMessageContext ?. toolResults
628+
629+ if ( toolResults && toolResults . length > 0 ) {
630+ // If last message has no tool uses but new message has tool results, this is invalid
631+ if ( ! lastMsg . toolUses || lastMsg . toolUses . length === 0 ) {
632+ this . #features. logging . warn ( 'New message has tool results but last message has no tool uses' )
633+ return false
634+ }
635+
636+ const toolUseIds = new Set ( lastMsg . toolUses . map ( toolUse => toolUse . toolUseId ) )
637+ const validToolResults = toolResults . filter ( toolResult => toolUseIds . has ( toolResult . toolUseId ) )
638+ const invalidToolResults = toolResults . filter ( toolResult => ! toolUseIds . has ( toolResult . toolUseId ) )
639+
640+ if ( invalidToolResults . length > 0 ) {
641+ this . #features. logging . warn (
642+ `Found ${ invalidToolResults . length } tool results without matching tool uses, marking them as cancelled`
643+ )
644+
645+ // Mark invalid tool results as cancelled
646+ for ( const invalidResult of invalidToolResults ) {
647+ invalidResult . status = ToolResultStatus . ERROR
648+ invalidResult . content = [
649+ {
650+ text : 'Tool use was cancelled by the user' ,
651+ } ,
652+ ]
653+ }
654+
655+ // Update the tool results in the message
656+ if ( newUserMessage . userInputMessage ?. userInputMessageContext ) {
657+ newUserMessage . userInputMessage . userInputMessageContext . toolResults = [
658+ ...validToolResults ,
659+ ...invalidToolResults ,
660+ ]
661+ }
662+ }
663+ }
664+
626665 if ( lastMsg . toolUses && lastMsg . toolUses . length > 0 ) {
627- const toolResults = newUserMessage . userInputMessage ?. userInputMessageContext ?. toolResults
628666 if ( ! toolResults || toolResults . length === 0 ) {
629667 this . #features. logging . debug (
630668 `No tools results in last user message following a tool use message from assisstant, marking as canceled`
@@ -645,5 +683,6 @@ export class ChatDatabase {
645683 }
646684 }
647685 }
686+ return true
648687 }
649688}
0 commit comments