Skip to content

Commit ce26f4b

Browse files
committed
Merge branch 'master' into fullyRemoveFSE
2 parents d94b040 + c529794 commit ce26f4b

File tree

11 files changed

+238
-97
lines changed

11 files changed

+238
-97
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Q dev handle no change required"
4+
}

packages/core/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@
273273
"AWS.amazonq.featureDev.error.illegalStateTransition": "Illegal transition between states, restart the conversation",
274274
"AWS.amazonq.featureDev.error.prepareRepoFailedError": "Sorry, I ran into an issue while trying to upload your code. Please try again.",
275275
"AWS.amazonq.featureDev.error.promptRefusalException": "I'm sorry, I can't generate code for your request. Please make sure your message and code files comply with the <a href=\"https://aws.amazon.com/machine-learning/responsible-ai/policy/\">AWS Responsible AI Policy.</a>",
276+
"AWS.amazonq.featureDev.error.noChangeRequiredException": "I’m sorry, I ran into an issue while trying to generate your code.\n\n- `/dev` can generate code to make a change in your project. Provide a detailed description of the new feature or code changes you want to make, including the specifics of what the code should achieve.\n\n- To ask me to explain, debug, or optimize your code, you can close this chat tab to start a new conversation.",
276277
"AWS.amazonq.featureDev.error.zipFileError": "The zip file is corrupted",
277278
"AWS.amazonq.featureDev.error.codeIterationLimitError": "Sorry, you've reached the quota for number of iterations on code generation. You can insert this code in your files or discuss a new plan. For more information on quotas, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html#quotas\" target=\"_blank\">Amazon Q Developer documentation.</a>",
278279
"AWS.amazonq.featureDev.error.tabIdNotFoundError": "I'm sorry, I'm having technical difficulties at the moment. Please try again.",

packages/core/src/amazonq/webview/ui/quickActions/generator.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ export class QuickActionGenerator {
2929
{
3030
command: '/dev',
3131
placeholder: 'Describe your task or issue in as much detail as possible',
32-
description:
33-
'Plan and implement new functionality across multiple files in your workspace.',
32+
description: 'Generate code to make a change in your project',
3433
},
3534
]
3635
: []),

packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ContentLengthError,
1515
FeatureDevServiceError,
1616
MonthlyConversationLimitError,
17+
NoChangeRequiredException,
1718
PrepareRepoFailedError,
1819
PromptRefusalException,
1920
SelectedFolderNotInWorkspaceFolderError,
@@ -131,6 +132,11 @@ export class FeatureDevController {
131132
this.initialExamples(data)
132133
break
133134
case FollowUpTypes.NewTask:
135+
this.messenger.sendAnswer({
136+
type: 'answer',
137+
tabID: data?.tabID,
138+
message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'),
139+
})
134140
return this.newTask(data)
135141
case FollowUpTypes.CloseSession:
136142
return this.closeSession(data)
@@ -246,6 +252,15 @@ export class FeatureDevController {
246252
case ZipFileError.errorName:
247253
this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, true)
248254
break
255+
case NoChangeRequiredException.errorName:
256+
this.messenger.sendAnswer({
257+
type: 'answer',
258+
tabID: message.tabID,
259+
message: err.message,
260+
canBeVoted: true,
261+
})
262+
// Allow users to re-work the task description.
263+
return this.newTask(message)
249264
case CodeIterationLimitError.errorName:
250265
this.messenger.sendAnswer({
251266
type: 'answer',
@@ -319,7 +334,7 @@ export class FeatureDevController {
319334
await this.onCodeGeneration(session, message.message, message.tabID)
320335
}
321336
} catch (err: any) {
322-
this.processErrorChatMessage(err, message, session)
337+
await this.processErrorChatMessage(err, message, session)
323338
// Lock the chat input until they explicitly click one of the follow ups
324339
this.messenger.sendChatInputEnabled(message.tabID, false)
325340
}
@@ -758,11 +773,7 @@ export class FeatureDevController {
758773
// Re-run the opening flow, where we check auth + create a session
759774
await this.tabOpened(message)
760775

761-
this.messenger.sendAnswer({
762-
type: 'answer',
763-
tabID: message.tabID,
764-
message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'),
765-
})
776+
this.messenger.sendChatInputEnabled(message.tabID, true)
766777
this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.describe'))
767778
}
768779

packages/core/src/amazonqFeatureDev/errors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ export class PromptRefusalException extends ToolkitError {
6666
}
6767
}
6868

69+
export class NoChangeRequiredException extends ToolkitError {
70+
static errorName = 'NoChangeRequiredException'
71+
constructor() {
72+
super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
73+
code: 'NoChangeRequiredException',
74+
})
75+
}
76+
}
77+
6978
export class FeatureDevServiceError extends ToolkitError {
7079
static errorName = 'FeatureDevServiceError'
7180
constructor(message: string, code: string) {

packages/core/src/amazonqFeatureDev/session/sessionState.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import { telemetry } from '../../shared/telemetry/telemetry'
1313
import { VirtualFileSystem } from '../../shared/virtualFilesystem'
1414
import { VirtualMemoryFile } from '../../shared/virtualMemoryFile'
1515
import { featureDevScheme } from '../constants'
16-
import { FeatureDevServiceError, IllegalStateTransition, PromptRefusalException } from '../errors'
16+
import {
17+
FeatureDevServiceError,
18+
IllegalStateTransition,
19+
NoChangeRequiredException,
20+
PromptRefusalException,
21+
} from '../errors'
1722
import {
1823
CodeGenerationStatus,
1924
CurrentWsFolders,
@@ -208,6 +213,9 @@ abstract class CodeGenBase {
208213
throw new PromptRefusalException()
209214
}
210215
case codegenResult.codeGenerationStatusDetail?.includes('EmptyPatch'): {
216+
if (codegenResult.codeGenerationStatusDetail?.includes('NO_CHANGE_REQUIRED')) {
217+
throw new NoChangeRequiredException()
218+
}
211219
throw new FeatureDevServiceError(
212220
i18n('AWS.amazonq.featureDev.error.codeGen.default'),
213221
'EmptyPatchException'

packages/core/src/shared/telemetry/vscodeTelemetry.json

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,22 +1157,6 @@
11571157
"name": "function_call",
11581158
"description": "Represents a function call. In most cases this should wrap code with a run(), then you can add context.",
11591159
"metadata": [
1160-
{
1161-
"type": "userCpuUsage",
1162-
"required": false
1163-
},
1164-
{
1165-
"type": "systemCpuUsage",
1166-
"required": false
1167-
},
1168-
{
1169-
"type": "architecture",
1170-
"required": false
1171-
},
1172-
{
1173-
"type": "heapTotal",
1174-
"required": false
1175-
},
11761160
{
11771161
"type": "functionName",
11781162
"required": true

packages/core/src/shared/utilities/workspaceUtils.ts

Lines changed: 55 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { sanitizeFilename } from './textUtilities'
1717
import { GitIgnoreAcceptor } from '@gerhobbelt/gitignore-parser'
1818
import * as parser from '@gerhobbelt/gitignore-parser'
1919
import fs from '../fs/fs'
20-
import { telemetry } from '../telemetry'
2120

2221
type GitIgnoreRelativeAcceptor = {
2322
folderPath: string
@@ -323,80 +322,70 @@ export async function collectFiles(
323322
zipFilePath: string
324323
}[]
325324
> {
326-
return telemetry.function_call.run(
327-
async (span) => {
328-
const storage: Awaited<ReturnType<typeof collectFiles>> = []
329-
330-
const workspaceFoldersMapping = getWorkspaceFoldersByPrefixes(workspaceFolders)
331-
const workspaceToPrefix = new Map<vscode.WorkspaceFolder, string>(
332-
workspaceFoldersMapping === undefined
333-
? [[workspaceFolders[0], '']]
334-
: Object.entries(workspaceFoldersMapping).map((value) => [value[1], value[0]])
335-
)
336-
const prefixWithFolderPrefix = (folder: vscode.WorkspaceFolder, path: string) => {
337-
const prefix = workspaceToPrefix.get(folder)
338-
/**
339-
* collects all files that are marked as source
340-
* @param sourcePaths the paths where collection starts
341-
* @param workspaceFolders the current workspace folders opened
342-
* @param respectGitIgnore whether to respect gitignore file
343-
* @returns all matched files
344-
*/
345-
if (prefix === undefined) {
346-
throw new ToolkitError(`Failed to find prefix for workspace folder ${folder.name}`)
347-
}
348-
return prefix === '' ? path : `${prefix}/${path}`
349-
}
325+
const storage: Awaited<ReturnType<typeof collectFiles>> = []
350326

351-
let totalSizeBytes = 0
352-
for (const rootPath of sourcePaths) {
353-
const allFiles = await vscode.workspace.findFiles(
354-
new vscode.RelativePattern(rootPath, '**'),
355-
getExcludePattern()
356-
)
327+
const workspaceFoldersMapping = getWorkspaceFoldersByPrefixes(workspaceFolders)
328+
const workspaceToPrefix = new Map<vscode.WorkspaceFolder, string>(
329+
workspaceFoldersMapping === undefined
330+
? [[workspaceFolders[0], '']]
331+
: Object.entries(workspaceFoldersMapping).map((value) => [value[1], value[0]])
332+
)
333+
const prefixWithFolderPrefix = (folder: vscode.WorkspaceFolder, path: string) => {
334+
const prefix = workspaceToPrefix.get(folder)
335+
/**
336+
* collects all files that are marked as source
337+
* @param sourcePaths the paths where collection starts
338+
* @param workspaceFolders the current workspace folders opened
339+
* @param respectGitIgnore whether to respect gitignore file
340+
* @returns all matched files
341+
*/
342+
if (prefix === undefined) {
343+
throw new ToolkitError(`Failed to find prefix for workspace folder ${folder.name}`)
344+
}
345+
return prefix === '' ? path : `${prefix}/${path}`
346+
}
357347

358-
const files = respectGitIgnore ? await filterOutGitignoredFiles(rootPath, allFiles) : allFiles
348+
let totalSizeBytes = 0
349+
for (const rootPath of sourcePaths) {
350+
const allFiles = await vscode.workspace.findFiles(
351+
new vscode.RelativePattern(rootPath, '**'),
352+
getExcludePattern()
353+
)
359354

360-
for (const file of files) {
361-
const relativePath = getWorkspaceRelativePath(file.fsPath, { workspaceFolders })
362-
if (!relativePath) {
363-
continue
364-
}
355+
const files = respectGitIgnore ? await filterOutGitignoredFiles(rootPath, allFiles) : allFiles
365356

366-
const fileStat = await fs.stat(file)
367-
if (totalSizeBytes + fileStat.size > maxSize) {
368-
throw new ToolkitError(
369-
'The project you have selected for source code is too large to use as context. Please select a different folder to use',
370-
{ code: 'ContentLengthError' }
371-
)
372-
}
357+
for (const file of files) {
358+
const relativePath = getWorkspaceRelativePath(file.fsPath, { workspaceFolders })
359+
if (!relativePath) {
360+
continue
361+
}
373362

374-
const fileContent = await readFile(file)
363+
const fileStat = await fs.stat(file)
364+
if (totalSizeBytes + fileStat.size > maxSize) {
365+
throw new ToolkitError(
366+
'The project you have selected for source code is too large to use as context. Please select a different folder to use',
367+
{ code: 'ContentLengthError' }
368+
)
369+
}
375370

376-
if (fileContent === undefined) {
377-
continue
378-
}
371+
const fileContent = await readFile(file)
379372

380-
// Now that we've read the file, increase our usage
381-
totalSizeBytes += fileStat.size
382-
storage.push({
383-
workspaceFolder: relativePath.workspaceFolder,
384-
relativeFilePath: relativePath.relativePath,
385-
fileUri: file,
386-
fileContent: fileContent,
387-
zipFilePath: prefixWithFolderPrefix(relativePath.workspaceFolder, relativePath.relativePath),
388-
})
389-
}
373+
if (fileContent === undefined) {
374+
continue
390375
}
391-
return storage
392-
},
393-
{
394-
emit: true,
395-
functionId: {
396-
name: 'collectFiles',
397-
},
376+
377+
// Now that we've read the file, increase our usage
378+
totalSizeBytes += fileStat.size
379+
storage.push({
380+
workspaceFolder: relativePath.workspaceFolder,
381+
relativeFilePath: relativePath.relativePath,
382+
fileUri: file,
383+
fileContent: fileContent,
384+
zipFilePath: prefixWithFolderPrefix(relativePath.workspaceFolder, relativePath.relativePath),
385+
})
398386
}
399-
)
387+
}
388+
return storage
400389
}
401390

402391
const readFile = async (file: vscode.Uri) => {

packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ import { CurrentWsFolders, FollowUpTypes, NewFileInfo, DeletedFileInfo } from '.
1313
import { Session } from '../../../../amazonqFeatureDev/session/session'
1414
import { Prompter } from '../../../../shared/ui/prompter'
1515
import { assertTelemetry, toFile } from '../../../testUtil'
16-
import { SelectedFolderNotInWorkspaceFolderError } from '../../../../amazonqFeatureDev/errors'
16+
import {
17+
NoChangeRequiredException,
18+
SelectedFolderNotInWorkspaceFolderError,
19+
} from '../../../../amazonqFeatureDev/errors'
1720
import { CodeGenState, PrepareCodeGenState } from '../../../../amazonqFeatureDev/session/sessionState'
1821
import { FeatureDevClient } from '../../../../amazonqFeatureDev/client/featureDev'
1922
import { createAmazonQUri } from '../../../../amazonq/commons/diff'
23+
import { AuthUtil } from '../../../../codewhisperer'
2024

2125
let mockGetCodeGeneration: sinon.SinonStub
2226
describe('Controller', () => {
@@ -68,6 +72,12 @@ describe('Controller', () => {
6872
beforeEach(async () => {
6973
controllerSetup = await createController()
7074
session = await createSession({ messenger: controllerSetup.messenger, conversationID, tabID, uploadID })
75+
76+
sinon.stub(AuthUtil.instance, 'getChatAuthState').resolves({
77+
codewhispererCore: 'connected',
78+
codewhispererChat: 'connected',
79+
amazonQ: 'connected',
80+
})
7181
})
7282

7383
afterEach(() => {
@@ -357,4 +367,41 @@ describe('Controller', () => {
357367
})
358368
})
359369
})
370+
371+
describe('processUserChatMessage', function () {
372+
async function fireChatMessage() {
373+
const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
374+
375+
controllerSetup.emitters.processHumanChatMessage.fire({
376+
tabID,
377+
conversationID,
378+
message: 'test message',
379+
})
380+
381+
// Wait until the controller has time to process the event
382+
await waitUntil(() => {
383+
return Promise.resolve(getSessionStub.callCount > 0)
384+
}, {})
385+
}
386+
387+
describe('processErrorChatMessage', function () {
388+
it('should handle NoChangeRequiredException', async function () {
389+
const noChangeRequiredException = new NoChangeRequiredException()
390+
sinon.stub(session, 'preloader').throws(noChangeRequiredException)
391+
const sendAnswerSpy = sinon.stub(controllerSetup.messenger, 'sendAnswer')
392+
393+
await fireChatMessage()
394+
395+
assert.strictEqual(
396+
sendAnswerSpy.calledWith({
397+
type: 'answer',
398+
tabID,
399+
message: noChangeRequiredException.message,
400+
canBeVoted: true,
401+
}),
402+
true
403+
)
404+
})
405+
})
406+
})
360407
})

packages/core/src/testInteg/shared/utilities/workspaceUtils.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { getTestWorkspaceFolder } from '../../integrationTestsUtilities'
1717
import globals from '../../../shared/extensionGlobals'
1818
import { CodelensRootRegistry } from '../../../shared/fs/codelensRootRegistry'
19-
import { assertTelemetry, createTestWorkspace, createTestWorkspaceFolder, toFile } from '../../../test/testUtil'
19+
import { createTestWorkspace, createTestWorkspaceFolder, toFile } from '../../../test/testUtil'
2020
import sinon from 'sinon'
2121
import { performanceTest } from '../../../shared/performance/performance'
2222
import { randomUUID } from '../../../shared/crypto'
@@ -298,10 +298,6 @@ describe('collectFiles', function () {
298298
] satisfies typeof result,
299299
result
300300
)
301-
302-
assertTelemetry('function_call', {
303-
functionName: 'collectFiles',
304-
})
305301
})
306302

307303
it('does not return license files', async function () {

0 commit comments

Comments
 (0)