Skip to content

Commit 50c9121

Browse files
feat(amazonq): pre-fetch next recommendations for inline completions (#6419)
## Problem To support complex recommendations broken into multiple chunks, we need the ability to immediately show user next recommendation when they accept previous one ## Solution pre-fetch next recommendation predicting user accept first recommendation when showing them to user, and if user did accept first recommendation, immediately show next recommendation --- - 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: Will Lo <[email protected]>
1 parent 9afc587 commit 50c9121

23 files changed

+292
-116
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Inline suggestions: Pre-fetch recommendations to reduce suggestion latency."
4+
}

packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ import * as sinon from 'sinon'
99
import {
1010
onAcceptance,
1111
AcceptedSuggestionEntry,
12-
session,
12+
CodeWhispererSessionState,
1313
CodeWhispererTracker,
1414
RecommendationHandler,
1515
AuthUtil,
16+
CodeWhispererSession,
1617
} from 'aws-core-vscode/codewhisperer'
1718
import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test'
1819
import { assertTelemetryCurried } from 'aws-core-vscode/test'
1920

2021
describe('onAcceptance', function () {
22+
let session: CodeWhispererSession
2123
describe('onAcceptance', function () {
2224
beforeEach(async function () {
25+
session = CodeWhispererSessionState.instance.getSession()
2326
await resetCodeWhispererGlobalVariables()
24-
session.reset()
2527
})
2628

2729
afterEach(function () {

packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@ import * as vscode from 'vscode'
88
import * as sinon from 'sinon'
99
import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test'
1010
import { assertTelemetryCurried } from 'aws-core-vscode/test'
11-
import { onInlineAcceptance, RecommendationHandler, AuthUtil, session } from 'aws-core-vscode/codewhisperer'
11+
import {
12+
onInlineAcceptance,
13+
RecommendationHandler,
14+
AuthUtil,
15+
CodeWhispererSessionState,
16+
CodeWhispererSession,
17+
} from 'aws-core-vscode/codewhisperer'
1218
import { globals } from 'aws-core-vscode/shared'
1319
import { extensionVersion } from 'aws-core-vscode/shared'
1420

1521
describe('onInlineAcceptance', function () {
22+
let session: CodeWhispererSession
1623
describe('onInlineAcceptance', function () {
1724
beforeEach(async function () {
25+
session = CodeWhispererSessionState.instance.getSession()
1826
await resetCodeWhispererGlobalVariables()
19-
session.reset()
2027
})
2128

2229
afterEach(function () {

packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
getLabel,
1313
Recommendation,
1414
RecommendationHandler,
15-
session,
15+
CodeWhispererSessionState,
1616
} from 'aws-core-vscode/codewhisperer'
1717
import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test'
1818

@@ -39,6 +39,7 @@ describe('completionProviderService', function () {
3939

4040
describe('getCompletionItem', function () {
4141
it('should return targetCompletionItem given input', function () {
42+
const session = CodeWhispererSessionState.instance.getSession()
4243
session.startPos = new vscode.Position(0, 0)
4344
RecommendationHandler.instance.requestId = 'mock_requestId_getCompletionItem'
4445
session.sessionId = 'mock_sessionId_getCompletionItem'
@@ -95,6 +96,7 @@ describe('completionProviderService', function () {
9596

9697
describe('getCompletionItems', function () {
9798
it('should return completion items for each non-empty recommendation', async function () {
99+
const session = CodeWhispererSessionState.instance.getSession()
98100
session.recommendations = [
99101
{ content: "\n\t\tconsole.log('Hello world!');\n\t}" },
100102
{ content: '\nvar a = 10' },
@@ -106,6 +108,7 @@ describe('completionProviderService', function () {
106108
})
107109

108110
it('should return empty completion items when recommendation is empty', async function () {
111+
const session = CodeWhispererSessionState.instance.getSession()
109112
session.recommendations = []
110113
const mockPosition = new vscode.Position(14, 83)
111114
const mockDocument = createMockDocument()

packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CodeSuggestionsState,
1515
ConfigurationEntry,
1616
CWInlineCompletionItemProvider,
17-
session,
17+
CodeWhispererSessionState,
1818
AuthUtil,
1919
listCodeWhispererCommandsId,
2020
DefaultCodeWhispererClient,
@@ -46,6 +46,7 @@ describe('inlineCompletionService', function () {
4646
})
4747

4848
it('should call checkAndResetCancellationTokens before showing inline and next token to be null', async function () {
49+
const session = CodeWhispererSessionState.instance.getSession()
4950
const mockEditor = createMockTextEditor()
5051
sinon.stub(RecommendationHandler.instance, 'getRecommendations').resolves({
5152
result: 'Succeeded',
@@ -70,6 +71,7 @@ describe('inlineCompletionService', function () {
7071

7172
describe('clearInlineCompletionStates', function () {
7273
it('should remove inline reference and recommendations', async function () {
74+
const session = CodeWhispererSessionState.instance.getSession()
7375
const fakeReferences = [
7476
{
7577
message: '',

packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as vscode from 'vscode'
88
import * as sinon from 'sinon'
99
import {
1010
ReferenceInlineProvider,
11-
session,
11+
CodeWhispererSessionState,
1212
AuthUtil,
1313
DefaultCodeWhispererClient,
1414
RecommendationsList,
@@ -55,6 +55,7 @@ describe('recommendationHandler', function () {
5555
})
5656

5757
it('should assign correct recommendations given input', async function () {
58+
const session = CodeWhispererSessionState.instance.getSession()
5859
assert.strictEqual(CodeWhispererCodeCoverageTracker.instances.size, 0)
5960
assert.strictEqual(
6061
CodeWhispererCodeCoverageTracker.getTracker(mockEditor.document.languageId)?.serviceInvocationCount,
@@ -74,7 +75,7 @@ describe('recommendationHandler', function () {
7475
}
7576
const handler = new RecommendationHandler()
7677
sinon.stub(handler, 'getServerResponse').resolves(mockServerResult)
77-
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false)
78+
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter', false)
7879
const actual = session.recommendations
7980
const expected: RecommendationsList = [{ content: "print('Hello World!')" }, { content: '' }]
8081
assert.deepStrictEqual(actual, expected)
@@ -85,6 +86,7 @@ describe('recommendationHandler', function () {
8586
})
8687

8788
it('should assign request id correctly', async function () {
89+
const session = CodeWhispererSessionState.instance.getSession()
8890
const mockServerResult = {
8991
recommendations: [{ content: "print('Hello World!')" }, { content: '' }],
9092
$response: {
@@ -99,7 +101,7 @@ describe('recommendationHandler', function () {
99101
const handler = new RecommendationHandler()
100102
sinon.stub(handler, 'getServerResponse').resolves(mockServerResult)
101103
sinon.stub(handler, 'isCancellationRequested').returns(false)
102-
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false)
104+
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter', false)
103105
assert.strictEqual(handler.requestId, 'test_request')
104106
assert.strictEqual(session.sessionId, 'test_request')
105107
assert.strictEqual(session.triggerType, 'AutoTrigger')
@@ -128,9 +130,10 @@ describe('recommendationHandler', function () {
128130
strategy: 'empty',
129131
})
130132
sinon.stub(performance, 'now').returns(0.0)
133+
const session = CodeWhispererSessionState.instance.getSession()
131134
session.startPos = new vscode.Position(1, 0)
132135
session.startCursorOffset = 2
133-
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter')
136+
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter')
134137
const assertTelemetry = assertTelemetryCurried('codewhisperer_serviceInvocation')
135138
assertTelemetry({
136139
codewhispererRequestId: 'test_request',
@@ -167,10 +170,11 @@ describe('recommendationHandler', function () {
167170
const handler = new RecommendationHandler()
168171
sinon.stub(handler, 'getServerResponse').resolves(mockServerResult)
169172
sinon.stub(performance, 'now').returns(0.0)
173+
const session = CodeWhispererSessionState.instance.getSession()
170174
session.startPos = new vscode.Position(1, 0)
171175
session.requestIdList = ['test_request_empty']
172176
session.startCursorOffset = 2
173-
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter')
177+
await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter')
174178
const assertTelemetry = assertTelemetryCurried('codewhisperer_userDecision')
175179
assertTelemetry({
176180
codewhispererRequestId: 'test_request_empty',
@@ -192,6 +196,7 @@ describe('recommendationHandler', function () {
192196
sinon.restore()
193197
})
194198
it('should return true if any response is not empty', function () {
199+
const session = CodeWhispererSessionState.instance.getSession()
195200
const handler = new RecommendationHandler()
196201
session.recommendations = [
197202
{
@@ -204,12 +209,14 @@ describe('recommendationHandler', function () {
204209
})
205210

206211
it('should return false if response is empty', function () {
212+
const session = CodeWhispererSessionState.instance.getSession()
207213
const handler = new RecommendationHandler()
208214
session.recommendations = []
209215
assert.ok(!handler.isValidResponse())
210216
})
211217

212218
it('should return false if all response has no string length', function () {
219+
const session = CodeWhispererSessionState.instance.getSession()
213220
const handler = new RecommendationHandler()
214221
session.recommendations = [{ content: '' }, { content: '' }]
215222
assert.ok(!handler.isValidResponse())
@@ -222,6 +229,7 @@ describe('recommendationHandler', function () {
222229
})
223230

224231
it('should set the completion type to block given a multi-line suggestion', function () {
232+
const session = CodeWhispererSessionState.instance.getSession()
225233
session.setCompletionType(0, { content: 'test\n\n \t\r\nanother test' })
226234
assert.strictEqual(session.getCompletionType(0), 'Block')
227235

@@ -233,6 +241,7 @@ describe('recommendationHandler', function () {
233241
})
234242

235243
it('should set the completion type to line given a single-line suggestion', function () {
244+
const session = CodeWhispererSessionState.instance.getSession()
236245
session.setCompletionType(0, { content: 'test' })
237246
assert.strictEqual(session.getCompletionType(0), 'Line')
238247

@@ -241,6 +250,7 @@ describe('recommendationHandler', function () {
241250
})
242251

243252
it('should set the completion type to line given a multi-line completion but only one-lien of non-blank sequence', function () {
253+
const session = CodeWhispererSessionState.instance.getSession()
244254
session.setCompletionType(0, { content: 'test\n\t' })
245255
assert.strictEqual(session.getCompletionType(0), 'Line')
246256

@@ -257,6 +267,7 @@ describe('recommendationHandler', function () {
257267

258268
describe('on event change', async function () {
259269
beforeEach(function () {
270+
const session = CodeWhispererSessionState.instance.getSession()
260271
const fakeReferences = [
261272
{
262273
message: '',
@@ -274,12 +285,14 @@ describe('recommendationHandler', function () {
274285
})
275286

276287
it('should remove inline reference onEditorChange', async function () {
288+
const session = CodeWhispererSessionState.instance.getSession()
277289
session.sessionId = 'aSessionId'
278290
RecommendationHandler.instance.requestId = 'aRequestId'
279291
await RecommendationHandler.instance.onEditorChange()
280292
assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0)
281293
})
282294
it('should remove inline reference onFocusChange', async function () {
295+
const session = CodeWhispererSessionState.instance.getSession()
283296
session.sessionId = 'aSessionId'
284297
RecommendationHandler.instance.requestId = 'aRequestId'
285298
await RecommendationHandler.instance.onFocusChange()

packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
invokeRecommendation,
2323
ConfigurationEntry,
2424
RecommendationHandler,
25-
session,
25+
CodeWhispererSessionState,
2626
vsCodeCursorUpdateDelay,
2727
AuthUtil,
2828
} from 'aws-core-vscode/codewhisperer'
@@ -36,6 +36,7 @@ type CodeWhispererResponse = ListRecommendationsResponse & {
3636
let tempFolder: string
3737

3838
describe.skip('CodeWhisperer telemetry', async function () {
39+
const session = CodeWhispererSessionState.instance.getSession()
3940
let sandbox: sinon.SinonSandbox
4041
let client: DefaultCodeWhispererClient
4142

@@ -519,6 +520,7 @@ async function manualTrigger(
519520

520521
// Note: RecommendationHandler.isSuggestionVisible seems not to work well, hence not using it
521522
async function waitUntilSuggestionSeen(index: number = 0) {
523+
const session = CodeWhispererSessionState.instance.getSession()
522524
const state = await waitUntil(
523525
async () => {
524526
const r = session.getSuggestionState(index)

packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import assert from 'assert'
77
import { assertTelemetryCurried, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test'
8-
import { TelemetryHelper, Completion, session } from 'aws-core-vscode/codewhisperer'
8+
import { TelemetryHelper, Completion, CodeWhispererSessionState } from 'aws-core-vscode/codewhisperer'
99
import {
1010
CodewhispererCompletionType,
1111
CodewhispererSuggestionState,
@@ -39,6 +39,7 @@ function aCompletion(): Completion {
3939
}
4040

4141
describe('telemetryHelper', function () {
42+
const session = CodeWhispererSessionState.instance.getSession()
4243
describe('clientComponentLatency', function () {
4344
let sut: TelemetryHelper
4445

@@ -48,6 +49,7 @@ describe('telemetryHelper', function () {
4849

4950
afterEach(function () {
5051
sinon.restore()
52+
session.reset()
5153
})
5254

5355
it('resetClientComponentLatencyTime should reset state variables', function () {

packages/core/src/codewhisperer/client/codewhisperer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { isSsoConnection } from '../../auth/connection'
1717
import { pageableToCollection } from '../../shared/utilities/collectionUtils'
1818
import apiConfig = require('./service-2.json')
1919
import userApiConfig = require('./user-service-2.json')
20-
import { session } from '../util/codeWhispererSession'
20+
import { CodeWhispererSessionState } from '../util/codeWhispererSession'
2121
import { getLogger } from '../../shared/logger'
2222
import { indent } from '../../shared/utilities/textUtilities'
2323
import { keepAliveHeader } from './agent'
@@ -133,6 +133,7 @@ export class DefaultCodeWhispererClient {
133133
}
134134

135135
async createUserSdkClient(maxRetries?: number): Promise<CodeWhispererUserClient> {
136+
const session = CodeWhispererSessionState.instance.getSession()
136137
const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
137138
session.setFetchCredentialStart()
138139
const bearerToken = await AuthUtil.instance.getBearerToken()

packages/core/src/codewhisperer/commands/invokeRecommendation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { vsCodeState, ConfigurationEntry } from '../models/model'
88
import { resetIntelliSenseState } from '../util/globalStateUtil'
99
import { DefaultCodeWhispererClient } from '../client/codewhisperer'
1010
import { RecommendationHandler } from '../service/recommendationHandler'
11-
import { session } from '../util/codeWhispererSession'
11+
import { CodeWhispererSessionState } from '../util/codeWhispererSession'
1212
import { RecommendationService } from '../service/recommendationService'
1313

1414
/**
@@ -33,6 +33,7 @@ export async function invokeRecommendation(
3333
/**
3434
* When using intelliSense, if invocation position changed, reject previous active recommendations
3535
*/
36+
const session = CodeWhispererSessionState.instance.getSession()
3637
if (vsCodeState.isIntelliSenseActive && editor.selection.active !== session.startPos) {
3738
resetIntelliSenseState(
3839
config.isManualTriggerEnabled,

0 commit comments

Comments
 (0)