Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,6 @@ export class Connector extends BaseConnector {
if (
!this.onChatAnswerUpdated ||
![
'accept-code-diff',
'reject-code-diff',
'run-shell-command',
'reject-shell-command',
Expand All @@ -379,17 +378,6 @@ export class Connector extends BaseConnector {
header: currentChatItem?.header ? { ...currentChatItem.header } : {},
}
switch (action.id) {
case 'accept-code-diff':
if (answer.header) {
answer.header.status = {
icon: 'ok' as MynahIconsType,
text: 'Accepted',
status: 'success',
}
answer.header.buttons = []
answer.body = ' '
}
break
case 'reject-code-diff':
if (answer.header) {
answer.header.status = {
Expand Down
67 changes: 46 additions & 21 deletions packages/core/src/codewhispererChat/clients/chat/v0/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,28 @@ export type ToolUseWithError = {
error: Error | undefined
}

type OperationType = 'read' | 'write' | 'listDir'

interface FileOperation {
type: OperationType
filePaths: DocumentReference[]
}

export class ChatSession {
private sessionId: string
/**
* _readFiles = list of files read from the project to gather context before generating response.
* _showDiffOnFileWrite = Controls whether to show diff view (true) or file context view (false) to the user
* _context = Additional context to be passed to the LLM for generating the response
* _messageIdToUpdate = messageId of a chat message to be updated, used for reducing consecutive tool messages
* _messageOperations = Maps messageId to filePaths which helps to open the read files and to open the code diff accordingly.
*/
private _readFiles: DocumentReference[] = []
private _readFolders: DocumentReference[] = []
private _toolUseWithError: ToolUseWithError | undefined
private _showDiffOnFileWrite: boolean = false
private _context: PromptMessage['context']
private _pairProgrammingModeOn: boolean = true
private _fsWriteBackups: Map<string, FsWriteBackup> = new Map()
private _agenticLoopInProgress: boolean = false
private _messageOperations: Map<string, FileOperation> = new Map()

/**
* True if messages from local history have been sent to session.
Expand Down Expand Up @@ -144,30 +150,12 @@ export class ChatSession {
public setSessionID(id: string) {
this.sessionId = id
}
public get readFiles(): DocumentReference[] {
return this._readFiles
}
public get readFolders(): DocumentReference[] {
return this._readFolders
}
public get showDiffOnFileWrite(): boolean {
return this._showDiffOnFileWrite
}
public setShowDiffOnFileWrite(value: boolean) {
this._showDiffOnFileWrite = value
}
public addToReadFiles(filePath: DocumentReference) {
this._readFiles.push(filePath)
}
public clearListOfReadFiles() {
this._readFiles = []
}
public setReadFolders(folder: DocumentReference) {
this._readFolders.push(folder)
}
public clearListOfReadFolders() {
this._readFolders = []
}
async chatIam(chatRequest: SendMessageRequest): Promise<SendMessageCommandOutput> {
const client = await createQDeveloperStreamingClient()

Expand Down Expand Up @@ -196,4 +184,41 @@ export class ChatSession {

return response
}

/**
* Adds a file operation for a specific message
* @param messageId The ID of the message
* @param type The type of operation ('read' or 'listDir' or 'write')
* @param filePaths Array of DocumentReference involved in the operation
*/
public addMessageOperation(messageId: string, type: OperationType, filePaths: DocumentReference[]) {
this._messageOperations.set(messageId, { type, filePaths })
}

/**
* Gets the file operation details for a specific message
* @param messageId The ID of the message
* @returns The file operation details or undefined if not found
*/
public getMessageOperation(messageId: string): FileOperation | undefined {
return this._messageOperations.get(messageId)
}

/**
* Gets all file paths along with line ranges associated with a message
* @param messageId The ID of the message
* @returns Array of DocumentReference or empty array if message ID not found
*/
public getFilePathsByMessageId(messageId: string): DocumentReference[] {
return this._messageOperations.get(messageId)?.filePaths || []
}

/**
* Gets the operation type for a specific message
* @param messageId The ID of the message
* @returns The operation type or undefined if message ID not found
*/
public getOperationTypeByMessageId(messageId: string): OperationType | undefined {
return this._messageOperations.get(messageId)?.type
}
}
34 changes: 25 additions & 9 deletions packages/core/src/codewhispererChat/controllers/chat/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ export class ChatController {
}

private async closeDiffView(message: CustomFormActionMessage) {
// Close the diff view if User rejected or accepted the generated code changes.
// Close the diff view if User rejected the generated code changes or asked a different question.
if (vscode.window.tabGroups.activeTabGroup.activeTab?.label.includes(amazonQTabSuffix)) {
await vscode.commands.executeCommand('workbench.action.closeActiveEditor')
}
Expand Down Expand Up @@ -919,9 +919,6 @@ export class ChatController {
)
}
break
case 'accept-code-diff':
await this.closeDiffView(message)
break
case 'reject-code-diff':
await this.restoreBackup(message)
await this.closeDiffView(message)
Expand Down Expand Up @@ -1006,7 +1003,26 @@ export class ChatController {
}

private async processFileClickMessage(message: FileClick) {
/**
* This function is used for 3 useCases
* 1. Read files/folders for Agentic chat
* 2. Read files in workspace context: Project falcon
* 3. Open code diff for generated files in Agentic chat.
*/
const session = this.sessionStorage.getSession(message.tabID)

if (session.getMessageOperation(message.messageId)?.type === 'read') {
try {
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(message.filePath))
} catch {
void vscode.window.showInformationMessage(
`Sorry, Amazon Q failed to open the file: ${path.basename(message.filePath)}`
)
}
} else if (session.getMessageOperation(message.messageId)?.type === 'listDir') {
void vscode.window.showInformationMessage(`Analyzed the directory: ${message.filePath}`)
}

// Check if user clicked on filePath in the contextList or in the fileListTree and perform the functionality accordingly.
if (session.showDiffOnFileWrite) {
const toolUseId = message.messageId
Expand All @@ -1030,7 +1046,9 @@ export class ChatController {
)
} catch (error) {
getLogger().error(`Unexpected error in diff view generation: ${error}`)
void vscode.window.showErrorMessage(`Failed to open diff view.`)
void vscode.window.showErrorMessage(
`Sorry, Amazon Q failed to open the diff view for ${path.basename(message.filePath)}`
)
}
} else {
const lineRanges = session.contexts.get(message.filePath)
Expand Down Expand Up @@ -1299,12 +1317,10 @@ export class ChatController {
this.processException(e, message.tabID)
}
}
private sessionCleanUp(session: ChatSession) {
private initialCleanUp(session: ChatSession) {
// Create a fresh token for this new conversation
session.createNewTokenSource()
session.setAgenticLoopInProgress(true)
session.clearListOfReadFiles()
session.clearListOfReadFolders()
session.setShowDiffOnFileWrite(false)
session.setMessageIdToUpdate(undefined)
session.setMessageIdToUpdateListDirectory(undefined)
Expand All @@ -1316,7 +1332,7 @@ export class ChatController {
if (session.agenticLoopInProgress) {
session.disposeTokenSource()
}
this.sessionCleanUp(session)
this.initialCleanUp(session)
this.editorContextExtractor
.extractContextForTrigger('ChatMessage')
.then(async (context) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
} from '@aws/mynah-ui'
import { Database } from '../../../../shared/db/chatDb/chatDb'
import { TabType } from '../../../../amazonq/webview/ui/storages/tabsStorage'
import { ToolType, ToolUtils } from '../../../tools/toolUtils'
import { Tool, ToolType, ToolUtils } from '../../../tools/toolUtils'
import { ChatStream } from '../../../tools/chatStream'
import path from 'path'
import { CommandValidation, ExecuteBashParams } from '../../../tools/executeBash'
Expand Down Expand Up @@ -193,6 +193,29 @@ export class Messenger {
return codeBlocks.length
}

public handleFileReadOrListOperation = (session: ChatSession, toolUse: ToolUse, tool: Tool) => {
const messageIdToUpdate =
tool.type === ToolType.FsRead ? session.messageIdToUpdate : session.messageIdToUpdateListDirectory
const messageId = messageIdToUpdate ?? toolUse?.toolUseId ?? ''
const operationType = tool.type === ToolType.FsRead ? 'read' : 'listDir'
const input = toolUse.input as unknown as FsReadParams | ListDirectoryParams
const existingPaths = session.getFilePathsByMessageId(messageId)

// Check if path already exists in the list
const isPathAlreadyProcessed = existingPaths.some((path) => path.relativeFilePath === input.path)

if (!isPathAlreadyProcessed) {
session.addMessageOperation(messageId, operationType, [
...existingPaths,
{
relativeFilePath: input.path,
lineRanges: [{ first: -1, second: -1 }],
},
])
}
return messageIdToUpdate
}

public async sendAIResponse(
response: MessengerResponseType,
session: ChatSession,
Expand Down Expand Up @@ -335,32 +358,8 @@ export class Messenger {
session.setShowDiffOnFileWrite(true)
changeList = await tool.tool.getDiffChanges()
}
if (tool.type === ToolType.FsRead) {
messageIdToUpdate = session.messageIdToUpdate
const input = toolUse.input as unknown as FsReadParams
// Check if this file path is already in the readFiles list
const isFileAlreadyRead = session.readFiles.some(
(file) => file.relativeFilePath === input.path
)
if (!isFileAlreadyRead) {
session.addToReadFiles({
relativeFilePath: input?.path,
lineRanges: [{ first: -1, second: -1 }],
})
}
} else if (tool.type === ToolType.ListDirectory) {
messageIdToUpdate = session.messageIdToUpdateListDirectory
const input = toolUse.input as unknown as ListDirectoryParams
// Check if this folder is already in the readFolders list
const isFolderAlreadyRead = session.readFolders.some(
(folder) => folder.relativeFilePath === input.path
)
if (!isFolderAlreadyRead) {
session.setReadFolders({
relativeFilePath: input?.path,
lineRanges: [{ first: -1, second: -1 }],
})
}
if (isReadOrList) {
messageIdToUpdate = this.handleFileReadOrListOperation(session, toolUse, tool)
}
const validation = ToolUtils.requiresAcceptance(tool)
const chatStream = new ChatStream(
Expand All @@ -381,14 +380,11 @@ export class Messenger {
chatStream,
chatStream.validation.requiresAcceptance
)
if (session.messageIdToUpdate === undefined && tool.type === ToolType.FsRead) {
if (!session.messageIdToUpdate && tool.type === ToolType.FsRead) {
// Store the first messageId in a chain of tool uses
session.setMessageIdToUpdate(toolUse.toolUseId)
}
if (
session.messageIdToUpdateListDirectory === undefined &&
tool.type === ToolType.ListDirectory
) {
if (!session.messageIdToUpdateListDirectory && tool.type === ToolType.ListDirectory) {
session.setMessageIdToUpdateListDirectory(toolUse.toolUseId)
}
getLogger().debug(
Expand Down Expand Up @@ -706,10 +702,10 @@ export class Messenger {
triggerID: string,
messageIdToUpdate?: string
) {
const contextList = toolUse.name === ToolType.ListDirectory ? session.readFolders : session.readFiles
const messageID = messageIdToUpdate ?? toolUse?.toolUseId ?? ''
const contextList = messageIdToUpdate || toolUse?.toolUseId ? session.getFilePathsByMessageId(messageID) : []
const isFileRead = toolUse.name === ToolType.FsRead
const items = isFileRead ? session.readFiles : session.readFolders
const itemCount = items.length
const itemCount = contextList.length

const title =
itemCount < 1
Expand All @@ -727,7 +723,7 @@ export class Messenger {
followUpsHeader: undefined,
relatedSuggestions: undefined,
triggerID,
messageID: messageIdToUpdate ?? toolUse?.toolUseId ?? '',
messageID,
userIntent: undefined,
codeBlockLanguage: undefined,
contextList,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/codewhispererChat/tools/chatStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class ChatStream extends Writable {
) {
super()
this.logger.debug(
`ChatStream created for tabID: ${tabID}, triggerID: ${triggerID}, readFiles: ${session.readFiles}, emitEvent to mynahUI: ${emitEvent}`
`ChatStream created for tabID: ${tabID}, triggerID: ${triggerID}, emitEvent to mynahUI: ${emitEvent}`
)
if (!emitEvent) {
return
Expand Down
Loading