Skip to content

Commit d6397ad

Browse files
leigaolatonaamzaws-toolkit-automationandrewyuqWill-ShaoHua
authored
feat(amazonq): start AB test for inline completion rollback (aws#8006)
## 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. --------- Co-authored-by: atontb <[email protected]> Co-authored-by: aws-toolkit-automation <[email protected]> Co-authored-by: andrewyuq <[email protected]> Co-authored-by: Will Lo <[email protected]>
1 parent 83318dc commit d6397ad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+8156
-39
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"webpack-merge": "^5.10.0"
7575
},
7676
"dependencies": {
77-
"@aws/language-server-runtimes": "^0.2.125",
77+
"@aws/language-server-runtimes": "^0.2.128",
7878
"@types/node": "^22.7.5",
7979
"jaro-winkler": "^0.2.8",
8080
"vscode-nls": "^5.2.0",

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

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,73 @@
55

66
import vscode from 'vscode'
77
import {
8+
acceptSuggestion,
89
AuthUtil,
10+
CodeSuggestionsState,
11+
CodeWhispererCodeCoverageTracker,
912
CodeWhispererConstants,
13+
CodeWhispererSettings,
14+
ConfigurationEntry,
15+
DefaultCodeWhispererClient,
16+
invokeRecommendation,
1017
isInlineCompletionEnabled,
18+
KeyStrokeHandler,
19+
RecommendationHandler,
1120
runtimeLanguageContext,
21+
TelemetryHelper,
1222
UserWrittenCodeTracker,
1323
vsCodeState,
1424
} from 'aws-core-vscode/codewhisperer'
15-
import { globals, sleep } from 'aws-core-vscode/shared'
25+
import { Commands, getLogger, globals, sleep } from 'aws-core-vscode/shared'
26+
import { LanguageClient } from 'vscode-languageclient'
1627

17-
export async function activate() {
18-
if (isInlineCompletionEnabled()) {
19-
// Debugging purpose: only initialize NextEditPredictionPanel when development
20-
// NextEditPredictionPanel.getInstance()
28+
export async function activate(languageClient: LanguageClient) {
29+
const codewhispererSettings = CodeWhispererSettings.instance
30+
const client = new DefaultCodeWhispererClient()
2131

32+
if (isInlineCompletionEnabled()) {
2233
await setSubscriptionsforInlineCompletion()
2334
await AuthUtil.instance.setVscodeContextProps()
35+
RecommendationHandler.instance.setLanguageClient(languageClient)
36+
}
37+
38+
function getAutoTriggerStatus(): boolean {
39+
return CodeSuggestionsState.instance.isSuggestionsEnabled()
40+
}
41+
42+
async function getConfigEntry(): Promise<ConfigurationEntry> {
43+
const isShowMethodsEnabled: boolean =
44+
vscode.workspace.getConfiguration('editor').get('suggest.showMethods') || false
45+
const isAutomatedTriggerEnabled: boolean = getAutoTriggerStatus()
46+
const isManualTriggerEnabled: boolean = true
47+
const isSuggestionsWithCodeReferencesEnabled = codewhispererSettings.isSuggestionsWithCodeReferencesEnabled()
48+
49+
// TODO:remove isManualTriggerEnabled
50+
return {
51+
isShowMethodsEnabled,
52+
isManualTriggerEnabled,
53+
isAutomatedTriggerEnabled,
54+
isSuggestionsWithCodeReferencesEnabled,
55+
}
2456
}
2557

2658
async function setSubscriptionsforInlineCompletion() {
59+
RecommendationHandler.instance.subscribeSuggestionCommands()
60+
2761
/**
2862
* Automated trigger
2963
*/
3064
globals.context.subscriptions.push(
65+
acceptSuggestion.register(globals.context),
66+
vscode.window.onDidChangeActiveTextEditor(async (editor) => {
67+
await RecommendationHandler.instance.onEditorChange()
68+
}),
69+
vscode.window.onDidChangeWindowState(async (e) => {
70+
await RecommendationHandler.instance.onFocusChange()
71+
}),
72+
vscode.window.onDidChangeTextEditorSelection(async (e) => {
73+
await RecommendationHandler.instance.onCursorChange(e)
74+
}),
3175
vscode.workspace.onDidChangeTextDocument(async (e) => {
3276
const editor = vscode.window.activeTextEditor
3377
if (!editor) {
@@ -40,6 +84,7 @@ export async function activate() {
4084
return
4185
}
4286

87+
CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e)
4388
UserWrittenCodeTracker.instance.onTextDocumentChange(e)
4489
/**
4590
* Handle this keystroke event only when
@@ -51,6 +96,11 @@ export async function activate() {
5196
return
5297
}
5398

99+
if (vsCodeState.lastUserModificationTime) {
100+
TelemetryHelper.instance.setTimeSinceLastModification(
101+
performance.now() - vsCodeState.lastUserModificationTime
102+
)
103+
}
54104
vsCodeState.lastUserModificationTime = performance.now()
55105
/**
56106
* Important: Doing this sleep(10) is to make sure
@@ -59,6 +109,19 @@ export async function activate() {
59109
* Then this event can be processed by our code.
60110
*/
61111
await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay)
112+
if (!RecommendationHandler.instance.isSuggestionVisible()) {
113+
await KeyStrokeHandler.instance.processKeyStroke(e, editor, client, await getConfigEntry())
114+
}
115+
}),
116+
// manual trigger
117+
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
118+
invokeRecommendation(
119+
vscode.window.activeTextEditor as vscode.TextEditor,
120+
client,
121+
await getConfigEntry()
122+
).catch((e: Error) => {
123+
getLogger().error('invokeRecommendation failed: %s', (e as Error).message)
124+
})
62125
})
63126
)
64127
}

packages/amazonq/src/lsp/client.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {
2222
AuthUtil,
2323
CodeWhispererSettings,
24+
FeatureConfigProvider,
2425
getSelectedCustomization,
2526
TelemetryHelper,
2627
vsCodeState,
@@ -45,6 +46,7 @@ import {
4546
} from 'aws-core-vscode/shared'
4647
import { processUtils } from 'aws-core-vscode/shared'
4748
import { activate } from './chat/activation'
49+
import { activate as activateInline } from '../app/inline/activation'
4850
import { AmazonQResourcePaths } from './lspInstaller'
4951
import { ConfigSection, isValidConfigSection, pushConfigUpdate, toAmazonQLSPLogLevel } from './config'
5052
import { activate as activateInlineChat } from '../inlineChat/activation'
@@ -338,8 +340,42 @@ async function onLanguageServerReady(
338340
// tutorial for inline chat
339341
const inlineChatTutorialAnnotation = new InlineChatTutorialAnnotation(inlineTutorialAnnotation)
340342

341-
const inlineManager = new InlineCompletionManager(client, sessionManager, lineTracker, inlineTutorialAnnotation)
342-
inlineManager.registerInlineCompletion()
343+
const enableInlineRollback = FeatureConfigProvider.instance.getPreFlareRollbackGroup() === 'treatment'
344+
if (enableInlineRollback) {
345+
// use VSC inline
346+
getLogger().info('Entering preflare logic')
347+
await activateInline(client)
348+
} else {
349+
// use language server for inline completion
350+
getLogger().info('Entering postflare logic')
351+
const inlineManager = new InlineCompletionManager(client, sessionManager, lineTracker, inlineTutorialAnnotation)
352+
inlineManager.registerInlineCompletion()
353+
toDispose.push(
354+
inlineManager,
355+
Commands.register('aws.amazonq.showPrev', async () => {
356+
await sessionManager.maybeRefreshSessionUx()
357+
await vscode.commands.executeCommand('editor.action.inlineSuggest.showPrevious')
358+
sessionManager.onPrevSuggestion()
359+
}),
360+
Commands.register('aws.amazonq.showNext', async () => {
361+
await sessionManager.maybeRefreshSessionUx()
362+
await vscode.commands.executeCommand('editor.action.inlineSuggest.showNext')
363+
sessionManager.onNextSuggestion()
364+
}),
365+
// this is a workaround since handleDidShowCompletionItem is not public API
366+
Commands.register('aws.amazonq.checkInlineSuggestionVisibility', async () => {
367+
sessionManager.checkInlineSuggestionVisibility()
368+
}),
369+
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
370+
vsCodeState.lastManualTriggerTime = performance.now()
371+
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
372+
}),
373+
vscode.workspace.onDidCloseTextDocument(async () => {
374+
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')
375+
})
376+
)
377+
}
378+
343379
activateInlineChat(extensionContext, client, encryptionKey, inlineChatTutorialAnnotation)
344380

345381
if (Experiments.instance.get('amazonqChatLSP', true)) {
@@ -354,25 +390,6 @@ async function onLanguageServerReady(
354390
await initializeLanguageServerConfiguration(client, 'startup')
355391

356392
toDispose.push(
357-
inlineManager,
358-
Commands.register('aws.amazonq.showPrev', async () => {
359-
await sessionManager.maybeRefreshSessionUx()
360-
await vscode.commands.executeCommand('editor.action.inlineSuggest.showPrevious')
361-
sessionManager.onPrevSuggestion()
362-
}),
363-
Commands.register('aws.amazonq.showNext', async () => {
364-
await sessionManager.maybeRefreshSessionUx()
365-
await vscode.commands.executeCommand('editor.action.inlineSuggest.showNext')
366-
sessionManager.onNextSuggestion()
367-
}),
368-
// this is a workaround since handleDidShowCompletionItem is not public API
369-
Commands.register('aws.amazonq.checkInlineSuggestionVisibility', async () => {
370-
sessionManager.checkInlineSuggestionVisibility()
371-
}),
372-
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
373-
vsCodeState.lastManualTriggerTime = performance.now()
374-
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
375-
}),
376393
Commands.register('aws.amazonq.refreshAnnotation', async (forceProceed: boolean) => {
377394
telemetry.record({
378395
traceId: TelemetryHelper.instance.traceId,
@@ -398,9 +415,6 @@ async function onLanguageServerReady(
398415
getLogger().debug(`codewhisperer: user dismiss tutorial.`)
399416
}
400417
}),
401-
vscode.workspace.onDidCloseTextDocument(async () => {
402-
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')
403-
}),
404418
AuthUtil.instance.auth.onDidChangeActiveConnection(async () => {
405419
await auth.refreshConnection()
406420
}),
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import * as sinon from 'sinon'
8+
import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test'
9+
import {
10+
ConfigurationEntry,
11+
invokeRecommendation,
12+
InlineCompletionService,
13+
isInlineCompletionEnabled,
14+
DefaultCodeWhispererClient,
15+
} from 'aws-core-vscode/codewhisperer'
16+
17+
describe('invokeRecommendation', function () {
18+
describe('invokeRecommendation', function () {
19+
let getRecommendationStub: sinon.SinonStub
20+
let mockClient: DefaultCodeWhispererClient
21+
22+
beforeEach(async function () {
23+
await resetCodeWhispererGlobalVariables()
24+
getRecommendationStub = sinon.stub(InlineCompletionService.instance, 'getPaginatedRecommendation')
25+
})
26+
27+
afterEach(function () {
28+
sinon.restore()
29+
})
30+
31+
it('Should call getPaginatedRecommendation with OnDemand as trigger type when inline completion is enabled', async function () {
32+
const mockEditor = createMockTextEditor()
33+
const config: ConfigurationEntry = {
34+
isShowMethodsEnabled: true,
35+
isManualTriggerEnabled: true,
36+
isAutomatedTriggerEnabled: true,
37+
isSuggestionsWithCodeReferencesEnabled: true,
38+
}
39+
await invokeRecommendation(mockEditor, mockClient, config)
40+
assert.strictEqual(getRecommendationStub.called, isInlineCompletionEnabled())
41+
})
42+
})
43+
})
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import * as vscode from 'vscode'
8+
import * as sinon from 'sinon'
9+
import { onAcceptance, AcceptedSuggestionEntry, session, CodeWhispererTracker } from 'aws-core-vscode/codewhisperer'
10+
import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test'
11+
12+
describe('onAcceptance', function () {
13+
describe('onAcceptance', function () {
14+
beforeEach(async function () {
15+
await resetCodeWhispererGlobalVariables()
16+
session.reset()
17+
})
18+
19+
afterEach(function () {
20+
sinon.restore()
21+
session.reset()
22+
})
23+
24+
it('Should enqueue an event object to tracker', async function () {
25+
const mockEditor = createMockTextEditor()
26+
const trackerSpy = sinon.spy(CodeWhispererTracker.prototype, 'enqueue')
27+
const fakeReferences = [
28+
{
29+
message: '',
30+
licenseName: 'MIT',
31+
repository: 'http://github.com/fake',
32+
recommendationContentSpan: {
33+
start: 0,
34+
end: 10,
35+
},
36+
},
37+
]
38+
await onAcceptance({
39+
editor: mockEditor,
40+
range: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 26)),
41+
effectiveRange: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 26)),
42+
acceptIndex: 0,
43+
recommendation: "print('Hello World!')",
44+
requestId: '',
45+
sessionId: '',
46+
triggerType: 'OnDemand',
47+
completionType: 'Line',
48+
language: 'python',
49+
references: fakeReferences,
50+
})
51+
const actualArg = trackerSpy.getCall(0).args[0] as AcceptedSuggestionEntry
52+
assert.ok(trackerSpy.calledOnce)
53+
assert.strictEqual(actualArg.originalString, 'def two_sum(nums, target):')
54+
assert.strictEqual(actualArg.requestId, '')
55+
assert.strictEqual(actualArg.sessionId, '')
56+
assert.strictEqual(actualArg.triggerType, 'OnDemand')
57+
assert.strictEqual(actualArg.completionType, 'Line')
58+
assert.strictEqual(actualArg.language, 'python')
59+
assert.deepStrictEqual(actualArg.startPosition, new vscode.Position(1, 0))
60+
assert.deepStrictEqual(actualArg.endPosition, new vscode.Position(1, 26))
61+
assert.strictEqual(actualArg.index, 0)
62+
})
63+
})
64+
})
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import * as vscode from 'vscode'
8+
import * as sinon from 'sinon'
9+
import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test'
10+
import { onInlineAcceptance, RecommendationHandler, session } from 'aws-core-vscode/codewhisperer'
11+
12+
describe('onInlineAcceptance', function () {
13+
describe('onInlineAcceptance', function () {
14+
beforeEach(async function () {
15+
await resetCodeWhispererGlobalVariables()
16+
session.reset()
17+
})
18+
19+
afterEach(function () {
20+
sinon.restore()
21+
session.reset()
22+
})
23+
24+
it('Should dispose inline completion provider', async function () {
25+
const mockEditor = createMockTextEditor()
26+
const spy = sinon.spy(RecommendationHandler.instance, 'disposeInlineCompletion')
27+
await onInlineAcceptance({
28+
editor: mockEditor,
29+
range: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 21)),
30+
effectiveRange: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 21)),
31+
acceptIndex: 0,
32+
recommendation: "print('Hello World!')",
33+
requestId: '',
34+
sessionId: '',
35+
triggerType: 'OnDemand',
36+
completionType: 'Line',
37+
language: 'python',
38+
references: undefined,
39+
})
40+
assert.ok(spy.calledWith())
41+
})
42+
})
43+
})

0 commit comments

Comments
 (0)