Skip to content

Commit bcdfcdd

Browse files
authored
fix(amazonq): enable input after prompt is rejected, prevent iteration limit exceeded (aws#6395)
## Problem When a user encounters an error while updating a README, they see a "Retry" button, which isn't correct because the previous prompt is not retried. Instead, the user should be able to input a new prompt. When a user runs out of iterations in a session, the chat allows them to continue making README update requests, which results in an "Iteration limit exceeded error". ## Solution If a user gets an error while updating a README, the chat input is enabled and the user can try another prompt. Once the user hits their iteration limit for the session, they are not able to suggest more changes and have to accept/reject the existing set of changes. This will reduce the "iteration limit exceeded" errors encountered by users. A E2E test is added to verify that prompt errors during README updates are handled correctly with the relevant error message, and validates that users can enter a new prompt. Screenshots: _Prompt enabled after error updating README_ <img width="520" alt="image" src="https://github.com/user-attachments/assets/407109d2-3f10-4e8c-9312-ecab57e625b4" /> _'Make changes' button hidden once iteration limit is reached_ <img width="506" alt="image" src="https://github.com/user-attachments/assets/acf960c0-1c43-475c-a478-c95336ab857b" /> --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent d7bfb5d commit bcdfcdd

File tree

10 files changed

+149
-130
lines changed

10 files changed

+149
-130
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Amazon Q /doc: Prevent users from requesting changes if no iterations remain"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Amazon Q /doc: Ask for user prompt if error occurs while updating documentation"
4+
}

packages/amazonq/test/e2e/amazonq/doc.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,33 @@ describe('Amazon Q Doc', async function () {
146146
FollowUpTypes.RejectChanges,
147147
])
148148
})
149+
150+
it('Handle unrelated prompt error', async () => {
151+
await tab.waitForButtons([FollowUpTypes.UpdateDocumentation])
152+
153+
tab.clickButton(FollowUpTypes.UpdateDocumentation)
154+
155+
await tab.waitForButtons([FollowUpTypes.SynchronizeDocumentation, FollowUpTypes.EditDocumentation])
156+
157+
tab.clickButton(FollowUpTypes.EditDocumentation)
158+
159+
await tab.waitForButtons([FollowUpTypes.ProceedFolderSelection])
160+
161+
tab.clickButton(FollowUpTypes.ProceedFolderSelection)
162+
163+
tab.addChatMessage({ prompt: 'tell me about the weather' })
164+
165+
await tab.waitForEvent(() =>
166+
tab.getChatItems().some(({ body }) => body?.startsWith(i18n('AWS.amazonq.doc.error.promptUnrelated')))
167+
)
168+
169+
await tab.waitForEvent(() => {
170+
const store = tab.getStore()
171+
return (
172+
!store.promptInputDisabledState &&
173+
store.promptInputPlaceholder === i18n('AWS.amazonq.doc.placeholder.editReadme')
174+
)
175+
})
176+
})
149177
})
150178
})

packages/core/package.nls.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@
376376
"AWS.amazonq.doc.answer.readmeCreated": "I've created a README for your code.",
377377
"AWS.amazonq.doc.answer.readmeUpdated": "I've updated your README.",
378378
"AWS.amazonq.doc.answer.codeResult": "You can accept the changes to your files, or describe any additional changes you'd like me to make.",
379+
"AWS.amazonq.doc.answer.acceptOrReject": "You can accept or reject the changes to your files.",
379380
"AWS.amazonq.doc.answer.scanning": "Scanning source files",
380381
"AWS.amazonq.doc.answer.summarizing": "Summarizing source files",
381382
"AWS.amazonq.doc.answer.generating": "Generating documentation",
@@ -385,7 +386,7 @@
385386
"AWS.amazonq.doc.error.noFolderSelected": "It looks like you didn't choose a folder. Choose a folder to continue.",
386387
"AWS.amazonq.doc.error.contentLengthError": "Your workspace is too large for me to review. Your workspace must be within the quota, even if you choose a smaller folder. For more information on quotas, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html#quotas\" target=\"_blank\">Amazon Q Developer documentation.</a>",
387388
"AWS.amazonq.doc.error.readmeTooLarge": "The README in your folder is too large for me to review. Try reducing the size of your README, or choose a folder with a smaller README. For more information on quotas, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html#quotas\" target=\"_blank\">Amazon Q Developer documentation.</a>",
388-
"AWS.amazonq.doc.error.readmeUpdateTooLarge": "The updated README is too large. Try reducing the size of your README, or asking for a smaller update. For more information on quotas, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html#quotas\" target=\"_blank\">Amazon Q Developer documentation.</a>",
389+
"AWS.amazonq.doc.error.readmeUpdateTooLarge": "The updated README exceeds document size limits. Try reducing the size of your current README or working on a smaller task that won't produce as much content. For more information on quotas, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html#quotas\" target=\"_blank\">Amazon Q Developer documentation.</a>",
389390
"AWS.amazonq.doc.error.workspaceEmpty": "The folder you chose did not contain any source files in a supported language. Choose another folder and try again. For more information on supported languages, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html\" target=\"_blank\">Amazon Q Developer documentation.</a>",
390391
"AWS.amazonq.doc.error.promptTooVague": "I need more information to make changes to your README. Try providing some of the following details:\n- Which sections you want to modify\n- The content you want to add or remove\n- Specific issues that need correcting\n\nFor more information on prompt best practices, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html\" target=\"_blank\">Amazon Q Developer documentation.</a>",
391392
"AWS.amazonq.doc.error.promptUnrelated": "These changes don't seem related to documentation. Try describing your changes again, using the following best practices:\n- Changes should relate to how project functionality is reflected in the README\n- Content you refer to should be available in your codebase\n\n For more information on prompt best practices, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html\" target=\"_blank\">Amazon Q Developer documentation.</a>",
@@ -397,6 +398,9 @@
397398
"AWS.amazonq.doc.pillText.newTask": "Start a new documentation task",
398399
"AWS.amazonq.doc.pillText.update": "Update README to reflect code",
399400
"AWS.amazonq.doc.pillText.makeChange": "Make a specific change",
401+
"AWS.amazonq.doc.pillText.accept": "Accept",
402+
"AWS.amazonq.doc.pillText.reject": "Reject",
403+
"AWS.amazonq.doc.pillText.makeChanges": "Make changes",
400404
"AWS.amazonq.inline.invokeChat": "Inline chat",
401405
"AWS.toolkit.lambda.walkthrough.quickpickTitle": "Application Builder Walkthrough",
402406
"AWS.toolkit.lambda.walkthrough.title": "Get started building your application",

packages/core/src/amazonq/commons/connector/baseMessenger.ts

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class Messenger {
8585
type: 'answer',
8686
tabID: tabID,
8787
message: i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
88+
disableChatInput: true,
8889
})
8990
this.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.placeholder.chatInputDisabled'))
9091
}

packages/core/src/amazonqDoc/constants.ts

+37
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,43 @@ export const FolderSelectorFollowUps = [
9292
},
9393
]
9494

95+
export const CodeChangeFollowUps = [
96+
{
97+
pillText: i18n('AWS.amazonq.doc.pillText.accept'),
98+
prompt: i18n('AWS.amazonq.doc.pillText.accept'),
99+
type: FollowUpTypes.AcceptChanges,
100+
icon: 'ok' as MynahIcons,
101+
status: 'success' as Status,
102+
},
103+
{
104+
pillText: i18n('AWS.amazonq.doc.pillText.makeChanges'),
105+
prompt: i18n('AWS.amazonq.doc.pillText.makeChanges'),
106+
type: FollowUpTypes.MakeChanges,
107+
icon: 'refresh' as MynahIcons,
108+
status: 'info' as Status,
109+
},
110+
{
111+
pillText: i18n('AWS.amazonq.doc.pillText.reject'),
112+
prompt: i18n('AWS.amazonq.doc.pillText.reject'),
113+
type: FollowUpTypes.RejectChanges,
114+
icon: 'cancel' as MynahIcons,
115+
status: 'error' as Status,
116+
},
117+
]
118+
119+
export const NewSessionFollowUps = [
120+
{
121+
pillText: i18n('AWS.amazonq.doc.pillText.newTask'),
122+
type: FollowUpTypes.NewTask,
123+
status: 'info' as Status,
124+
},
125+
{
126+
pillText: i18n('AWS.amazonq.doc.pillText.closeSession'),
127+
type: FollowUpTypes.CloseSession,
128+
status: 'info' as Status,
129+
},
130+
]
131+
95132
export const SynchronizeDocumentation = {
96133
pillText: i18n('AWS.amazonq.doc.pillText.update'),
97134
prompt: i18n('AWS.amazonq.doc.pillText.update'),

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

+29-71
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
EditDocumentation,
1111
FolderSelectorFollowUps,
1212
Mode,
13+
NewSessionFollowUps,
1314
SynchronizeDocumentation,
15+
CodeChangeFollowUps,
1416
docScheme,
1517
featureName,
1618
findReadmePath,
@@ -22,7 +24,6 @@ import { Session } from '../../session/session'
2224
import { i18n } from '../../../shared/i18n-helper'
2325
import path from 'path'
2426
import { createSingleFileDialog } from '../../../shared/ui/common/openDialog'
25-
import { MynahIcons } from '@aws/mynah-ui'
2627

2728
import {
2829
MonthlyConversationLimitError,
@@ -298,18 +299,7 @@ export class DocController {
298299
tabID: data?.tabID,
299300
disableChatInput: true,
300301
message: 'Your changes have been discarded.',
301-
followUps: [
302-
{
303-
pillText: i18n('AWS.amazonq.doc.pillText.newTask'),
304-
type: FollowUpTypes.NewTask,
305-
status: 'info',
306-
},
307-
{
308-
pillText: i18n('AWS.amazonq.doc.pillText.closeSession'),
309-
type: FollowUpTypes.CloseSession,
310-
status: 'info',
311-
},
312-
],
302+
followUps: NewSessionFollowUps,
313303
})
314304
break
315305
case FollowUpTypes.ProceedFolderSelection:
@@ -412,13 +402,19 @@ export class DocController {
412402
const errorMessage = createUserFacingErrorMessage(`${err.cause?.message ?? err.message}`)
413403
// eslint-disable-next-line unicorn/no-null
414404
this.messenger.sendUpdatePromptProgress(message.tabID, null)
405+
if (err.constructor.name === MonthlyConversationLimitError.name) {
406+
this.messenger.sendMonthlyLimitError(message.tabID)
407+
} else {
408+
const enableUserInput = this.mode === Mode.EDIT && err.remainingIterations > 0
415409

416-
switch (err.constructor.name) {
417-
case MonthlyConversationLimitError.name:
418-
this.messenger.sendMonthlyLimitError(message.tabID)
419-
break
420-
default:
421-
this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, false)
410+
this.messenger.sendErrorMessage(
411+
errorMessage,
412+
message.tabID,
413+
0,
414+
session?.conversationIdUnsafe,
415+
false,
416+
enableUserInput
417+
)
422418
}
423419
}
424420

@@ -427,8 +423,6 @@ export class DocController {
427423
await this.onDocsGeneration(session, message.message, message.tabID)
428424
} catch (err: any) {
429425
this.processErrorChatMessage(err, message, session)
430-
// Lock the chat input until they explicitly click one of the follow ups
431-
this.messenger.sendChatInputEnabled(message.tabID, false)
432426
}
433427
}
434428

@@ -461,12 +455,8 @@ export class DocController {
461455
}
462456

463457
await this.generateDocumentation({ message, session })
464-
this.messenger.sendChatInputEnabled(message?.tabID, false)
465-
this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption'))
466458
} catch (err: any) {
467459
this.processErrorChatMessage(err, message, session)
468-
// Lock the chat input until they explicitly click one of the follow ups
469-
this.messenger.sendChatInputEnabled(message.tabID, false)
470460
}
471461
}
472462

@@ -590,40 +580,21 @@ export class DocController {
590580
this.messenger.sendAnswer({
591581
type: 'answer',
592582
tabID: tabID,
593-
message: `${this.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${i18n('AWS.amazonq.doc.answer.codeResult')}`,
583+
message: `${this.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${remainingIterations > 0 ? i18n('AWS.amazonq.doc.answer.codeResult') : i18n('AWS.amazonq.doc.answer.acceptOrReject')}`,
594584
disableChatInput: true,
595585
})
596-
}
597586

598-
this.messenger.sendAnswer({
599-
message: undefined,
600-
type: 'system-prompt',
601-
disableChatInput: true,
602-
followUps: [
603-
{
604-
pillText: 'Accept',
605-
prompt: 'Accept',
606-
type: FollowUpTypes.AcceptChanges,
607-
icon: 'ok' as MynahIcons,
608-
status: 'success',
609-
},
610-
{
611-
pillText: 'Make changes',
612-
prompt: 'Make changes',
613-
type: FollowUpTypes.MakeChanges,
614-
icon: 'refresh' as MynahIcons,
615-
status: 'info',
616-
},
617-
{
618-
pillText: 'Reject',
619-
prompt: 'Reject',
620-
type: FollowUpTypes.RejectChanges,
621-
icon: 'cancel' as MynahIcons,
622-
status: 'error',
623-
},
624-
],
625-
tabID: tabID,
626-
})
587+
this.messenger.sendAnswer({
588+
message: undefined,
589+
type: 'system-prompt',
590+
disableChatInput: true,
591+
followUps:
592+
remainingIterations > 0
593+
? CodeChangeFollowUps
594+
: CodeChangeFollowUps.filter((followUp) => followUp.type !== FollowUpTypes.MakeChanges),
595+
tabID: tabID,
596+
})
597+
}
627598
} finally {
628599
if (session?.state?.tokenSource?.token.isCancellationRequested) {
629600
await this.newTask({ tabID })
@@ -642,10 +613,8 @@ export class DocController {
642613
type: 'answer',
643614
tabID: message.tabID,
644615
message: 'Follow instructions to re-authenticate ...',
616+
disableChatInput: true,
645617
})
646-
647-
// Explicitly ensure the user goes through the re-authenticate flow
648-
this.messenger.sendChatInputEnabled(message.tabID, false)
649618
}
650619

651620
private tabClosed(message: any) {
@@ -670,18 +639,7 @@ export class DocController {
670639
type: 'answer',
671640
disableChatInput: true,
672641
tabID: message.tabID,
673-
followUps: [
674-
{
675-
pillText: i18n('AWS.amazonq.doc.pillText.newTask'),
676-
type: FollowUpTypes.NewTask,
677-
status: 'info',
678-
},
679-
{
680-
pillText: i18n('AWS.amazonq.doc.pillText.closeSession'),
681-
type: FollowUpTypes.CloseSession,
682-
status: 'info',
683-
},
684-
],
642+
followUps: NewSessionFollowUps,
685643
})
686644

687645
this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption'))

packages/core/src/amazonqDoc/errors.ts

+23-35
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,57 @@ import { ToolkitError } from '../shared/errors'
77
import { i18n } from '../shared/i18n-helper'
88

99
export class DocServiceError extends ToolkitError {
10-
constructor(message: string, code: string) {
10+
remainingIterations?: number
11+
constructor(message: string, code: string, remainingIterations?: number) {
1112
super(message, { code })
13+
this.remainingIterations = remainingIterations
1214
}
1315
}
1416

15-
export class ReadmeTooLargeError extends ToolkitError {
17+
export class ReadmeTooLargeError extends DocServiceError {
1618
constructor() {
17-
super(i18n('AWS.amazonq.doc.error.readmeTooLarge'), {
18-
code: ReadmeTooLargeError.name,
19-
})
19+
super(i18n('AWS.amazonq.doc.error.readmeTooLarge'), ReadmeTooLargeError.name)
2020
}
2121
}
2222

23-
export class ReadmeUpdateTooLargeError extends ToolkitError {
24-
constructor() {
25-
super(i18n('AWS.amazonq.doc.error.readmeUpdateTooLarge'), {
26-
code: ReadmeUpdateTooLargeError.name,
27-
})
23+
export class ReadmeUpdateTooLargeError extends DocServiceError {
24+
constructor(remainingIterations: number) {
25+
super(i18n('AWS.amazonq.doc.error.readmeUpdateTooLarge'), ReadmeUpdateTooLargeError.name, remainingIterations)
2826
}
2927
}
3028

31-
export class WorkspaceEmptyError extends ToolkitError {
29+
export class WorkspaceEmptyError extends DocServiceError {
3230
constructor() {
33-
super(i18n('AWS.amazonq.doc.error.workspaceEmpty'), {
34-
code: WorkspaceEmptyError.name,
35-
})
31+
super(i18n('AWS.amazonq.doc.error.workspaceEmpty'), WorkspaceEmptyError.name)
3632
}
3733
}
3834

39-
export class NoChangeRequiredException extends ToolkitError {
35+
export class NoChangeRequiredException extends DocServiceError {
4036
constructor() {
41-
super(i18n('AWS.amazonq.doc.error.noChangeRequiredException'), {
42-
code: NoChangeRequiredException.name,
43-
})
37+
super(i18n('AWS.amazonq.doc.error.noChangeRequiredException'), NoChangeRequiredException.name)
4438
}
4539
}
4640

47-
export class PromptRefusalException extends ToolkitError {
48-
constructor() {
49-
super(i18n('AWS.amazonq.doc.error.promptRefusal'), {
50-
code: PromptRefusalException.name,
51-
})
41+
export class PromptRefusalException extends DocServiceError {
42+
constructor(remainingIterations: number) {
43+
super(i18n('AWS.amazonq.doc.error.promptRefusal'), PromptRefusalException.name, remainingIterations)
5244
}
5345
}
5446

55-
export class ContentLengthError extends ToolkitError {
47+
export class ContentLengthError extends DocServiceError {
5648
constructor() {
57-
super(i18n('AWS.amazonq.doc.error.contentLengthError'), { code: ContentLengthError.name })
49+
super(i18n('AWS.amazonq.doc.error.contentLengthError'), ContentLengthError.name)
5850
}
5951
}
6052

61-
export class PromptTooVagueError extends ToolkitError {
62-
constructor() {
63-
super(i18n('AWS.amazonq.doc.error.promptTooVague'), {
64-
code: PromptTooVagueError.name,
65-
})
53+
export class PromptTooVagueError extends DocServiceError {
54+
constructor(remainingIterations: number) {
55+
super(i18n('AWS.amazonq.doc.error.promptTooVague'), PromptTooVagueError.name, remainingIterations)
6656
}
6757
}
6858

69-
export class PromptUnrelatedError extends ToolkitError {
70-
constructor() {
71-
super(i18n('AWS.amazonq.doc.error.promptUnrelated'), {
72-
code: PromptUnrelatedError.name,
73-
})
59+
export class PromptUnrelatedError extends DocServiceError {
60+
constructor(remainingIterations: number) {
61+
super(i18n('AWS.amazonq.doc.error.promptUnrelated'), PromptUnrelatedError.name, remainingIterations)
7462
}
7563
}

0 commit comments

Comments
 (0)