Skip to content

Commit 1ca10f2

Browse files
authored
feat(amazonq): discard edit suggestions if it's displayed less than 1sec when a completion arrives (aws#7950)
## Problem ## Solution --- - 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 b9ce873 commit 1ca10f2

File tree

6 files changed

+76
-31
lines changed

6 files changed

+76
-31
lines changed

package-lock.json

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/amazonq/src/app/inline/EditRendering/displayImage.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class EditDecorationManager {
2424
private currentImageDecoration: vscode.DecorationOptions | undefined
2525
private currentRemovedCodeDecorations: vscode.DecorationOptions[] = []
2626
private acceptHandler: (() => void) | undefined
27-
private rejectHandler: (() => void) | undefined
27+
private rejectHandler: ((isDiscard: boolean) => void) | undefined
2828

2929
constructor() {
3030
this.registerCommandHandlers()
@@ -131,7 +131,7 @@ export class EditDecorationManager {
131131
svgImage: vscode.Uri,
132132
startLine: number,
133133
onAccept: () => Promise<void>,
134-
onReject: () => Promise<void>,
134+
onReject: (isDiscard: boolean) => Promise<void>,
135135
originalCode: string,
136136
newCode: string,
137137
originalCodeHighlightRanges: Array<{ line: number; start: number; end: number }>
@@ -185,9 +185,9 @@ export class EditDecorationManager {
185185
})
186186

187187
// Register Esc key handler for rejecting suggestion
188-
vscode.commands.registerCommand('aws.amazonq.inline.rejectEdit', () => {
188+
vscode.commands.registerCommand('aws.amazonq.inline.rejectEdit', (isDiscard: boolean = false) => {
189189
if (this.rejectHandler) {
190-
this.rejectHandler()
190+
this.rejectHandler(isDiscard)
191191
}
192192
})
193193
}
@@ -416,20 +416,31 @@ export async function displaySvgDecoration(
416416
// )
417417
// }
418418
},
419-
async () => {
419+
async (isDiscard: boolean) => {
420420
// Handle reject
421-
getLogger().info('Edit suggestion rejected')
421+
if (isDiscard) {
422+
getLogger().info('Edit suggestion discarded')
423+
} else {
424+
getLogger().info('Edit suggestion rejected')
425+
}
422426
await decorationManager.clearDecorations(editor)
423427
documentChangeListener.dispose()
424428
cursorChangeListener.dispose()
429+
const suggestionState = isDiscard
430+
? {
431+
seen: false,
432+
accepted: false,
433+
discarded: true,
434+
}
435+
: {
436+
seen: true,
437+
accepted: false,
438+
discarded: false,
439+
}
425440
const params: LogInlineCompletionSessionResultsParams = {
426441
sessionId: session.sessionId,
427442
completionSessionResult: {
428-
[item.itemId]: {
429-
seen: true,
430-
accepted: false,
431-
discarded: false,
432-
},
443+
[item.itemId]: suggestionState,
433444
},
434445
totalSessionDisplayTime: Date.now() - session.requestStartTime,
435446
firstCompletionDisplayLatency: session.firstCompletionDisplayLatency,

packages/amazonq/src/app/inline/editSuggestionState.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@
88
*/
99
export class EditSuggestionState {
1010
private static isEditSuggestionCurrentlyActive = false
11+
private static displayStartTime = performance.now()
1112

1213
static setEditSuggestionActive(active: boolean): void {
1314
this.isEditSuggestionCurrentlyActive = active
15+
if (active) {
16+
this.displayStartTime = performance.now()
17+
}
1418
}
1519

1620
static isEditSuggestionActive(): boolean {
1721
return this.isEditSuggestionCurrentlyActive
1822
}
23+
24+
static isEditSuggestionDisplayingOverOneSecond(): boolean {
25+
return this.isEditSuggestionActive() && performance.now() - this.displayStartTime > 1000
26+
}
1927
}

packages/amazonq/src/app/inline/recommendationService.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {
88
inlineCompletionWithReferencesRequestType,
99
TextDocumentContentChangeEvent,
1010
editCompletionRequestType,
11+
LogInlineCompletionSessionResultsParams,
1112
} from '@aws/language-server-runtimes/protocol'
12-
import { CancellationToken, InlineCompletionContext, Position, TextDocument } from 'vscode'
13+
import { CancellationToken, InlineCompletionContext, Position, TextDocument, commands } from 'vscode'
1314
import { LanguageClient } from 'vscode-languageclient'
1415
import { SessionManager } from './sessionManager'
1516
import {
@@ -24,8 +25,8 @@ import { getLogger } from 'aws-core-vscode/shared'
2425
import { DocumentEventListener } from './documentEventListener'
2526
import { getOpenFilesInWindow } from 'aws-core-vscode/utils'
2627
import { asyncCallWithTimeout } from '../../util/timeoutUtil'
27-
import { EditSuggestionState } from './editSuggestionState'
2828
import { extractFileContextInNotebooks } from './notebookUtil'
29+
import { EditSuggestionState } from './editSuggestionState'
2930

3031
export interface GetAllRecommendationsOptions {
3132
emitTelemetry?: boolean
@@ -137,7 +138,7 @@ export class RecommendationService {
137138
* Completions use PartialResultToken with single 1 call of [getAllRecommendations].
138139
* Edits leverage partialResultToken to achieve EditStreak such that clients can pull all continuous suggestions generated by the model within 1 EOS block.
139140
*/
140-
if (!isTriggerByDeletion && !request.partialResultToken && !EditSuggestionState.isEditSuggestionActive()) {
141+
if (!isTriggerByDeletion && !request.partialResultToken) {
141142
const completionPromise: Promise<InlineCompletionListWithReferences> = languageClient.sendRequest(
142143
inlineCompletionWithReferencesRequestType.method,
143144
request,
@@ -187,6 +188,39 @@ export class RecommendationService {
187188
})),
188189
})
189190

191+
if (result.items.length > 0 && result.items[0].isInlineEdit === false) {
192+
// Completion will not be rendered if an edit suggestion has been active for longer than 1 second
193+
if (EditSuggestionState.isEditSuggestionDisplayingOverOneSecond()) {
194+
const session = this.sessionManager.getActiveSession()
195+
if (!session) {
196+
return []
197+
}
198+
const params: LogInlineCompletionSessionResultsParams = {
199+
sessionId: session.sessionId,
200+
completionSessionResult: Object.fromEntries(
201+
result.items.map((item) => [
202+
item.itemId,
203+
{
204+
seen: false,
205+
accepted: false,
206+
discarded: true,
207+
},
208+
])
209+
),
210+
}
211+
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)
212+
this.sessionManager.clear()
213+
getLogger().info(
214+
'Completion discarded due to active edit suggestion displayed longer than 1 second'
215+
)
216+
return []
217+
} else if (EditSuggestionState.isEditSuggestionActive()) {
218+
// discard the current edit suggestion if its display time is less than 1 sec
219+
await commands.executeCommand('aws.amazonq.inline.rejectEdit', true)
220+
getLogger().info('Discarding active edit suggestion displaying less than 1 second')
221+
}
222+
}
223+
190224
TelemetryHelper.instance.setSdkApiCallEndTime()
191225
TelemetryHelper.instance.setSessionId(result.sessionId)
192226
if (result.items.length > 0 && result.items[0].itemId !== undefined) {

packages/amazonq/test/unit/amazonq/apps/inline/recommendationService.test.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,9 @@ describe('RecommendationService', () => {
333333
}
334334
})
335335

336-
it('should not make completion request when edit suggestion is active', async () => {
336+
it('should make completion request when edit suggestion is active', async () => {
337337
// Mock EditSuggestionState to return true (edit suggestion is active)
338-
const isEditSuggestionActiveStub = sandbox.stub(EditSuggestionState, 'isEditSuggestionActive').returns(true)
338+
sandbox.stub(EditSuggestionState, 'isEditSuggestionActive').returns(true)
339339

340340
const mockResult = {
341341
sessionId: 'test-session',
@@ -360,20 +360,13 @@ describe('RecommendationService', () => {
360360
const completionCalls = cs.filter((c) => c.firstArg === completionApi)
361361
const editCalls = cs.filter((c) => c.firstArg === editApi)
362362

363-
assert.strictEqual(cs.length, 1) // Only edit call
364-
assert.strictEqual(completionCalls.length, 0) // No completion calls
363+
assert.strictEqual(cs.length, 2) // Only edit call
364+
assert.strictEqual(completionCalls.length, 1) // No completion calls
365365
assert.strictEqual(editCalls.length, 1) // One edit call
366-
367-
// Verify the stub was called
368-
sinon.assert.calledOnce(isEditSuggestionActiveStub)
369366
})
370367

371368
it('should make completion request when edit suggestion is not active', async () => {
372369
// Mock EditSuggestionState to return false (no edit suggestion active)
373-
const isEditSuggestionActiveStub = sandbox
374-
.stub(EditSuggestionState, 'isEditSuggestionActive')
375-
.returns(false)
376-
377370
const mockResult = {
378371
sessionId: 'test-session',
379372
items: [mockInlineCompletionItemOne],
@@ -400,9 +393,6 @@ describe('RecommendationService', () => {
400393
assert.strictEqual(cs.length, 2) // Both calls
401394
assert.strictEqual(completionCalls.length, 1) // One completion call
402395
assert.strictEqual(editCalls.length, 1) // One edit call
403-
404-
// Verify the stub was called
405-
sinon.assert.calledOnce(isEditSuggestionActiveStub)
406396
})
407397
})
408398
})

plugins/eslint-plugin-aws-toolkits/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"clean": "ts-node ../../scripts/clean.ts dist"
1010
},
1111
"devDependencies": {
12+
"@types/eslint": "^8.56.0",
1213
"mocha": "^10.1.0"
1314
},
1415
"engines": {

0 commit comments

Comments
 (0)