@@ -92,6 +92,8 @@ import { amazonQTabSuffix } from '../../../shared/constants'
9292import { OutputKind } from '../../tools/toolShared'
9393import { ToolUtils , Tool } from '../../tools/toolUtils'
9494import { ChatStream } from '../../tools/chatStream'
95+ import { FsWriteParams } from '../../tools/fsWrite'
96+ import { tempDirPath } from '../../../shared/filesystemUtilities'
9597
9698export interface ChatControllerMessagePublishers {
9799 readonly processPromptChatMessage : MessagePublisher < PromptMessage >
@@ -399,31 +401,6 @@ export class ChatController {
399401 } )
400402 }
401403
402- private async processAcceptCodeDiff ( message : CustomFormActionMessage ) {
403- const session = this . sessionStorage . getSession ( message . tabID ?? '' )
404- const filePath = session . filePath ?? ''
405- const fileExists = await fs . existsFile ( filePath )
406- const tempFilePath = session . tempFilePath
407- const tempFileExists = await fs . existsFile ( tempFilePath ?? '' )
408- if ( fileExists && tempFileExists ) {
409- const fileContent = await fs . readFileText ( filePath )
410- const tempFileContent = await fs . readFileText ( tempFilePath ?? '' )
411- if ( fileContent !== tempFileContent ) {
412- await fs . writeFile ( filePath , tempFileContent )
413- }
414- await fs . delete ( tempFilePath ?? '' )
415- await vscode . commands . executeCommand ( 'vscode.open' , vscode . Uri . file ( filePath ) )
416- } else if ( ! fileExists && tempFileExists ) {
417- const fileContent = await fs . readFileText ( tempFilePath ?? '' )
418- await fs . writeFile ( filePath , fileContent )
419- await fs . delete ( tempFilePath ?? '' )
420- await vscode . commands . executeCommand ( 'vscode.open' , vscode . Uri . file ( filePath ) )
421- }
422- // Reset the filePaths to undefined
423- session . setFilePath ( undefined )
424- session . setTempFilePath ( undefined )
425- }
426-
427404 private async processCopyCodeToClipboard ( message : CopyCodeToClipboard ) {
428405 this . telemetryHelper . recordInteractWithMessage ( message )
429406 }
@@ -628,29 +605,126 @@ export class ChatController {
628605 this . handlePromptCreate ( message . tabID )
629606 }
630607 }
608+ private async handleCreatePrompt ( message : CustomFormActionMessage ) {
609+ const userPromptsDirectory = getUserPromptsDirectory ( )
610+ const title = message . action . formItemValues ?. [ 'prompt-name' ] ?? 'default'
611+ const newFilePath = path . join ( userPromptsDirectory , `${ title } ${ promptFileExtension } ` )
631612
632- private async processCustomFormAction ( message : CustomFormActionMessage ) {
633- if ( message . action . id === 'submit-create-prompt' ) {
634- const userPromptsDirectory = getUserPromptsDirectory ( )
613+ await fs . writeFile ( newFilePath , new Uint8Array ( Buffer . from ( '' ) ) )
614+ const newFileDoc = await vscode . workspace . openTextDocument ( newFilePath )
615+ await vscode . window . showTextDocument ( newFileDoc )
635616
636- const title = message . action . formItemValues ?. [ 'prompt-name' ]
637- const newFilePath = path . join (
638- userPromptsDirectory ,
639- title ? `${ title } ${ promptFileExtension } ` : `default${ promptFileExtension } `
640- )
641- const newFileContent = new Uint8Array ( Buffer . from ( '' ) )
642- await fs . writeFile ( newFilePath , newFileContent )
643- const newFileDoc = await vscode . workspace . openTextDocument ( newFilePath )
644- await vscode . window . showTextDocument ( newFileDoc )
645- telemetry . ui_click . emit ( { elementId : 'amazonq_createSavedPrompt' } )
646- } else if ( message . action . id === 'accept-code-diff' ) {
647- await this . processAcceptCodeDiff ( message )
648- } else if ( message . action . id === 'reject-code-diff' ) {
649- // Reset the filePaths to undefined
650- this . sessionStorage . getSession ( message . tabID ?? '' ) . setFilePath ( undefined )
651- this . sessionStorage . getSession ( message . tabID ?? '' ) . setTempFilePath ( undefined )
652- } else if ( message . action . id === 'confirm-tool-use' ) {
653- await this . processToolUseMessage ( message )
617+ telemetry . ui_click . emit ( { elementId : 'amazonq_createSavedPrompt' } )
618+ }
619+
620+ private async processToolUseMessage ( message : CustomFormActionMessage ) {
621+ const tabID = message . tabID
622+ if ( ! tabID ) {
623+ return
624+ }
625+ this . editorContextExtractor
626+ . extractContextForTrigger ( 'ChatMessage' )
627+ . then ( async ( context ) => {
628+ const triggerID = randomUUID ( )
629+ this . triggerEventsStorage . addTriggerEvent ( {
630+ id : triggerID ,
631+ tabID : message . tabID ,
632+ message : undefined ,
633+ type : 'chat_message' ,
634+ context,
635+ } )
636+ const session = this . sessionStorage . getSession ( tabID )
637+ const toolUse = session . toolUse
638+ if ( ! toolUse || ! toolUse . input ) {
639+ return
640+ }
641+ session . setToolUse ( undefined )
642+
643+ const toolResults : ToolResult [ ] = [ ]
644+
645+ const result = ToolUtils . tryFromToolUse ( toolUse )
646+ if ( 'type' in result ) {
647+ const tool : Tool = result
648+
649+ try {
650+ await ToolUtils . validate ( tool )
651+
652+ const chatStream = new ChatStream ( this . messenger , tabID , triggerID , toolUse )
653+ const output = await ToolUtils . invoke ( tool , chatStream )
654+
655+ toolResults . push ( {
656+ content : [
657+ output . output . kind === OutputKind . Text
658+ ? { text : output . output . content }
659+ : { json : output . output . content } ,
660+ ] ,
661+ toolUseId : toolUse . toolUseId ,
662+ status : ToolResultStatus . SUCCESS ,
663+ } )
664+ // Close the diff view if User accept the generated code changes.
665+ if ( vscode . window . tabGroups . activeTabGroup . activeTab ?. label . includes ( amazonQTabSuffix ) ) {
666+ await vscode . commands . executeCommand ( 'workbench.action.closeActiveEditor' )
667+ }
668+ } catch ( e : any ) {
669+ toolResults . push ( {
670+ content : [ { text : e . message } ] ,
671+ toolUseId : toolUse . toolUseId ,
672+ status : ToolResultStatus . ERROR ,
673+ } )
674+ }
675+ } else {
676+ const toolResult : ToolResult = result
677+ toolResults . push ( toolResult )
678+ }
679+
680+ await this . generateResponse (
681+ {
682+ message : '' ,
683+ trigger : ChatTriggerType . ChatMessage ,
684+ query : undefined ,
685+ codeSelection : context ?. focusAreaContext ?. selectionInsideExtendedCodeBlock ,
686+ fileText : context ?. focusAreaContext ?. extendedCodeBlock ?? '' ,
687+ fileLanguage : context ?. activeFileContext ?. fileLanguage ,
688+ filePath : context ?. activeFileContext ?. filePath ,
689+ matchPolicy : context ?. activeFileContext ?. matchPolicy ,
690+ codeQuery : context ?. focusAreaContext ?. names ,
691+ userIntent : undefined ,
692+ customization : getSelectedCustomization ( ) ,
693+ toolResults : toolResults ,
694+ origin : Origin . IDE ,
695+ chatHistory : this . chatHistoryManager . getHistory ( ) ,
696+ context : [ ] ,
697+ relevantTextDocuments : [ ] ,
698+ additionalContents : [ ] ,
699+ documentReferences : [ ] ,
700+ useRelevantDocuments : false ,
701+ contextLengths : {
702+ ...defaultContextLengths ,
703+ } ,
704+ } ,
705+ triggerID
706+ )
707+ } )
708+ . catch ( ( e ) => {
709+ this . processException ( e , tabID )
710+ } )
711+ }
712+
713+ private async processCustomFormAction ( message : CustomFormActionMessage ) {
714+ switch ( message . action . id ) {
715+ case 'submit-create-prompt' :
716+ await this . handleCreatePrompt ( message )
717+ break
718+ case 'accept-code-diff' :
719+ case 'confirm-tool-use' :
720+ await this . processToolUseMessage ( message )
721+ break
722+ case 'reject-code-diff' :
723+ // TODO: Do session cleanUp.
724+ getLogger ( ) . info ( 'Generated response is rejected' )
725+ break
726+ default :
727+ getLogger ( ) . warn ( `Unhandled action: ${ message . action . id } ` )
654728 }
655729 }
656730
@@ -663,15 +737,52 @@ export class ChatController {
663737 const session = this . sessionStorage . getSession ( message . tabID )
664738 // Check if user clicked on filePath in the contextList or in the fileListTree and perform the functionality accordingly.
665739 if ( session . showDiffOnFileWrite ) {
666- const filePath = session . filePath ?? message . filePath
740+ // Create a temporary file path to show the diff view
741+ const pathToArchiveDir = path . join ( tempDirPath , 'q-chat' )
742+ const archivePathExists = await fs . existsDir ( pathToArchiveDir )
743+ if ( archivePathExists ) {
744+ await fs . delete ( pathToArchiveDir , { recursive : true } )
745+ }
746+ await fs . mkdir ( pathToArchiveDir )
747+ const resultArtifactsDir = path . join ( pathToArchiveDir , 'resultArtifacts' )
748+ await fs . mkdir ( resultArtifactsDir )
749+ const tempFilePath = path . join (
750+ resultArtifactsDir ,
751+ `temp-${ path . basename ( ( session . toolUse ?. input as any ) . path ) } `
752+ )
753+
754+ // If we have existing filePath copy file content from existing file to temporary file.
755+ const filePath = ( session . toolUse ?. input as any ) . path ?? message . filePath
667756 const fileExists = await fs . existsFile ( filePath )
757+ if ( fileExists ) {
758+ const fileContent = await fs . readFileText ( filePath )
759+ await fs . writeFile ( tempFilePath , fileContent )
760+ }
761+
762+ // Create a deep clone of the toolUse object and pass this toolUse to FsWrite tool execution to get the modified temporary file.
763+ const clonedToolUse = structuredClone ( session . toolUse )
764+ if ( ! clonedToolUse ) {
765+ return
766+ }
767+ const input = clonedToolUse . input as unknown as FsWriteParams
768+ input . path = tempFilePath
769+ const result = ToolUtils . tryFromToolUse ( clonedToolUse )
770+ if ( ! ( 'type' in result ) ) {
771+ return
772+ }
773+ const tool : Tool = result
774+ await ToolUtils . validate ( tool )
775+
776+ const chatStream = new ChatStream ( this . messenger , message . tabID , randomUUID ( ) , clonedToolUse )
777+ await ToolUtils . invoke ( tool , chatStream )
778+
668779 // Check if fileExists=false, If yes, return instead of showing broken diff experience.
669- if ( ! session . tempFilePath ) {
780+ if ( ! tempFilePath ) {
670781 void vscode . window . showInformationMessage ( 'Generated code changes have been reviewed and processed.' )
671782 return
672783 }
673784 const leftUri = fileExists ? vscode . Uri . file ( filePath ) : vscode . Uri . from ( { scheme : 'untitled' } )
674- const rightUri = vscode . Uri . file ( session . tempFilePath ?? filePath )
785+ const rightUri = vscode . Uri . file ( tempFilePath ?? filePath )
675786 const fileName = path . basename ( filePath )
676787 await vscode . commands . executeCommand ( 'vscode.diff' , leftUri , rightUri , `${ fileName } ${ amazonQTabSuffix } ` )
677788 } else {
@@ -925,95 +1036,6 @@ export class ChatController {
9251036 }
9261037 }
9271038
928- private async processToolUseMessage ( message : CustomFormActionMessage ) {
929- const tabID = message . tabID
930- if ( ! tabID ) {
931- return
932- }
933- this . editorContextExtractor
934- . extractContextForTrigger ( 'ChatMessage' )
935- . then ( async ( context ) => {
936- const triggerID = randomUUID ( )
937- this . triggerEventsStorage . addTriggerEvent ( {
938- id : triggerID ,
939- tabID : message . tabID ,
940- message : undefined ,
941- type : 'chat_message' ,
942- context,
943- } )
944- const session = this . sessionStorage . getSession ( tabID )
945- const toolUse = session . toolUse
946- if ( ! toolUse || ! toolUse . input ) {
947- return
948- }
949- session . setToolUse ( undefined )
950-
951- const toolResults : ToolResult [ ] = [ ]
952-
953- const result = ToolUtils . tryFromToolUse ( toolUse )
954- if ( 'type' in result ) {
955- const tool : Tool = result
956-
957- try {
958- await ToolUtils . validate ( tool )
959-
960- const chatStream = new ChatStream ( this . messenger , tabID , triggerID , toolUse . toolUseId )
961- const output = await ToolUtils . invoke ( tool , chatStream )
962-
963- toolResults . push ( {
964- content : [
965- output . output . kind === OutputKind . Text
966- ? { text : output . output . content }
967- : { json : output . output . content } ,
968- ] ,
969- toolUseId : toolUse . toolUseId ,
970- status : ToolResultStatus . SUCCESS ,
971- } )
972- } catch ( e : any ) {
973- toolResults . push ( {
974- content : [ { text : e . message } ] ,
975- toolUseId : toolUse . toolUseId ,
976- status : ToolResultStatus . ERROR ,
977- } )
978- }
979- } else {
980- const toolResult : ToolResult = result
981- toolResults . push ( toolResult )
982- }
983-
984- await this . generateResponse (
985- {
986- message : '' ,
987- trigger : ChatTriggerType . ChatMessage ,
988- query : undefined ,
989- codeSelection : context ?. focusAreaContext ?. selectionInsideExtendedCodeBlock ,
990- fileText : context ?. focusAreaContext ?. extendedCodeBlock ?? '' ,
991- fileLanguage : context ?. activeFileContext ?. fileLanguage ,
992- filePath : context ?. activeFileContext ?. filePath ,
993- matchPolicy : context ?. activeFileContext ?. matchPolicy ,
994- codeQuery : context ?. focusAreaContext ?. names ,
995- userIntent : undefined ,
996- customization : getSelectedCustomization ( ) ,
997- toolResults : toolResults ,
998- origin : Origin . IDE ,
999- chatHistory : this . chatHistoryManager . getHistory ( ) ,
1000- context : [ ] ,
1001- relevantTextDocuments : [ ] ,
1002- additionalContents : [ ] ,
1003- documentReferences : [ ] ,
1004- useRelevantDocuments : false ,
1005- contextLengths : {
1006- ...defaultContextLengths ,
1007- } ,
1008- } ,
1009- triggerID
1010- )
1011- } )
1012- . catch ( ( e ) => {
1013- this . processException ( e , tabID )
1014- } )
1015- }
1016-
10171039 private async processPromptMessageAsNewThread ( message : PromptMessage ) {
10181040 const session = this . sessionStorage . getSession ( message . tabID )
10191041 session . clearListOfReadFiles ( )
0 commit comments