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
1 change: 1 addition & 0 deletions packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
"AWS.amazonq.executeBash.reject": "Reject",
"AWS.amazonq.chat.directive.pairProgrammingModeOn": "You are using **pair programming mode**: Q can now list files, preview code diffs and allow you to run shell commands.",
"AWS.amazonq.chat.directive.pairProgrammingModeOff": "You turned off **pair programming mode**. Q will not include code diffs or run commands in the chat.",
"AWS.amazonq.chat.directive.permission.readAndList": "I need permission to read files and list directories outside the workspace.",
"AWS.amazonq.chat.directive.runCommandToProceed": "Run the command to proceed.",
"AWS.toolkit.lambda.walkthrough.quickpickTitle": "Application Builder Walkthrough",
"AWS.toolkit.lambda.walkthrough.title": "Get started building your application",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ export class Messenger {
changeList,
explanation
)
await ToolUtils.queueDescription(tool, chatStream)
await ToolUtils.queueDescription(
tool,
chatStream,
chatStream.validation.requiresAcceptance
)
if (session.messageIdToUpdate === undefined && tool.type === ToolType.FsRead) {
// Store the first messageId in a chain of tool uses
session.setMessageIdToUpdate(toolUse.toolUseId)
Expand Down Expand Up @@ -748,13 +752,17 @@ export class Messenger {
}

// Handle read tool and list directory messages
if (toolUse?.name === ToolType.FsRead || toolUse?.name === ToolType.ListDirectory) {
if (
(toolUse?.name === ToolType.FsRead || toolUse?.name === ToolType.ListDirectory) &&
!validation.requiresAcceptance
) {
return this.sendReadAndListDirToolMessage(toolUse, session, tabID, triggerID, messageIdToUpdate)
}

// Handle file write tool, execute bash tool and bash command output log messages
const buttons: ChatItemButton[] = []
let header: ChatItemHeader | undefined = undefined
let messageID: string = toolUse?.toolUseId ?? ''
if (toolUse?.name === ToolType.ExecuteBash && message.startsWith('```shell')) {
if (validation.requiresAcceptance) {
const buttons: ChatItemButton[] = [
Expand Down Expand Up @@ -825,6 +833,10 @@ export class Messenger {
}
} else if (toolUse?.name === ToolType.ListDirectory || toolUse?.name === ToolType.FsRead) {
if (validation.requiresAcceptance) {
/** For Read and List Directory tools
* requiredAcceptance = false, we use messageID = toolID and we keep on updating this messageID
* requiredAcceptance = true, IDE sends messageID != toolID, some default value, as this overlaps with previous message. */
messageID = 'toolUse'
const buttons: ChatItemButton[] = [
{
id: 'confirm-tool-use',
Expand All @@ -840,7 +852,6 @@ export class Messenger {
},
]
header = {
body: 'shell',
buttons,
}
}
Expand All @@ -859,7 +870,7 @@ export class Messenger {
followUpsHeader: undefined,
relatedSuggestions: undefined,
triggerID,
messageID: toolUse?.toolUseId ?? '',
messageID,
userIntent: undefined,
codeBlockLanguage: undefined,
contextList: undefined,
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/codewhispererChat/tools/chatStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class ChatStream extends Writable {
private readonly messageIdToUpdate: string | undefined,
// emitEvent decides to show the streaming message or read/list directory tool message to the user.
private readonly emitEvent: boolean,
private readonly validation: CommandValidation,
readonly validation: CommandValidation,
private readonly isReadorList: boolean,
private readonly changeList?: Change[],
private readonly explanation?: string,
Expand All @@ -45,7 +45,13 @@ export class ChatStream extends Writable {
if (this.explanation) {
this.messenger.sendDirectiveMessage(tabID, triggerID, this.explanation)
}
if (validation.requiresAcceptance && this.toolUse?.name === 'executeBash') {
if (validation.requiresAcceptance && isReadorList) {
this.messenger.sendDirectiveMessage(
tabID,
triggerID,
i18n('AWS.amazonq.chat.directive.permission.readAndList')
)
} else if (validation.requiresAcceptance && this.toolUse?.name === 'executeBash') {
this.messenger.sendDirectiveMessage(
tabID,
triggerID,
Expand Down
25 changes: 23 additions & 2 deletions packages/core/src/codewhispererChat/tools/fsRead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fs from '../../shared/fs/fs'
import { Writable } from 'stream'
import { InvokeOutput, OutputKind, sanitizePath, CommandValidation, fsReadToolResponseSize } from './toolShared'
import { isInDirectory } from '../../shared/filesystemUtilities'
import path from 'path'

export interface FsReadParams {
path: string
Expand Down Expand Up @@ -47,8 +48,28 @@ export class FsRead {
this.logger.debug(`Validation succeeded for path: ${this.fsPath}`)
}

public queueDescription(updates: Writable): void {
updates.write('')
public queueDescription(updates: Writable, requiresAcceptance: boolean): void {
if (requiresAcceptance) {
const fileName = path.basename(this.fsPath)
const fileUri = vscode.Uri.file(this.fsPath)
updates.write(`Reading file: [${fileName}](${fileUri}), `)

const [start, end] = this.readRange ?? []

if (start && end) {
updates.write(`from line ${start} to ${end}`)
} else if (start) {
if (start > 0) {
updates.write(`from line ${start} to end of file`)
} else {
updates.write(`${start} line from the end of file to end of file`)
}
} else {
updates.write('all lines')
}
} else {
updates.write('')
}
updates.end()
}

Expand Down
21 changes: 13 additions & 8 deletions packages/core/src/codewhispererChat/tools/listDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ export class ListDirectory {
}
}

public queueDescription(updates: Writable): void {
const fileName = path.basename(this.fsPath)
if (this.maxDepth === undefined) {
updates.write(`Analyzing directories recursively: ${fileName}`)
} else if (this.maxDepth === 0) {
updates.write(`Analyzing directory: ${fileName}`)
public queueDescription(updates: Writable, requiresAcceptance: boolean): void {
if (requiresAcceptance) {
const fileName = path.basename(this.fsPath)
if (this.maxDepth === undefined) {
updates.write(`Analyzing directories recursively: ${fileName}`)
} else if (this.maxDepth === 0) {
updates.write(`Analyzing directory: ${fileName}`)
} else {
const level = this.maxDepth > 1 ? 'levels' : 'level'
updates.write(`Analyzing directory: ${fileName} limited to ${this.maxDepth} subfolder ${level}`)
}
} else {
const level = this.maxDepth > 1 ? 'levels' : 'level'
updates.write(`Analyzing directory: ${fileName} limited to ${this.maxDepth} subfolder ${level}`)
updates.write('')
}

updates.end()
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/codewhispererChat/tools/toolUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import { Writable } from 'stream'
import { FsRead, FsReadParams } from './fsRead'
import { FsWrite, FsWriteParams } from './fsWrite'
Expand All @@ -14,7 +15,6 @@ import {
fsReadToolResponseSize,
} from './toolShared'
import { ListDirectory, ListDirectoryParams } from './listDirectory'
import * as vscode from 'vscode'

export enum ToolType {
FsRead = 'fsRead',
Expand Down Expand Up @@ -95,10 +95,10 @@ export class ToolUtils {
}
}

static async queueDescription(tool: Tool, updates: Writable): Promise<void> {
static async queueDescription(tool: Tool, updates: Writable, requiresAcceptance: boolean): Promise<void> {
switch (tool.type) {
case ToolType.FsRead:
tool.tool.queueDescription(updates)
tool.tool.queueDescription(updates, requiresAcceptance)
break
case ToolType.FsWrite:
await tool.tool.queueDescription(updates)
Expand All @@ -107,7 +107,7 @@ export class ToolUtils {
tool.tool.queueDescription(updates)
break
case ToolType.ListDirectory:
tool.tool.queueDescription(updates)
tool.tool.queueDescription(updates, requiresAcceptance)
break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,30 +234,30 @@ describe('ToolUtils', function () {
// TODO: Adding "void" to the following tests for the current implementation but in the next followup PR I will fix this issue.
it('delegates to FsRead tool queueDescription method', function () {
const tool: Tool = { type: ToolType.FsRead, tool: mockFsRead as unknown as FsRead }
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable)
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable, false)

assert(mockFsRead.queueDescription.calledOnceWith(mockWritable))
assert(mockFsRead.queueDescription.calledOnceWith(mockWritable, false))
})

it('delegates to FsWrite tool queueDescription method', function () {
const tool: Tool = { type: ToolType.FsWrite, tool: mockFsWrite as unknown as FsWrite }
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable)
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable, false)

assert(mockFsWrite.queueDescription.calledOnceWith(mockWritable))
})

it('delegates to ExecuteBash tool queueDescription method', function () {
const tool: Tool = { type: ToolType.ExecuteBash, tool: mockExecuteBash as unknown as ExecuteBash }
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable)
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable, false)

assert(mockExecuteBash.queueDescription.calledOnceWith(mockWritable))
})

it('delegates to ListDirectory tool queueDescription method', function () {
const tool: Tool = { type: ToolType.ListDirectory, tool: mockListDirectory as unknown as ListDirectory }
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable)
void ToolUtils.queueDescription(tool, mockWritable as unknown as Writable, false)

assert(mockListDirectory.queueDescription.calledOnceWith(mockWritable))
assert(mockListDirectory.queueDescription.calledOnceWith(mockWritable, false))
})
})

Expand Down
Loading