From b155f873f0647a302b43261d705ae0c8d04ed849 Mon Sep 17 00:00:00 2001 From: Zoe Lin Date: Wed, 13 Nov 2024 16:12:50 -0800 Subject: [PATCH 01/17] poc model routing --- .../commands/onAcceptance.test.ts | 3 +- .../commands/onInlineAcceptance.test.ts | 8 +- .../service/completionProvider.test.ts | 3 +- .../service/inlineCompletionService.test.ts | 3 +- .../service/recommendationHandler.test.ts | 3 +- .../codewhisperer/service/telemetry.test.ts | 4 +- .../util/telemetryHelper.test.ts | 3 +- .../src/codewhisperer/client/codewhisperer.ts | 3 +- .../commands/invokeRecommendation.ts | 3 +- .../commands/onInlineAcceptance.ts | 10 +- packages/core/src/codewhisperer/index.ts | 2 +- .../service/completionProvider.ts | 4 +- .../service/inlineCompletionItemProvider.ts | 26 ++- .../service/inlineCompletionService.ts | 3 +- .../service/nextRecommendationProvider.ts | 28 +++ .../service/recommendationHandler.ts | 210 +++++++++++++----- .../service/referenceLogViewProvider.ts | 3 +- .../util/codeWhispererSession.ts | 107 ++++++--- .../src/codewhisperer/util/telemetryHelper.ts | 169 +++++++------- .../views/lineAnnotationController.ts | 3 +- .../core/src/test/codewhisperer/testUtil.ts | 5 +- .../codewhisperer/referenceTracker.test.ts | 6 +- .../codewhisperer/serviceInvocations.test.ts | 8 +- 23 files changed, 419 insertions(+), 198 deletions(-) create mode 100644 packages/core/src/codewhisperer/service/nextRecommendationProvider.ts diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index 5af3252ec82..c61ed4ba942 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -9,7 +9,7 @@ import * as sinon from 'sinon' import { onAcceptance, AcceptedSuggestionEntry, - session, + CodeWhispererSessionState, CodeWhispererTracker, RecommendationHandler, AuthUtil, @@ -18,6 +18,7 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { await resetCodeWhispererGlobalVariables() diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index ae02e7bd7c3..4a86240687a 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -8,11 +8,17 @@ import * as vscode from 'vscode' import * as sinon from 'sinon' import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' import { assertTelemetryCurried } from 'aws-core-vscode/test' -import { onInlineAcceptance, RecommendationHandler, AuthUtil, session } from 'aws-core-vscode/codewhisperer' +import { + onInlineAcceptance, + RecommendationHandler, + AuthUtil, + CodeWhispererSessionState, +} from 'aws-core-vscode/codewhisperer' import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { await resetCodeWhispererGlobalVariables() diff --git a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts index 956999d64ad..e110743932d 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts @@ -12,11 +12,12 @@ import { getLabel, Recommendation, RecommendationHandler, - session, + CodeWhispererSessionState, } from 'aws-core-vscode/codewhisperer' import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' describe('completionProviderService', function () { + const session = CodeWhispererSessionState.instance.getSession() beforeEach(async function () { await resetCodeWhispererGlobalVariables() }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts index 18fd7d2f21b..a3bc8418f12 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts @@ -14,7 +14,7 @@ import { CodeSuggestionsState, ConfigurationEntry, CWInlineCompletionItemProvider, - session, + CodeWhispererSessionState, AuthUtil, listCodeWhispererCommandsId, DefaultCodeWhispererClient, @@ -22,6 +22,7 @@ import { import { createMockTextEditor, resetCodeWhispererGlobalVariables, createMockDocument } from 'aws-core-vscode/test' describe('inlineCompletionService', function () { + const session = CodeWhispererSessionState.instance.getSession() beforeEach(async function () { await resetCodeWhispererGlobalVariables() }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts index d8855796df0..e519a42d7bc 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode' import * as sinon from 'sinon' import { ReferenceInlineProvider, - session, + CodeWhispererSessionState, AuthUtil, DefaultCodeWhispererClient, RecommendationsList, @@ -26,6 +26,7 @@ import { // import * as supplementalContextUtil from 'aws-core-vscode/codewhisperer' describe('recommendationHandler', function () { + const session = CodeWhispererSessionState.instance.getSession() const config: ConfigurationEntry = { isShowMethodsEnabled: true, isManualTriggerEnabled: true, diff --git a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts index 0f1429f130b..797de801428 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts @@ -22,7 +22,7 @@ import { invokeRecommendation, ConfigurationEntry, RecommendationHandler, - session, + CodeWhispererSessionState, vsCodeCursorUpdateDelay, AuthUtil, } from 'aws-core-vscode/codewhisperer' @@ -36,6 +36,7 @@ type CodeWhispererResponse = ListRecommendationsResponse & { let tempFolder: string describe.skip('CodeWhisperer telemetry', async function () { + const session = CodeWhispererSessionState.instance.getSession() let sandbox: sinon.SinonSandbox let client: DefaultCodeWhispererClient @@ -519,6 +520,7 @@ async function manualTrigger( // Note: RecommendationHandler.isSuggestionVisible seems not to work well, hence not using it async function waitUntilSuggestionSeen(index: number = 0) { + const session = CodeWhispererSessionState.instance.getSession() const state = await waitUntil( async () => { const r = session.getSuggestionState(index) diff --git a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts index e042b1d43a2..b6ff97053d4 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts @@ -5,7 +5,7 @@ import assert from 'assert' import { assertTelemetryCurried, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' -import { TelemetryHelper, Completion, session } from 'aws-core-vscode/codewhisperer' +import { TelemetryHelper, Completion, CodeWhispererSessionState } from 'aws-core-vscode/codewhisperer' import { CodewhispererCompletionType, CodewhispererSuggestionState, @@ -39,6 +39,7 @@ function aCompletion(): Completion { } describe('telemetryHelper', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('clientComponentLatency', function () { let sut: TelemetryHelper diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 7a869a68372..ceaf020fb02 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -17,7 +17,7 @@ import { isSsoConnection } from '../../auth/connection' import { pageableToCollection } from '../../shared/utilities/collectionUtils' import apiConfig = require('./service-2.json') import userApiConfig = require('./user-service-2.json') -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import { getLogger } from '../../shared/logger' import { indent } from '../../shared/utilities/textUtilities' import { keepAliveHeader } from './agent' @@ -133,6 +133,7 @@ export class DefaultCodeWhispererClient { } async createUserSdkClient(maxRetries?: number): Promise { + const session = CodeWhispererSessionState.instance.getSession() const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled() session.setFetchCredentialStart() const bearerToken = await AuthUtil.instance.getBearerToken() diff --git a/packages/core/src/codewhisperer/commands/invokeRecommendation.ts b/packages/core/src/codewhisperer/commands/invokeRecommendation.ts index 37fcb965774..e1f17e8a909 100644 --- a/packages/core/src/codewhisperer/commands/invokeRecommendation.ts +++ b/packages/core/src/codewhisperer/commands/invokeRecommendation.ts @@ -8,7 +8,7 @@ import { vsCodeState, ConfigurationEntry } from '../models/model' import { resetIntelliSenseState } from '../util/globalStateUtil' import { DefaultCodeWhispererClient } from '../client/codewhisperer' import { RecommendationHandler } from '../service/recommendationHandler' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import { RecommendationService } from '../service/recommendationService' /** @@ -33,6 +33,7 @@ export async function invokeRecommendation( /** * When using intelliSense, if invocation position changed, reject previous active recommendations */ + const session = CodeWhispererSessionState.instance.getSession() if (vsCodeState.isIntelliSenseActive && editor.selection.active !== session.startPos) { resetIntelliSenseState( config.isManualTriggerEnabled, diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index da581d1aacc..2dc1956d6dc 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -26,7 +26,7 @@ import { import { ReferenceLogViewProvider } from '../service/referenceLogViewProvider' import { ReferenceHoverProvider } from '../service/referenceHoverProvider' import { ImportAdderProvider } from '../service/importAdderProvider' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import path from 'path' import { RecommendationService } from '../service/recommendationService' import { Container } from '../service/serviceContainer' @@ -89,6 +89,7 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept const end = acceptanceEntry.editor.selection.active vsCodeState.isCodeWhispererEditing = true + const session = CodeWhispererSessionState.instance.getSession() /** * Mitigation to right context handling mainly for auto closing bracket use case */ @@ -142,5 +143,12 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept } RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) + if (acceptanceEntry.acceptIndex == 0) { + // TODO: gate behind A/B group if needed + const nextSession = CodeWhispererSessionState.instance.getNextSession() + nextSession.startPos = acceptanceEntry.editor.selection.active + CodeWhispererSessionState.instance.setSession(nextSession) + await RecommendationHandler.instance.showNextRecommendations() + } } } diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 3aea72fb4ca..f0dfd97c9ca 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -59,7 +59,7 @@ export { onAcceptance } from './commands/onAcceptance' export { CodeWhispererTracker } from './tracker/codewhispererTracker' export { RecommendationHandler } from './service/recommendationHandler' export { CodeWhispererUserGroupSettings } from './util/userGroupUtil' -export { session } from './util/codeWhispererSession' +export { CodeWhispererSessionState } from './util/codeWhispererSession' export { onInlineAcceptance } from './commands/onInlineAcceptance' export { stopTransformByQ } from './commands/startTransformByQ' export { getCompletionItems, getCompletionItem, getLabel } from './service/completionProvider' diff --git a/packages/core/src/codewhisperer/service/completionProvider.ts b/packages/core/src/codewhisperer/service/completionProvider.ts index 226d04dec2b..df4f2f98466 100644 --- a/packages/core/src/codewhisperer/service/completionProvider.ts +++ b/packages/core/src/codewhisperer/service/completionProvider.ts @@ -9,12 +9,13 @@ import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { Recommendation } from '../client/codewhisperer' import { LicenseUtil } from '../util/licenseUtil' import { RecommendationHandler } from './recommendationHandler' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import path from 'path' /** * completion provider for intelliSense popup */ export function getCompletionItems(document: vscode.TextDocument, position: vscode.Position) { + const session = CodeWhispererSessionState.instance.getSession() const completionItems: vscode.CompletionItem[] = [] for (const [index, recommendation] of session.recommendations.entries()) { completionItems.push(getCompletionItem(document, position, recommendation, index)) @@ -29,6 +30,7 @@ export function getCompletionItem( recommendationDetail: Recommendation, recommendationIndex: number ) { + const session = CodeWhispererSessionState.instance.getSession() const start = session.startPos const range = new vscode.Range(start, start) const recommendation = recommendationDetail.content diff --git a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts index a6c424c321d..b77e2c5a371 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts @@ -5,7 +5,7 @@ import vscode, { Position } from 'vscode' import { getPrefixSuffixOverlap } from '../util/commonUtil' import { Recommendation } from '../client/codewhisperer' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import { TelemetryHelper } from '../util/telemetryHelper' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { ReferenceInlineProvider } from './referenceInlineProvider' @@ -21,6 +21,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt private requestId: string private startPos: Position private nextToken: string + private session = CodeWhispererSessionState.instance.getSession() private _onDidShow: vscode.EventEmitter = new vscode.EventEmitter() public readonly onDidShow: vscode.Event = this._onDidShow.event @@ -100,8 +101,8 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt const effectiveStart = document.positionAt(document.offsetAt(start) + prefix.length) const truncatedSuggestion = this.truncateOverlapWithRightContext(document, r.content, end) if (truncatedSuggestion.length === 0) { - if (session.getSuggestionState(index) !== 'Showed') { - session.setSuggestionState(index, 'Discard') + if (this.session.getSuggestionState(index) !== 'Showed') { + this.session.setSuggestionState(index, 'Discard') } return undefined } @@ -118,9 +119,9 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt index, truncatedSuggestion, this.requestId, - session.sessionId, - session.triggerType, - session.getCompletionType(index), + this.session.sessionId, + this.session.triggerType, + this.session.getCompletionType(index), runtimeLanguageContext.getLanguageContext(document.languageId, path.extname(document.fileName)) .language, r.references, @@ -152,25 +153,28 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt // valid one. Otherwise, inline completion which utilizes this position will function // improperly. const start = document.validatePosition(this.startPos) + console.log('start pos', start) const end = position const iteratingIndexes = this.getIteratingIndexes() const prefix = document.getText(new vscode.Range(start, end)).replace(/\r\n/g, '\n') - const matchedCount = session.recommendations.filter( - (r) => r.content.length > 0 && r.content.startsWith(prefix) && r.content !== prefix + const matchedCount = this.session.recommendations.filter( + (r: any) => r.content.length > 0 && r.content.startsWith(prefix) && r.content !== prefix ).length for (const i of iteratingIndexes) { - const r = session.recommendations[i] + const r = this.recommendations[i] + console.log('in show', r) const item = this.getInlineCompletionItem(document, r, start, end, i, prefix) if (item === undefined) { continue } + console.log('item', item) this.activeItemIndex = i - session.setSuggestionState(i, 'Showed') + this.session.setSuggestionState(i, 'Showed') ReferenceInlineProvider.instance.setInlineReference(this.startPos.line, r.content, r.references) ImportAdderProvider.instance.onShowRecommendation(document, this.startPos.line, r) this.nextMove = 0 TelemetryHelper.instance.setFirstSuggestionShowTime() - session.setPerceivedLatency() + this.session.setPerceivedLatency() UserWrittenCodeTracker.instance.onQStartsMakingEdits() this._onDidShow.fire() if (matchedCount >= 2 || this.nextToken !== '') { diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index 715fd93ad2d..cc93bfa21b0 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts @@ -16,7 +16,7 @@ import { shared } from '../../shared/utilities/functionUtils' import { ClassifierTrigger } from './classifierTrigger' import { getSelectedCustomization } from '../util/customizationUtil' import { codicon, getIcon } from '../../shared/icons' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import { noSuggestions } from '../models/constants' import { Commands } from '../../shared/vscode/commands2' import { listCodeWhispererCommandsId } from '../ui/statusBarMenu' @@ -119,6 +119,7 @@ export class InlineCompletionService { errorMessage: undefined, recommendationCount: 0, } + const session = CodeWhispererSessionState.instance.getSession() try { let page = 0 while (page < this.maxPage) { diff --git a/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts b/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts new file mode 100644 index 00000000000..00f79cf4704 --- /dev/null +++ b/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts @@ -0,0 +1,28 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { ConfigurationEntry, DefaultCodeWhispererClient } from '..' +import { RecommendationService } from './recommendationService' +import { CodewhispererAutomatedTriggerType, CodewhispererTriggerType } from '../../shared/telemetry' + +export class NextRecommendationProvider { + async getNextRecommendation( + client: DefaultCodeWhispererClient, + editor: vscode.TextEditor, + config: ConfigurationEntry, + triggerType: CodewhispererTriggerType, + autoTriggerType?: CodewhispererAutomatedTriggerType + ) { + const recommendations = await RecommendationService.instance.generateRecommendation( + client, + editor, + triggerType, + config, + autoTriggerType + ) + return recommendations + } +} diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index 1f5096ad1cc..d5f440707e5 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -5,10 +5,16 @@ import * as vscode from 'vscode' import { extensionVersion } from '../../shared/vscode/env' -import { RecommendationsList, DefaultCodeWhispererClient, CognitoCredentialsError } from '../client/codewhisperer' +import { + RecommendationsList, + DefaultCodeWhispererClient, + CognitoCredentialsError, + ListRecommendationsRequest, + GenerateRecommendationsRequest, +} from '../client/codewhisperer' import * as EditorContext from '../util/editorContext' import * as CodeWhispererConstants from '../models/constants' -import { ConfigurationEntry, GetRecommendationsResponse, vsCodeState } from '../models/model' +import { CodeSuggestionsState, ConfigurationEntry, GetRecommendationsResponse, vsCodeState } from '../models/model' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { AWSError } from 'aws-sdk' import { isAwsError } from '../../shared/errors' @@ -31,7 +37,7 @@ import { import { CodeWhispererCodeCoverageTracker } from '../tracker/codewhispererCodeCoverageTracker' import { invalidCustomizationMessage } from '../models/constants' import { getSelectedCustomization, switchToBaseCustomizationAndNotify } from '../util/customizationUtil' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState, CodeWhispererSession } from '../util/codeWhispererSession' import { Commands } from '../../shared/vscode/commands2' import globals from '../../shared/extensionGlobals' import { noSuggestions, updateInlineLockKey } from '../models/constants' @@ -44,6 +50,8 @@ import { indent } from '../../shared/utilities/textUtilities' import path from 'path' import { isIamConnection } from '../../auth/connection' import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' +import * as codewhispererClient from '../client/codewhisperer' +import { CodeWhispererSettings } from '../util/codewhispererSettings' /** * This class is for getRecommendation/listRecommendation API calls and its states @@ -85,6 +93,8 @@ export class RecommendationHandler { private prev: vscode.Disposable private _timer?: NodeJS.Timer documentUri: vscode.Uri | undefined = undefined + // private session: CodeWhispererSession + // private nextSession: CodeWhispererSession constructor() { this.requestId = '' @@ -94,6 +104,8 @@ export class RecommendationHandler { this.prev = new vscode.Disposable(() => {}) this.next = new vscode.Disposable(() => {}) this.reject = new vscode.Disposable(() => {}) + // this.session = CodeWhispererSessionState.instance.getSession() + // this.nextSession = CodeWhispererSessionState.instance.getNextSession() } static #instance: RecommendationHandler @@ -103,6 +115,7 @@ export class RecommendationHandler { } isValidResponse(): boolean { + const session = CodeWhispererSessionState.instance.getSession() return session.recommendations.some((r) => r.content.trim() !== '') } @@ -157,11 +170,19 @@ export class RecommendationHandler { autoTriggerType?: CodewhispererAutomatedTriggerType, pagination: boolean = true, page: number = 0, - generate: boolean = isIamConnection(AuthUtil.instance.conn) + generate: boolean = isIamConnection(AuthUtil.instance.conn), + // currentSession: any = this.session, + isNextSession: boolean = false ): Promise { let invocationResult: 'Succeeded' | 'Failed' = 'Failed' let errorMessage: string | undefined = undefined let errorCode: string | undefined = undefined + let currentSession = CodeWhispererSessionState.instance.getSession() + if (isNextSession) { + getLogger().info('pre-fetching next recommendation for model routing') + currentSession = new CodeWhispererSession() + CodeWhispererSessionState.instance.setNextSession(currentSession) + } if (!editor) { return Promise.resolve({ @@ -178,43 +199,65 @@ export class RecommendationHandler { let latency = 0 let nextToken = '' let shouldRecordServiceInvocation = true - session.language = runtimeLanguageContext.getLanguageContext( + currentSession.language = runtimeLanguageContext.getLanguageContext( editor.document.languageId, path.extname(editor.document.fileName) ).language - session.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName) + currentSession.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName) if (pagination && !generate) { + // else { if (page === 0) { - session.requestContext = await EditorContext.buildListRecommendationRequest( - editor as vscode.TextEditor, - this.nextToken, - config.isSuggestionsWithCodeReferencesEnabled - ) + if (isNextSession) { + const session = CodeWhispererSessionState.instance.getSession() + const request = session.requestContext.request as ListRecommendationsRequest + currentSession.requestContext = { + request: { + ...request, + fileContext: { + ...request.fileContext, + leftFileContent: `${request.fileContext.leftFileContent}${session.recommendations[0].content}`, + }, + nextToken: undefined, + }, + supplementalMetadata: currentSession.requestContext.supplementalMetadata, + } + } else { + currentSession.requestContext = await EditorContext.buildListRecommendationRequest( + editor as vscode.TextEditor, + this.nextToken, + config.isSuggestionsWithCodeReferencesEnabled + ) + } } else { - session.requestContext = { + currentSession.requestContext = { request: { - ...session.requestContext.request, + ...currentSession.requestContext.request, // Putting nextToken assignment in the end so it overwrites the existing nextToken nextToken: this.nextToken, - }, - supplementalMetadata: session.requestContext.supplementalMetadata, + } as ListRecommendationsRequest, + supplementalMetadata: currentSession.requestContext.supplementalMetadata, } } + // } } else { - session.requestContext = await EditorContext.buildGenerateRecommendationRequest(editor as vscode.TextEditor) + currentSession.requestContext = await EditorContext.buildGenerateRecommendationRequest( + editor as vscode.TextEditor + ) } - const request = session.requestContext.request - // record preprocessing end time + const request = generate + ? (currentSession.requestContext.request as GenerateRecommendationsRequest) + : (currentSession.requestContext.request as ListRecommendationsRequest) + getLogger().info(`request to inline recommendation : \n${JSON.stringify(request)}`) TelemetryHelper.instance.setPreprocessEndTime() // set start pos for non pagination call or first pagination call if (!pagination || (pagination && page === 0)) { - session.startPos = editor.selection.active - session.startCursorOffset = editor.document.offsetAt(session.startPos) - session.leftContextOfCurrentLine = EditorContext.getLeftContext(editor, session.startPos.line) - session.triggerType = triggerType - session.autoTriggerType = autoTriggerType + currentSession.startPos = editor.selection.active + currentSession.startCursorOffset = editor.document.offsetAt(currentSession.startPos) + currentSession.leftContextOfCurrentLine = EditorContext.getLeftContext(editor, currentSession.startPos.line) + currentSession.triggerType = triggerType + currentSession.autoTriggerType = autoTriggerType /** * Validate request @@ -253,9 +296,10 @@ export class RecommendationHandler { requestId = resp?.$response && resp?.$response?.requestId nextToken = resp?.nextToken ? resp?.nextToken : '' sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid'] + getLogger().info(`response from inline recommendation : \n${JSON.stringify(resp)}`) TelemetryHelper.instance.setFirstResponseRequestId(requestId) if (page === 0) { - session.setTimeToFirstRecommendation(performance.now()) + currentSession.setTimeToFirstRecommendation(performance.now()) } if (nextToken === '') { TelemetryHelper.instance.setAllPaginationEndTime() @@ -291,6 +335,7 @@ export class RecommendationHandler { reason = error ? String(error) : 'unknown' } } finally { + console.log('final session', currentSession) const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone let msg = indent( @@ -301,9 +346,9 @@ export class RecommendationHandler { vscode version: '${vscode.version}', extension version: '${extensionVersion}', filename: '${EditorContext.getFileName(editor)}', - left context of line: '${session.leftContextOfCurrentLine}', - line number: ${session.startPos.line}, - character location: ${session.startPos.character}, + left context of line: '${currentSession.leftContextOfCurrentLine}', + line number: ${currentSession.startPos.line}, + character location: ${currentSession.startPos.character}, latency: ${latency} ms. Recommendations:`, 4, @@ -311,11 +356,11 @@ export class RecommendationHandler { ).trimStart() for (const [index, item] of recommendations.entries()) { msg += `\n ${index.toString().padStart(2, '0')}: ${indent(item.content, 8, true).trim()}` - session.requestIdList.push(requestId) + currentSession.requestIdList.push(requestId) } getLogger().debug(msg) if (invocationResult === 'Succeeded') { - CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount() + CodeWhispererCodeCoverageTracker.getTracker(currentSession.language)?.incrementServiceInvocationCount() UserWrittenCodeTracker.instance.onQFeatureInvoked() } else { if ( @@ -343,13 +388,13 @@ export class RecommendationHandler { TelemetryHelper.instance.recordServiceInvocationTelemetry( requestId, sessionId, - session.recommendations.length + recommendations.length - 1, + currentSession.recommendations.length + recommendations.length - 1, invocationResult, latency, - session.language, - session.taskType, + currentSession.language, + currentSession.taskType, reason, - session.requestContext.supplementalMetadata + currentSession.requestContext.supplementalMetadata ) } } @@ -358,67 +403,68 @@ export class RecommendationHandler { return Promise.resolve({ result: invocationResult, errorMessage: errorMessage, - recommendationCount: session.recommendations.length, + recommendationCount: currentSession.recommendations.length, }) } const typedPrefix = editor.document - .getText(new vscode.Range(session.startPos, editor.selection.active)) + .getText(new vscode.Range(currentSession.startPos, editor.selection.active)) .replace('\r\n', '\n') if (recommendations.length > 0) { TelemetryHelper.instance.setTypeAheadLength(typedPrefix.length) // mark suggestions that does not match typeahead when arrival as Discard // these suggestions can be marked as Showed if typeahead can be removed with new inline API for (const [i, r] of recommendations.entries()) { - const recommendationIndex = i + session.recommendations.length + const recommendationIndex = i + currentSession.recommendations.length if ( !r.content.startsWith(typedPrefix) && - session.getSuggestionState(recommendationIndex) === undefined + currentSession.getSuggestionState(recommendationIndex) === undefined ) { - session.setSuggestionState(recommendationIndex, 'Discard') + currentSession.setSuggestionState(recommendationIndex, 'Discard') } - session.setCompletionType(recommendationIndex, r) + currentSession.setCompletionType(recommendationIndex, r) } - session.recommendations = pagination ? session.recommendations.concat(recommendations) : recommendations - if (isInlineCompletionEnabled() && this.hasAtLeastOneValidSuggestion(typedPrefix)) { + currentSession.recommendations = pagination ? currentSession.recommendations.concat(recommendations) : recommendations + if (isInlineCompletionEnabled() && this.hasAtLeastOneValidSuggestion(typedPrefix, currentSession)) { this._onDidReceiveRecommendation.fire() } } this.requestId = requestId - session.sessionId = sessionId + currentSession.sessionId = sessionId this.nextToken = nextToken // send Empty userDecision event if user receives no recommendations in this session at all. if (invocationResult === 'Succeeded' && nextToken === '') { // case 1: empty list of suggestion [] - if (session.recommendations.length === 0) { - session.requestIdList.push(requestId) + if (currentSession.recommendations.length === 0) { + currentSession.requestIdList.push(requestId) // Received an empty list of recommendations TelemetryHelper.instance.recordUserDecisionTelemetryForEmptyList( - session.requestIdList, + currentSession.requestIdList, sessionId, page, runtimeLanguageContext.getLanguageContext( editor.document.languageId, path.extname(editor.document.fileName) ).language, - session.requestContext.supplementalMetadata + currentSession.requestContext.supplementalMetadata ) } // case 2: non empty list of suggestion but with (a) empty content or (b) non-matching typeahead - else if (!this.hasAtLeastOneValidSuggestion(typedPrefix)) { + else if (!this.hasAtLeastOneValidSuggestion(typedPrefix, currentSession)) { this.reportUserDecisions(-1) } } + console.log('final session', currentSession) return Promise.resolve({ result: invocationResult, errorMessage: errorMessage, - recommendationCount: session.recommendations.length, + recommendationCount: currentSession.recommendations.length, }) } - hasAtLeastOneValidSuggestion(typedPrefix: string): boolean { + hasAtLeastOneValidSuggestion(typedPrefix: string, session: CodeWhispererSession): boolean { return session.recommendations.some((r) => r.content.trim() !== '' && r.content.startsWith(typedPrefix)) } @@ -444,14 +490,25 @@ export class RecommendationHandler { * Clear recommendation state */ clearRecommendations() { + const session = CodeWhispererSessionState.instance.getSession() session.requestIdList = [] session.recommendations = [] + // session.nextRecommendations = [] session.suggestionStates = new Map() session.completionTypes = new Map() this.requestId = '' session.sessionId = '' this.nextToken = '' session.requestContext.supplementalMetadata = undefined + // nextSession.requestIdList = [] + // nextSession.recommendations = [] + // // nextSession.nextRecommendations = [] + // nextSession.suggestionStates = new Map() + // nextSession.completionTypes = new Map() + // this.requestId = '' + // nextSession.sessionId = '' + // this.nextToken = '' + // nextSession.requestContext.supplementalMetadata = undefined } async clearInlineCompletionStates() { @@ -473,6 +530,7 @@ export class RecommendationHandler { } reportDiscardedUserDecisions() { + const session = CodeWhispererSessionState.instance.getSession() for (const [i, _] of session.recommendations.entries()) { session.setSuggestionState(i, 'Discard') } @@ -483,6 +541,7 @@ export class RecommendationHandler { * Emits telemetry reflecting user decision for current recommendation. */ reportUserDecisions(acceptIndex: number) { + const session = CodeWhispererSessionState.instance.getSession() if (session.sessionId === '' || this.requestId === '') { return } @@ -512,6 +571,7 @@ export class RecommendationHandler { showPrompt: boolean = false, response: GetRecommendationsResponse ): boolean { + const session = CodeWhispererSessionState.instance.getSession() const reject = () => { this.reportUserDecisions(-1) } @@ -593,6 +653,12 @@ export class RecommendationHandler { } async showRecommendation(indexShift: number, noSuggestionVisible: boolean = false) { + const session = CodeWhispererSessionState.instance.getSession() + + if (!indexShift && session.recommendations.length) { + // TODO: gate behind A/B group if needed + await this.fetchNextRecommendations() + } await lock.acquire(updateInlineLockKey, async () => { if (!vscode.window.state.focused) { this.reportDiscardedUserDecisions() @@ -623,7 +689,7 @@ export class RecommendationHandler { } if (noSuggestionVisible) { await vscode.commands.executeCommand(`editor.action.inlineSuggest.trigger`) - this.sendPerceivedLatencyTelemetry() + this.sendPerceivedLatencyTelemetry(session) } }) } @@ -654,7 +720,49 @@ export class RecommendationHandler { return this.inlineCompletionProvider?.getActiveItemIndex !== undefined } + async showNextRecommendations() { + getLogger().info('show pre-loaded recommendation') + await this.showRecommendation(0, false) + } + + async getConfigEntry(): Promise { + const codewhispererSettings = CodeWhispererSettings.instance + const isShowMethodsEnabled: boolean = + vscode.workspace.getConfiguration('editor').get('suggest.showMethods') || false + const isAutomatedTriggerEnabled: boolean = CodeSuggestionsState.instance.isSuggestionsEnabled() + const isManualTriggerEnabled: boolean = true + const isSuggestionsWithCodeReferencesEnabled = codewhispererSettings.isSuggestionsWithCodeReferencesEnabled() + + // TODO:remove isManualTriggerEnabled + return { + isShowMethodsEnabled, + isManualTriggerEnabled, + isAutomatedTriggerEnabled, + isSuggestionsWithCodeReferencesEnabled, + } + } + + async fetchNextRecommendations() { + const session = CodeWhispererSessionState.instance.getSession() + const client = new codewhispererClient.DefaultCodeWhispererClient() + const editor = vscode.window.activeTextEditor + if (!editor) return + + this.getRecommendations( + client, + editor, + session.triggerType, + await this.getConfigEntry(), + session.autoTriggerType, + true, + 0, + false, + true + ) + } + async tryShowRecommendation() { + const session = CodeWhispererSessionState.instance.getSession() const editor = vscode.window.activeTextEditor if (editor === undefined) { return @@ -683,7 +791,7 @@ export class RecommendationHandler { } } - private sendPerceivedLatencyTelemetry() { + private sendPerceivedLatencyTelemetry(session: CodeWhispererSession) { if (vscode.window.activeTextEditor) { const languageContext = runtimeLanguageContext.getLanguageContext( vscode.window.activeTextEditor.document.languageId, diff --git a/packages/core/src/codewhisperer/service/referenceLogViewProvider.ts b/packages/core/src/codewhisperer/service/referenceLogViewProvider.ts index 9ec20b8cb44..02055f0aded 100644 --- a/packages/core/src/codewhisperer/service/referenceLogViewProvider.ts +++ b/packages/core/src/codewhisperer/service/referenceLogViewProvider.ts @@ -10,7 +10,7 @@ import * as CodeWhispererConstants from '../models/constants' import { CodeWhispererSettings } from '../util/codewhispererSettings' import globals from '../../shared/extensionGlobals' import { AuthUtil } from '../util/authUtil' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' export class ReferenceLogViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'aws.codeWhisperer.referenceLog' @@ -68,6 +68,7 @@ export class ReferenceLogViewProvider implements vscode.WebviewViewProvider { reference.recommendationContentSpan.start, reference.recommendationContentSpan.end ) + const session = CodeWhispererSessionState.instance.getSession() const firstCharLineNumber = editor.document.positionAt(session.startCursorOffset + reference.recommendationContentSpan.start).line + 1 diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index 042cd947124..26fb3b70a1f 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -14,40 +14,88 @@ import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendat import { Position } from 'vscode' import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model' -class CodeWhispererSession { - static #instance: CodeWhispererSession +export class CodeWhispererSessionState { + static #instance: CodeWhispererSessionState + session: CodeWhispererSession + nextSession: CodeWhispererSession + + constructor() { + this.session = new CodeWhispererSession() + this.nextSession = new CodeWhispererSession() + } + public static get instance() { + return (this.#instance ??= new CodeWhispererSessionState()) + } - // Per-session states - sessionId = '' - requestIdList: string[] = [] - startPos = new Position(0, 0) - startCursorOffset = 0 - leftContextOfCurrentLine = '' + getSession() { + return this.session + } + + setSession(session: CodeWhispererSession) { + this.session = session + } + + getNextSession() { + return this.nextSession + } + + setNextSession(session: CodeWhispererSession) { + this.nextSession = session + } +} + +export class CodeWhispererSession { + // static #instance: CodeWhispererSession + sessionId: string + requestIdList: string[] + startPos: Position + startCursorOffset: number + leftContextOfCurrentLine: string requestContext: { - request: ListRecommendationsRequest | GenerateRecommendationsRequest + request: ListRecommendationsRequest | GenerateRecommendationsRequest | undefined supplementalMetadata: CodeWhispererSupplementalContext | undefined - } = { request: {} as any, supplementalMetadata: {} as any } - language: CodewhispererLanguage = 'python' + } + language: CodewhispererLanguage taskType: CodewhispererGettingStartedTask | undefined - triggerType: CodewhispererTriggerType = 'OnDemand' + triggerType: CodewhispererTriggerType autoTriggerType: CodewhispererAutomatedTriggerType | undefined + recommendations: Recommendation[] + suggestionStates: Map + completionTypes: Map + fetchCredentialStartTime: number + sdkApiCallStartTime: number + invokeSuggestionStartTime: number + preprocessEndTime: number + timeToFirstRecommendation: number + firstSuggestionShowTime: number + perceivedLatency: number - // Various states of recommendations - recommendations: Recommendation[] = [] - suggestionStates = new Map() - completionTypes = new Map() - - // Some other variables for client component latency - fetchCredentialStartTime = 0 - sdkApiCallStartTime = 0 - invokeSuggestionStartTime = 0 - preprocessEndTime = 0 - timeToFirstRecommendation = 0 - firstSuggestionShowTime = 0 - perceivedLatency = 0 + // Per-session states + constructor() { + this.sessionId = '' + this.requestIdList = [] + this.startPos = new Position(0, 0) + this.startCursorOffset = 0 + this.leftContextOfCurrentLine = '' + this.requestContext = { request: undefined, supplementalMetadata: undefined } + this.language = 'python' + this.taskType = undefined + this.triggerType = 'OnDemand' + this.autoTriggerType = undefined - public static get instance() { - return (this.#instance ??= new CodeWhispererSession()) + // Various states of recommendations + this.recommendations = [] + this.suggestionStates = new Map() + this.completionTypes = new Map() + + // Some other variables for client component latency + this.fetchCredentialStartTime = 0 + this.sdkApiCallStartTime = 0 + this.invokeSuggestionStartTime = 0 + this.preprocessEndTime = 0 + this.timeToFirstRecommendation = 0 + this.firstSuggestionShowTime = 0 + this.perceivedLatency = 0 } setFetchCredentialStart() { @@ -89,7 +137,7 @@ class CodeWhispererSession { if (triggerType === 'OnDemand') { return this.timeToFirstRecommendation } else { - return session.firstSuggestionShowTime - vsCodeState.lastUserModificationTime + return this.firstSuggestionShowTime - vsCodeState.lastUserModificationTime } } @@ -118,6 +166,3 @@ class CodeWhispererSession { this.completionTypes.clear() } } - -// TODO: convert this to a function call -export const session = CodeWhispererSession.instance diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 5276d869bb9..d58b97eee47 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -22,7 +22,7 @@ import { getSelectedCustomization } from './customizationUtil' import { AuthUtil } from './authUtil' import { isAwsError } from '../../shared/errors' import { getLogger } from '../../shared/logger' -import { session } from './codeWhispererSession' +import { CodeWhispererSessionState } from './codeWhispererSession' import { CodeWhispererSupplementalContext } from '../models/model' import { FeatureConfigProvider } from '../../shared/featureConfig' import { CodeScanRemediationsEventType } from '../client/codewhispereruserclient' @@ -58,6 +58,7 @@ export class TelemetryHelper { // use this to distinguish DocumentChangeEvent from CWSPR or from other sources public lastSuggestionInDisplay = '' + private session = CodeWhispererSessionState.instance.getSession() constructor() {} @@ -125,21 +126,21 @@ export class TelemetryHelper { supplementalContextMetadata?: CodeWhispererSupplementalContext | undefined ) { const event = { - codewhispererAutomatedTriggerType: session.autoTriggerType, - codewhispererCursorOffset: session.startCursorOffset, + codewhispererAutomatedTriggerType: this.session.autoTriggerType, + codewhispererCursorOffset: this.session.startCursorOffset, codewhispererCustomizationArn: getSelectedCustomization().arn, CodewhispererGettingStartedTask: taskType, codewhispererImportRecommendationEnabled: CodeWhispererSettings.instance.isImportRecommendationEnabled(), codewhispererLastSuggestionIndex: lastSuggestionIndex, codewhispererLanguage: language, - codewhispererLineNumber: session.startPos.line, + codewhispererLineNumber: this.session.startPos.line, codewhispererRequestId: requestId ? requestId : undefined, codewhispererSessionId: sessionId ? sessionId : undefined, codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLatency: supplementalContextMetadata?.latency, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: session.triggerType, + codewhispererTriggerType: this.session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, duration: duration || 0, reason: reason ? reason.substring(0, 200) : undefined, @@ -160,7 +161,7 @@ export class TelemetryHelper { telemetry.codewhisperer_userDecision.emit({ codewhispererCompletionType: 'Line', - codewhispererGettingStartedTask: session.taskType, + codewhispererGettingStartedTask: this.session.taskType, codewhispererLanguage: language, codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[0], @@ -172,23 +173,23 @@ export class TelemetryHelper { codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: session.triggerType, + codewhispererTriggerType: this.session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, }) telemetry.codewhisperer_userTriggerDecision.emit({ - codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererAutomatedTriggerType: this.session.autoTriggerType, codewhispererClassifierResult: this.classifierResult, codewhispererClassifierThreshold: this.classifierThreshold, codewhispererCompletionType: 'Line', - codewhispererCursorOffset: session.startCursorOffset, + codewhispererCursorOffset: this.session.startCursorOffset, codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), codewhispererFirstRequestId: requestIdList[0], - codewhispererGettingStartedTask: session.taskType, + codewhispererGettingStartedTask: this.session.taskType, codewhispererLanguage: language, - codewhispererLineNumber: session.startPos.line, + codewhispererLineNumber: this.session.startPos.line, codewhispererPreviousSuggestionState: this.prevTriggerDecision, codewhispererSessionId: sessionId, codewhispererSuggestionCount: 0, @@ -205,8 +206,8 @@ export class TelemetryHelper { codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime ? performance.now() - this.lastTriggerDecisionTime : undefined, - codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, - codewhispererTriggerType: session.triggerType, + codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, + codewhispererTriggerType: this.session.triggerType, codewhispererTypeaheadLength: this.typeAheadLength, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, @@ -280,8 +281,8 @@ export class TelemetryHelper { const event: CodewhispererUserDecision = { // TODO: maintain a list of RecommendationContexts with both recommendation and requestId in it, instead of two separate list items. codewhispererCompletionType: this.getCompletionType(i, completionTypes), - codewhispererGettingStartedTask: session.taskType, - codewhispererLanguage: session.language, + codewhispererGettingStartedTask: this.session.taskType, + codewhispererLanguage: this.session.language, codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[i], codewhispererSessionId: sessionId ? sessionId : undefined, @@ -293,7 +294,7 @@ export class TelemetryHelper { codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: session.triggerType, + codewhispererTriggerType: this.session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, } @@ -339,13 +340,13 @@ export class TelemetryHelper { return } const aggregated: CodewhispererUserTriggerDecision = { - codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererAutomatedTriggerType: this.session.autoTriggerType, codewhispererCompletionType: events[0].codewhispererCompletionType, - codewhispererCursorOffset: session.startCursorOffset, + codewhispererCursorOffset: this.session.startCursorOffset, codewhispererFirstRequestId: requestId, - codewhispererGettingStartedTask: session.taskType, + codewhispererGettingStartedTask: this.session.taskType, codewhispererLanguage: events[0].codewhispererLanguage, - codewhispererLineNumber: session.startPos.line, + codewhispererLineNumber: this.session.startPos.line, codewhispererSessionId: sessionId, codewhispererSuggestionCount: events.length, codewhispererSuggestionImportCount: events @@ -387,51 +388,51 @@ export class TelemetryHelper { .map((e) => e.codewhispererSuggestionCount) .reduce((a, b) => a + b, 0) - const aggregated: CodewhispererUserTriggerDecision = { - codewhispererAutomatedTriggerType: autoTriggerType, - codewhispererCharactersAccepted: acceptedRecommendationContent.length, - codewhispererClassifierResult: this.classifierResult, - codewhispererClassifierThreshold: this.classifierThreshold, - codewhispererCompletionType: aggregatedCompletionType, - codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, - codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, - codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), - codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, - codewhispererGettingStartedTask: session.taskType, - codewhispererLanguage: language, - codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, - codewhispererPreviousSuggestionState: this.prevTriggerDecision, - codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, - codewhispererSuggestionCount: suggestionCount, - codewhispererSuggestionImportCount: this.sessionDecisions - .map((e) => e.codewhispererSuggestionImportCount || 0) - .reduce((a, b) => a + b, 0), - codewhispererSuggestionState: aggregatedSuggestionState, - codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, - codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, - // eslint-disable-next-line id-length - codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification - ? this.timeSinceLastModification - : undefined, - codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime - ? performance.now() - this.lastTriggerDecisionTime - : undefined, - codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, - codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, - codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, - codewhispererTypeaheadLength: this.typeAheadLength, - credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, - traceId: this.traceId, - } + const aggregated: CodewhispererUserTriggerDecision = { + codewhispererAutomatedTriggerType: autoTriggerType, + codewhispererCharactersAccepted: acceptedRecommendationContent.length, + codewhispererClassifierResult: this.classifierResult, + codewhispererClassifierThreshold: this.classifierThreshold, + codewhispererCompletionType: aggregatedCompletionType, + codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, + codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), + codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, + codewhispererGettingStartedTask: this.session.taskType, + codewhispererLanguage: language, + codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, + codewhispererPreviousSuggestionState: this.prevTriggerDecision, + codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, + codewhispererSuggestionCount: suggestionCount, + codewhispererSuggestionImportCount: this.sessionDecisions + .map((e) => e.codewhispererSuggestionImportCount || 0) + .reduce((a, b) => a + b, 0), + codewhispererSuggestionState: aggregatedSuggestionState, + codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, + codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + // eslint-disable-next-line id-length + codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification + ? this.timeSinceLastModification + : undefined, + codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime + ? performance.now() - this.lastTriggerDecisionTime + : undefined, + codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, + codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, + codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, + codewhispererTypeaheadLength: this.typeAheadLength, + credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, + traceId: this.traceId, + } telemetry.codewhisperer_userTriggerDecision.emit(aggregated) this.prevTriggerDecision = this.getAggregatedSuggestionState(this.sessionDecisions) this.lastTriggerDecisionTime = performance.now() // When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value // and client side will set this value to 0.0. - let e2eLatency = session.firstSuggestionShowTime - session.invokeSuggestionStartTime + let e2eLatency = this.session.firstSuggestionShowTime - this.session.invokeSuggestionStartTime if (aggregatedSuggestionState !== 'Reject' && aggregatedSuggestionState !== 'Accept') { e2eLatency = 0.0 } @@ -451,8 +452,8 @@ export class TelemetryHelper { completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType), suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState), recommendationLatencyMilliseconds: e2eLatency, - triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, - perceivedLatencyMilliseconds: session.perceivedLatency, + triggerToResponseLatencyMilliseconds: this.session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: this.session.perceivedLatency, timestamp: new Date(Date.now()), suggestionReferenceCount: referenceCount, generatedLine: generatedLines, @@ -512,8 +513,8 @@ export class TelemetryHelper { this.triggerChar = '' this.typeAheadLength = 0 this.timeSinceLastModification = 0 - session.timeToFirstRecommendation = 0 - session.perceivedLatency = 0 + this.session.timeToFirstRecommendation = 0 + this.session.perceivedLatency = 0 this.classifierResult = undefined this.classifierThreshold = undefined } @@ -592,31 +593,31 @@ export class TelemetryHelper { } public resetClientComponentLatencyTime() { - session.invokeSuggestionStartTime = 0 - session.preprocessEndTime = 0 - session.sdkApiCallStartTime = 0 + this.session.invokeSuggestionStartTime = 0 + this.session.preprocessEndTime = 0 + this.session.sdkApiCallStartTime = 0 this._sdkApiCallEndTime = 0 - session.fetchCredentialStartTime = 0 - session.firstSuggestionShowTime = 0 + this.session.fetchCredentialStartTime = 0 + this.session.firstSuggestionShowTime = 0 this._allPaginationEndTime = 0 this._firstResponseRequestId = '' } public setPreprocessEndTime() { - if (session.preprocessEndTime !== 0) { + if (this.session.preprocessEndTime !== 0) { getLogger().warn(`inline completion preprocessEndTime has been set and not reset correctly`) } - session.preprocessEndTime = performance.now() + this.session.preprocessEndTime = performance.now() } /** This method is assumed to be invoked first at the start of execution **/ public setInvokeSuggestionStartTime() { this.resetClientComponentLatencyTime() - session.invokeSuggestionStartTime = performance.now() + this.session.invokeSuggestionStartTime = performance.now() } public setSdkApiCallEndTime() { - if (this._sdkApiCallEndTime === 0 && session.sdkApiCallStartTime !== 0) { + if (this._sdkApiCallEndTime === 0 && this.session.sdkApiCallStartTime !== 0) { this._sdkApiCallEndTime = performance.now() } } @@ -628,8 +629,8 @@ export class TelemetryHelper { } public setFirstSuggestionShowTime() { - if (session.firstSuggestionShowTime === 0 && this._sdkApiCallEndTime !== 0) { - session.firstSuggestionShowTime = performance.now() + if (this.session.firstSuggestionShowTime === 0 && this._sdkApiCallEndTime !== 0) { + this.session.firstSuggestionShowTime = performance.now() } } @@ -642,22 +643,22 @@ export class TelemetryHelper { // report client component latency after all pagination call finish // and at least one suggestion is shown to the user public tryRecordClientComponentLatency() { - if (session.firstSuggestionShowTime === 0 || this._allPaginationEndTime === 0) { + if (this.session.firstSuggestionShowTime === 0 || this._allPaginationEndTime === 0) { return } telemetry.codewhisperer_clientComponentLatency.emit({ - codewhispererAllCompletionsLatency: this._allPaginationEndTime - session.sdkApiCallStartTime, + codewhispererAllCompletionsLatency: this._allPaginationEndTime - this.session.sdkApiCallStartTime, codewhispererCompletionType: 'Line', - codewhispererCredentialFetchingLatency: session.sdkApiCallStartTime - session.fetchCredentialStartTime, + codewhispererCredentialFetchingLatency: this.session.sdkApiCallStartTime - this.session.fetchCredentialStartTime, codewhispererCustomizationArn: getSelectedCustomization().arn, - codewhispererEndToEndLatency: session.firstSuggestionShowTime - session.invokeSuggestionStartTime, - codewhispererFirstCompletionLatency: this._sdkApiCallEndTime - session.sdkApiCallStartTime, - codewhispererLanguage: session.language, - codewhispererPostprocessingLatency: session.firstSuggestionShowTime - this._sdkApiCallEndTime, - codewhispererPreprocessingLatency: session.preprocessEndTime - session.invokeSuggestionStartTime, + codewhispererEndToEndLatency: this.session.firstSuggestionShowTime - this.session.invokeSuggestionStartTime, + codewhispererFirstCompletionLatency: this._sdkApiCallEndTime - this.session.sdkApiCallStartTime, + codewhispererLanguage: this.session.language, + codewhispererPostprocessingLatency: this.session.firstSuggestionShowTime - this._sdkApiCallEndTime, + codewhispererPreprocessingLatency: this.session.preprocessEndTime - this.session.invokeSuggestionStartTime, codewhispererRequestId: this._firstResponseRequestId, - codewhispererSessionId: session.sessionId, - codewhispererTriggerType: session.triggerType, + codewhispererSessionId: this.session.sessionId, + codewhispererTriggerType: this.session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, }) } diff --git a/packages/core/src/codewhisperer/views/lineAnnotationController.ts b/packages/core/src/codewhisperer/views/lineAnnotationController.ts index 12a52f9b4eb..39732c84064 100644 --- a/packages/core/src/codewhisperer/views/lineAnnotationController.ts +++ b/packages/core/src/codewhisperer/views/lineAnnotationController.ts @@ -16,7 +16,7 @@ import { Container } from '../service/serviceContainer' import { telemetry } from '../../shared/telemetry/telemetry' import { getLogger } from '../../shared/logger/logger' import { Commands } from '../../shared/vscode/commands2' -import { session } from '../util/codeWhispererSession' +import { CodeWhispererSessionState } from '../util/codeWhispererSession' import { RecommendationHandler } from '../service/recommendationHandler' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { setContext } from '../../shared' @@ -75,6 +75,7 @@ export class AutotriggerState implements AnnotationState { static acceptedCount = 0 updateState(changeSource: AnnotationChangeSource, force: boolean): AnnotationState | undefined { + const session = CodeWhispererSessionState.instance.getSession() if (AutotriggerState.acceptedCount < RecommendationService.instance.acceptedSuggestionCount) { return new ManualtriggerState() } else if (session.recommendations.length > 0 && RecommendationHandler.instance.isSuggestionVisible()) { diff --git a/packages/core/src/test/codewhisperer/testUtil.ts b/packages/core/src/test/codewhisperer/testUtil.ts index 027087a86b6..b27bc116c06 100644 --- a/packages/core/src/test/codewhisperer/testUtil.ts +++ b/packages/core/src/test/codewhisperer/testUtil.ts @@ -16,7 +16,7 @@ import { MockDocument } from '../fake/fakeDocument' import { getLogger } from '../../shared/logger' import { CodeWhispererCodeCoverageTracker } from '../../codewhisperer/tracker/codewhispererCodeCoverageTracker' import globals from '../../shared/extensionGlobals' -import { session } from '../../codewhisperer/util/codeWhispererSession' +import { CodeWhispererSessionState } from '../../codewhisperer/util/codeWhispererSession' import { DefaultAWSClientBuilder, ServiceOptions } from '../../shared/awsClientBuilder' import { FakeAwsContext } from '../utilities/fakeAwsContext' import { HttpResponse, Service } from 'aws-sdk' @@ -33,7 +33,10 @@ export async function resetCodeWhispererGlobalVariables() { vsCodeState.isCodeWhispererEditing = false CodeWhispererCodeCoverageTracker.instances.clear() globals.telemetry.logger.clear() + const session = CodeWhispererSessionState.instance.getSession() session.reset() + const nextSession = CodeWhispererSessionState.instance.nextSession + nextSession.reset() await globals.globalState.clear() await CodeSuggestionsState.instance.setSuggestionsEnabled(true) await RecommendationHandler.instance.clearInlineCompletionStates() diff --git a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts b/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts index 0038795ad89..d3ad4719c8d 100644 --- a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts +++ b/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts @@ -10,7 +10,7 @@ import { setValidConnection, skipTestIfNoValidConn } from '../util/connection' import { RecommendationHandler } from '../../codewhisperer/service/recommendationHandler' import { createMockTextEditor, resetCodeWhispererGlobalVariables } from '../../test/codewhisperer/testUtil' import { invokeRecommendation } from '../../codewhisperer/commands/invokeRecommendation' -import { session } from '../../codewhisperer/util/codeWhispererSession' +import { CodeWhispererSessionState } from '../../codewhisperer/util/codeWhispererSession' /* New model deployment may impact references returned. @@ -62,7 +62,8 @@ describe('CodeWhisperer service invocation', async function () { }) it('trigger known to return recs with references returns rec with reference', async function () { - // check that handler is empty before invocation + //check that handler is empty before invocation + const session = CodeWhispererSessionState.instance.getSession() const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -97,6 +98,7 @@ describe('CodeWhisperer service invocation', async function () { it('trigger known to return rec with references does not return rec with references when reference tracker setting is off', async function () { // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId + const session = CodeWhispererSessionState.instance.getSession() const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() diff --git a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts b/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts index d4265d13982..00fad503ccc 100644 --- a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts +++ b/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts @@ -19,9 +19,10 @@ import { KeyStrokeHandler } from '../../codewhisperer/service/keyStrokeHandler' import { sleep } from '../../shared/utilities/timeoutUtils' import { invokeRecommendation } from '../../codewhisperer/commands/invokeRecommendation' import { getTestWorkspaceFolder } from '../../testInteg/integrationTestsUtilities' -import { session } from '../../codewhisperer/util/codeWhispererSession' +import { CodeWhispererSessionState } from '../../codewhisperer/util/codeWhispererSession' describe('CodeWhisperer service invocation', async function () { + const session = CodeWhispererSessionState.instance.getSession() let validConnection: boolean const client = new codewhispererClient.DefaultCodeWhispererClient() const config: ConfigurationEntry = { @@ -43,7 +44,7 @@ describe('CodeWhisperer service invocation', async function () { }) it('manual trigger returns valid recommendation response', async function () { - // check that handler is empty before invocation + //check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -65,7 +66,7 @@ describe('CodeWhisperer service invocation', async function () { }) it('auto trigger returns valid recommendation response', async function () { - // check that handler is empty before invocation + //check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -102,6 +103,7 @@ describe('CodeWhisperer service invocation', async function () { // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId + const session = CodeWhispererSessionState.instance.getSession() const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() From 244e8174751b682699c109fbcce749740f612847 Mon Sep 17 00:00:00 2001 From: Zoe Lin Date: Mon, 13 Jan 2025 14:22:51 -0800 Subject: [PATCH 02/17] fix --- .../src/codewhisperer/commands/onInlineAcceptance.ts | 5 +++-- .../src/codewhisperer/service/recommendationHandler.ts | 10 +--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index 2dc1956d6dc..9581a15e729 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -144,11 +144,12 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) if (acceptanceEntry.acceptIndex == 0) { - // TODO: gate behind A/B group if needed const nextSession = CodeWhispererSessionState.instance.getNextSession() nextSession.startPos = acceptanceEntry.editor.selection.active CodeWhispererSessionState.instance.setSession(nextSession) - await RecommendationHandler.instance.showNextRecommendations() + if (nextSession.recommendations.length) { + await RecommendationHandler.instance.tryShowRecommendation() + } } } } diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index d5f440707e5..f6824191619 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -171,7 +171,6 @@ export class RecommendationHandler { pagination: boolean = true, page: number = 0, generate: boolean = isIamConnection(AuthUtil.instance.conn), - // currentSession: any = this.session, isNextSession: boolean = false ): Promise { let invocationResult: 'Succeeded' | 'Failed' = 'Failed' @@ -335,7 +334,6 @@ export class RecommendationHandler { reason = error ? String(error) : 'unknown' } } finally { - console.log('final session', currentSession) const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone let msg = indent( @@ -399,7 +397,7 @@ export class RecommendationHandler { } } - if (this.isCancellationRequested()) { + if (!isNextSession && this.isCancellationRequested()) { return Promise.resolve({ result: invocationResult, errorMessage: errorMessage, @@ -456,7 +454,6 @@ export class RecommendationHandler { this.reportUserDecisions(-1) } } - console.log('final session', currentSession) return Promise.resolve({ result: invocationResult, errorMessage: errorMessage, @@ -720,11 +717,6 @@ export class RecommendationHandler { return this.inlineCompletionProvider?.getActiveItemIndex !== undefined } - async showNextRecommendations() { - getLogger().info('show pre-loaded recommendation') - await this.showRecommendation(0, false) - } - async getConfigEntry(): Promise { const codewhispererSettings = CodeWhispererSettings.instance const isShowMethodsEnabled: boolean = From 7fc546d0e5fcaff5c2bbb409e0cff1688c9f9446 Mon Sep 17 00:00:00 2001 From: Zoe Lin Date: Tue, 14 Jan 2025 17:42:02 -0800 Subject: [PATCH 03/17] rebase --- .../service/nextRecommendationProvider.ts | 28 ------- .../src/codewhisperer/util/telemetryHelper.ts | 83 ++++++++++--------- 2 files changed, 42 insertions(+), 69 deletions(-) delete mode 100644 packages/core/src/codewhisperer/service/nextRecommendationProvider.ts diff --git a/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts b/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts deleted file mode 100644 index 00f79cf4704..00000000000 --- a/packages/core/src/codewhisperer/service/nextRecommendationProvider.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ConfigurationEntry, DefaultCodeWhispererClient } from '..' -import { RecommendationService } from './recommendationService' -import { CodewhispererAutomatedTriggerType, CodewhispererTriggerType } from '../../shared/telemetry' - -export class NextRecommendationProvider { - async getNextRecommendation( - client: DefaultCodeWhispererClient, - editor: vscode.TextEditor, - config: ConfigurationEntry, - triggerType: CodewhispererTriggerType, - autoTriggerType?: CodewhispererAutomatedTriggerType - ) { - const recommendations = await RecommendationService.instance.generateRecommendation( - client, - editor, - triggerType, - config, - autoTriggerType - ) - return recommendations - } -} diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index d58b97eee47..60bc765256a 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -226,8 +226,8 @@ export class TelemetryHelper { completionType: 'LINE', suggestionState: 'EMPTY', recommendationLatencyMilliseconds: 0, - triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, - perceivedLatencyMilliseconds: session.perceivedLatency, + triggerToResponseLatencyMilliseconds: this.session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: this.session.perceivedLatency, timestamp: new Date(Date.now()), suggestionReferenceCount: 0, generatedLine: 0, @@ -388,44 +388,44 @@ export class TelemetryHelper { .map((e) => e.codewhispererSuggestionCount) .reduce((a, b) => a + b, 0) - const aggregated: CodewhispererUserTriggerDecision = { - codewhispererAutomatedTriggerType: autoTriggerType, - codewhispererCharactersAccepted: acceptedRecommendationContent.length, - codewhispererClassifierResult: this.classifierResult, - codewhispererClassifierThreshold: this.classifierThreshold, - codewhispererCompletionType: aggregatedCompletionType, - codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, - codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, - codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), - codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, - codewhispererGettingStartedTask: this.session.taskType, - codewhispererLanguage: language, - codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, - codewhispererPreviousSuggestionState: this.prevTriggerDecision, - codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, - codewhispererSuggestionCount: suggestionCount, - codewhispererSuggestionImportCount: this.sessionDecisions - .map((e) => e.codewhispererSuggestionImportCount || 0) - .reduce((a, b) => a + b, 0), - codewhispererSuggestionState: aggregatedSuggestionState, - codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, - codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, - // eslint-disable-next-line id-length - codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification - ? this.timeSinceLastModification - : undefined, - codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime - ? performance.now() - this.lastTriggerDecisionTime - : undefined, - codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, - codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, - codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, - codewhispererTypeaheadLength: this.typeAheadLength, - credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, - traceId: this.traceId, - } + const aggregated: CodewhispererUserTriggerDecision = { + codewhispererAutomatedTriggerType: autoTriggerType, + codewhispererCharactersAccepted: acceptedRecommendationContent.length, + codewhispererClassifierResult: this.classifierResult, + codewhispererClassifierThreshold: this.classifierThreshold, + codewhispererCompletionType: aggregatedCompletionType, + codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, + codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), + codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, + codewhispererGettingStartedTask: this.session.taskType, + codewhispererLanguage: language, + codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, + codewhispererPreviousSuggestionState: this.prevTriggerDecision, + codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, + codewhispererSuggestionCount: suggestionCount, + codewhispererSuggestionImportCount: this.sessionDecisions + .map((e) => e.codewhispererSuggestionImportCount || 0) + .reduce((a, b) => a + b, 0), + codewhispererSuggestionState: aggregatedSuggestionState, + codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, + codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + // eslint-disable-next-line id-length + codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification + ? this.timeSinceLastModification + : undefined, + codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime + ? performance.now() - this.lastTriggerDecisionTime + : undefined, + codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, + codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, + codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, + codewhispererTypeaheadLength: this.typeAheadLength, + credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, + traceId: this.traceId, + } telemetry.codewhisperer_userTriggerDecision.emit(aggregated) this.prevTriggerDecision = this.getAggregatedSuggestionState(this.sessionDecisions) this.lastTriggerDecisionTime = performance.now() @@ -649,7 +649,8 @@ export class TelemetryHelper { telemetry.codewhisperer_clientComponentLatency.emit({ codewhispererAllCompletionsLatency: this._allPaginationEndTime - this.session.sdkApiCallStartTime, codewhispererCompletionType: 'Line', - codewhispererCredentialFetchingLatency: this.session.sdkApiCallStartTime - this.session.fetchCredentialStartTime, + codewhispererCredentialFetchingLatency: + this.session.sdkApiCallStartTime - this.session.fetchCredentialStartTime, codewhispererCustomizationArn: getSelectedCustomization().arn, codewhispererEndToEndLatency: this.session.firstSuggestionShowTime - this.session.invokeSuggestionStartTime, codewhispererFirstCompletionLatency: this._sdkApiCallEndTime - this.session.sdkApiCallStartTime, From 5254a565f1035a9d15e8685bce11365e04a0f955 Mon Sep 17 00:00:00 2001 From: Zoe Lin Date: Tue, 21 Jan 2025 11:06:10 -0800 Subject: [PATCH 04/17] cleanup --- .../commands/onInlineAcceptance.ts | 2 +- .../service/inlineCompletionItemProvider.ts | 5 +-- .../service/recommendationHandler.ts | 42 ++++++------------- .../util/codeWhispererSession.ts | 5 +-- .../codewhisperer/referenceTracker.test.ts | 5 +-- .../codewhisperer/serviceInvocations.test.ts | 5 +-- 6 files changed, 21 insertions(+), 43 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index 9581a15e729..8f88112d5bb 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -143,7 +143,7 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept } RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) - if (acceptanceEntry.acceptIndex == 0) { + if (acceptanceEntry.acceptIndex === 0) { const nextSession = CodeWhispererSessionState.instance.getNextSession() nextSession.startPos = acceptanceEntry.editor.selection.active CodeWhispererSessionState.instance.setSession(nextSession) diff --git a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts index b77e2c5a371..dedd2531901 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts @@ -153,21 +153,18 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt // valid one. Otherwise, inline completion which utilizes this position will function // improperly. const start = document.validatePosition(this.startPos) - console.log('start pos', start) const end = position const iteratingIndexes = this.getIteratingIndexes() const prefix = document.getText(new vscode.Range(start, end)).replace(/\r\n/g, '\n') const matchedCount = this.session.recommendations.filter( - (r: any) => r.content.length > 0 && r.content.startsWith(prefix) && r.content !== prefix + (r) => r.content.length > 0 && r.content.startsWith(prefix) && r.content !== prefix ).length for (const i of iteratingIndexes) { const r = this.recommendations[i] - console.log('in show', r) const item = this.getInlineCompletionItem(document, r, start, end, i, prefix) if (item === undefined) { continue } - console.log('item', item) this.activeItemIndex = i this.session.setSuggestionState(i, 'Showed') ReferenceInlineProvider.instance.setInlineReference(this.startPos.line, r.content, r.references) diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index f6824191619..1ef7a241f58 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -10,7 +10,6 @@ import { DefaultCodeWhispererClient, CognitoCredentialsError, ListRecommendationsRequest, - GenerateRecommendationsRequest, } from '../client/codewhisperer' import * as EditorContext from '../util/editorContext' import * as CodeWhispererConstants from '../models/constants' @@ -93,8 +92,6 @@ export class RecommendationHandler { private prev: vscode.Disposable private _timer?: NodeJS.Timer documentUri: vscode.Uri | undefined = undefined - // private session: CodeWhispererSession - // private nextSession: CodeWhispererSession constructor() { this.requestId = '' @@ -104,8 +101,6 @@ export class RecommendationHandler { this.prev = new vscode.Disposable(() => {}) this.next = new vscode.Disposable(() => {}) this.reject = new vscode.Disposable(() => {}) - // this.session = CodeWhispererSessionState.instance.getSession() - // this.nextSession = CodeWhispererSessionState.instance.getNextSession() } static #instance: RecommendationHandler @@ -178,7 +173,7 @@ export class RecommendationHandler { let errorCode: string | undefined = undefined let currentSession = CodeWhispererSessionState.instance.getSession() if (isNextSession) { - getLogger().info('pre-fetching next recommendation for model routing') + getLogger().debug('pre-fetching next recommendation for model routing') currentSession = new CodeWhispererSession() CodeWhispererSessionState.instance.setNextSession(currentSession) } @@ -205,7 +200,6 @@ export class RecommendationHandler { currentSession.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName) if (pagination && !generate) { - // else { if (page === 0) { if (isNextSession) { const session = CodeWhispererSessionState.instance.getSession() @@ -234,7 +228,7 @@ export class RecommendationHandler { ...currentSession.requestContext.request, // Putting nextToken assignment in the end so it overwrites the existing nextToken nextToken: this.nextToken, - } as ListRecommendationsRequest, + }, supplementalMetadata: currentSession.requestContext.supplementalMetadata, } } @@ -244,10 +238,7 @@ export class RecommendationHandler { editor as vscode.TextEditor ) } - const request = generate - ? (currentSession.requestContext.request as GenerateRecommendationsRequest) - : (currentSession.requestContext.request as ListRecommendationsRequest) - getLogger().info(`request to inline recommendation : \n${JSON.stringify(request)}`) + const request = currentSession.requestContext.request TelemetryHelper.instance.setPreprocessEndTime() // set start pos for non pagination call or first pagination call @@ -295,7 +286,6 @@ export class RecommendationHandler { requestId = resp?.$response && resp?.$response?.requestId nextToken = resp?.nextToken ? resp?.nextToken : '' sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid'] - getLogger().info(`response from inline recommendation : \n${JSON.stringify(resp)}`) TelemetryHelper.instance.setFirstResponseRequestId(requestId) if (page === 0) { currentSession.setTimeToFirstRecommendation(performance.now()) @@ -422,7 +412,9 @@ export class RecommendationHandler { } currentSession.setCompletionType(recommendationIndex, r) } - currentSession.recommendations = pagination ? currentSession.recommendations.concat(recommendations) : recommendations + currentSession.recommendations = pagination + ? currentSession.recommendations.concat(recommendations) + : recommendations if (isInlineCompletionEnabled() && this.hasAtLeastOneValidSuggestion(typedPrefix, currentSession)) { this._onDidReceiveRecommendation.fire() } @@ -430,7 +422,9 @@ export class RecommendationHandler { this.requestId = requestId currentSession.sessionId = sessionId - this.nextToken = nextToken + if (!isNextSession) { + this.nextToken = nextToken + } // send Empty userDecision event if user receives no recommendations in this session at all. if (invocationResult === 'Succeeded' && nextToken === '') { @@ -490,22 +484,12 @@ export class RecommendationHandler { const session = CodeWhispererSessionState.instance.getSession() session.requestIdList = [] session.recommendations = [] - // session.nextRecommendations = [] session.suggestionStates = new Map() session.completionTypes = new Map() this.requestId = '' session.sessionId = '' this.nextToken = '' session.requestContext.supplementalMetadata = undefined - // nextSession.requestIdList = [] - // nextSession.recommendations = [] - // // nextSession.nextRecommendations = [] - // nextSession.suggestionStates = new Map() - // nextSession.completionTypes = new Map() - // this.requestId = '' - // nextSession.sessionId = '' - // this.nextToken = '' - // nextSession.requestContext.supplementalMetadata = undefined } async clearInlineCompletionStates() { @@ -653,7 +637,6 @@ export class RecommendationHandler { const session = CodeWhispererSessionState.instance.getSession() if (!indexShift && session.recommendations.length) { - // TODO: gate behind A/B group if needed await this.fetchNextRecommendations() } await lock.acquire(updateInlineLockKey, async () => { @@ -725,7 +708,6 @@ export class RecommendationHandler { const isManualTriggerEnabled: boolean = true const isSuggestionsWithCodeReferencesEnabled = codewhispererSettings.isSuggestionsWithCodeReferencesEnabled() - // TODO:remove isManualTriggerEnabled return { isShowMethodsEnabled, isManualTriggerEnabled, @@ -738,9 +720,11 @@ export class RecommendationHandler { const session = CodeWhispererSessionState.instance.getSession() const client = new codewhispererClient.DefaultCodeWhispererClient() const editor = vscode.window.activeTextEditor - if (!editor) return + if (!editor) { + return + } - this.getRecommendations( + await this.getRecommendations( client, editor, session.triggerType, diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index 26fb3b70a1f..29ee9130e36 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -45,14 +45,13 @@ export class CodeWhispererSessionState { } export class CodeWhispererSession { - // static #instance: CodeWhispererSession sessionId: string requestIdList: string[] startPos: Position startCursorOffset: number leftContextOfCurrentLine: string requestContext: { - request: ListRecommendationsRequest | GenerateRecommendationsRequest | undefined + request: ListRecommendationsRequest | GenerateRecommendationsRequest supplementalMetadata: CodeWhispererSupplementalContext | undefined } language: CodewhispererLanguage @@ -77,7 +76,7 @@ export class CodeWhispererSession { this.startPos = new Position(0, 0) this.startCursorOffset = 0 this.leftContextOfCurrentLine = '' - this.requestContext = { request: undefined, supplementalMetadata: undefined } + this.requestContext = { request: {} as any, supplementalMetadata: undefined } this.language = 'python' this.taskType = undefined this.triggerType = 'OnDemand' diff --git a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts b/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts index d3ad4719c8d..6511c628eb9 100644 --- a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts +++ b/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts @@ -47,6 +47,7 @@ describe('CodeWhisperer service invocation', async function () { isAutomatedTriggerEnabled: true, isSuggestionsWithCodeReferencesEnabled: false, } + const session = CodeWhispererSessionState.instance.getSession() before(async function () { validConnection = await setValidConnection() @@ -62,8 +63,7 @@ describe('CodeWhisperer service invocation', async function () { }) it('trigger known to return recs with references returns rec with reference', async function () { - //check that handler is empty before invocation - const session = CodeWhispererSessionState.instance.getSession() + // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -98,7 +98,6 @@ describe('CodeWhisperer service invocation', async function () { it('trigger known to return rec with references does not return rec with references when reference tracker setting is off', async function () { // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId - const session = CodeWhispererSessionState.instance.getSession() const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() diff --git a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts b/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts index 00fad503ccc..6770373489b 100644 --- a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts +++ b/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts @@ -44,7 +44,7 @@ describe('CodeWhisperer service invocation', async function () { }) it('manual trigger returns valid recommendation response', async function () { - //check that handler is empty before invocation + // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -66,7 +66,7 @@ describe('CodeWhisperer service invocation', async function () { }) it('auto trigger returns valid recommendation response', async function () { - //check that handler is empty before invocation + // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() @@ -103,7 +103,6 @@ describe('CodeWhisperer service invocation', async function () { // check that handler is empty before invocation const requestIdBefore = RecommendationHandler.instance.requestId - const session = CodeWhispererSessionState.instance.getSession() const sessionIdBefore = session.sessionId const validRecsBefore = RecommendationHandler.instance.isValidResponse() From 00574ae46e9c7d5057cec22fd0775407a553b44f Mon Sep 17 00:00:00 2001 From: Zoe Lin Date: Wed, 22 Jan 2025 22:01:09 -0800 Subject: [PATCH 05/17] update tests and pr feedback --- .../commands/onAcceptance.test.ts | 4 +- .../commands/onInlineAcceptance.test.ts | 4 +- .../service/completionProvider.test.ts | 4 +- .../service/inlineCompletionService.test.ts | 3 +- .../service/recommendationHandler.test.ts | 22 +++- .../util/telemetryHelper.test.ts | 3 +- .../commands/onInlineAcceptance.ts | 18 +-- .../service/inlineCompletionService.ts | 1 + .../service/recommendationHandler.ts | 6 +- .../src/codewhisperer/util/telemetryHelper.ts | 112 ++++++++++-------- .../core/src/test/codewhisperer/testUtil.ts | 2 - 11 files changed, 107 insertions(+), 72 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index c61ed4ba942..37fd0c4c1ed 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -18,14 +18,15 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -71,6 +72,7 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 4a86240687a..8530f21f86f 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -18,14 +18,15 @@ import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -50,6 +51,7 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts index e110743932d..a7f87a346ad 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts @@ -17,7 +17,6 @@ import { import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' describe('completionProviderService', function () { - const session = CodeWhispererSessionState.instance.getSession() beforeEach(async function () { await resetCodeWhispererGlobalVariables() }) @@ -40,6 +39,7 @@ describe('completionProviderService', function () { describe('getCompletionItem', function () { it('should return targetCompletionItem given input', function () { + const session = CodeWhispererSessionState.instance.getSession() session.startPos = new vscode.Position(0, 0) RecommendationHandler.instance.requestId = 'mock_requestId_getCompletionItem' session.sessionId = 'mock_sessionId_getCompletionItem' @@ -96,6 +96,7 @@ describe('completionProviderService', function () { describe('getCompletionItems', function () { it('should return completion items for each non-empty recommendation', async function () { + const session = CodeWhispererSessionState.instance.getSession() session.recommendations = [ { content: "\n\t\tconsole.log('Hello world!');\n\t}" }, { content: '\nvar a = 10' }, @@ -107,6 +108,7 @@ describe('completionProviderService', function () { }) it('should return empty completion items when recommendation is empty', async function () { + const session = CodeWhispererSessionState.instance.getSession() session.recommendations = [] const mockPosition = new vscode.Position(14, 83) const mockDocument = createMockDocument() diff --git a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts index a3bc8418f12..f1618c83dac 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts @@ -22,7 +22,6 @@ import { import { createMockTextEditor, resetCodeWhispererGlobalVariables, createMockDocument } from 'aws-core-vscode/test' describe('inlineCompletionService', function () { - const session = CodeWhispererSessionState.instance.getSession() beforeEach(async function () { await resetCodeWhispererGlobalVariables() }) @@ -47,6 +46,7 @@ describe('inlineCompletionService', function () { }) it('should call checkAndResetCancellationTokens before showing inline and next token to be null', async function () { + const session = CodeWhispererSessionState.instance.getSession() const mockEditor = createMockTextEditor() sinon.stub(RecommendationHandler.instance, 'getRecommendations').resolves({ result: 'Succeeded', @@ -71,6 +71,7 @@ describe('inlineCompletionService', function () { describe('clearInlineCompletionStates', function () { it('should remove inline reference and recommendations', async function () { + const session = CodeWhispererSessionState.instance.getSession() const fakeReferences = [ { message: '', diff --git a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts index e519a42d7bc..9668fa4c5a4 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts @@ -26,7 +26,6 @@ import { // import * as supplementalContextUtil from 'aws-core-vscode/codewhisperer' describe('recommendationHandler', function () { - const session = CodeWhispererSessionState.instance.getSession() const config: ConfigurationEntry = { isShowMethodsEnabled: true, isManualTriggerEnabled: true, @@ -56,6 +55,7 @@ describe('recommendationHandler', function () { }) it('should assign correct recommendations given input', async function () { + const session = CodeWhispererSessionState.instance.getSession() assert.strictEqual(CodeWhispererCodeCoverageTracker.instances.size, 0) assert.strictEqual( CodeWhispererCodeCoverageTracker.getTracker(mockEditor.document.languageId)?.serviceInvocationCount, @@ -75,7 +75,7 @@ describe('recommendationHandler', function () { } const handler = new RecommendationHandler() sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false) + await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter', false) const actual = session.recommendations const expected: RecommendationsList = [{ content: "print('Hello World!')" }, { content: '' }] assert.deepStrictEqual(actual, expected) @@ -86,6 +86,7 @@ describe('recommendationHandler', function () { }) it('should assign request id correctly', async function () { + const session = CodeWhispererSessionState.instance.getSession() const mockServerResult = { recommendations: [{ content: "print('Hello World!')" }, { content: '' }], $response: { @@ -100,7 +101,7 @@ describe('recommendationHandler', function () { const handler = new RecommendationHandler() sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) sinon.stub(handler, 'isCancellationRequested').returns(false) - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false) + await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter', false) assert.strictEqual(handler.requestId, 'test_request') assert.strictEqual(session.sessionId, 'test_request') assert.strictEqual(session.triggerType, 'AutoTrigger') @@ -129,9 +130,10 @@ describe('recommendationHandler', function () { strategy: 'empty', }) sinon.stub(performance, 'now').returns(0.0) + const session = CodeWhispererSessionState.instance.getSession() session.startPos = new vscode.Position(1, 0) session.startCursorOffset = 2 - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter') + await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter') const assertTelemetry = assertTelemetryCurried('codewhisperer_serviceInvocation') assertTelemetry({ codewhispererRequestId: 'test_request', @@ -168,10 +170,11 @@ describe('recommendationHandler', function () { const handler = new RecommendationHandler() sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) sinon.stub(performance, 'now').returns(0.0) + const session = CodeWhispererSessionState.instance.getSession() session.startPos = new vscode.Position(1, 0) session.requestIdList = ['test_request_empty'] session.startCursorOffset = 2 - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter') + await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, session, 'Enter') const assertTelemetry = assertTelemetryCurried('codewhisperer_userDecision') assertTelemetry({ codewhispererRequestId: 'test_request_empty', @@ -193,6 +196,7 @@ describe('recommendationHandler', function () { sinon.restore() }) it('should return true if any response is not empty', function () { + const session = CodeWhispererSessionState.instance.getSession() const handler = new RecommendationHandler() session.recommendations = [ { @@ -205,12 +209,14 @@ describe('recommendationHandler', function () { }) it('should return false if response is empty', function () { + const session = CodeWhispererSessionState.instance.getSession() const handler = new RecommendationHandler() session.recommendations = [] assert.ok(!handler.isValidResponse()) }) it('should return false if all response has no string length', function () { + const session = CodeWhispererSessionState.instance.getSession() const handler = new RecommendationHandler() session.recommendations = [{ content: '' }, { content: '' }] assert.ok(!handler.isValidResponse()) @@ -223,6 +229,7 @@ describe('recommendationHandler', function () { }) it('should set the completion type to block given a multi-line suggestion', function () { + const session = CodeWhispererSessionState.instance.getSession() session.setCompletionType(0, { content: 'test\n\n \t\r\nanother test' }) assert.strictEqual(session.getCompletionType(0), 'Block') @@ -234,6 +241,7 @@ describe('recommendationHandler', function () { }) it('should set the completion type to line given a single-line suggestion', function () { + const session = CodeWhispererSessionState.instance.getSession() session.setCompletionType(0, { content: 'test' }) assert.strictEqual(session.getCompletionType(0), 'Line') @@ -242,6 +250,7 @@ describe('recommendationHandler', function () { }) it('should set the completion type to line given a multi-line completion but only one-lien of non-blank sequence', function () { + const session = CodeWhispererSessionState.instance.getSession() session.setCompletionType(0, { content: 'test\n\t' }) assert.strictEqual(session.getCompletionType(0), 'Line') @@ -258,6 +267,7 @@ describe('recommendationHandler', function () { describe('on event change', async function () { beforeEach(function () { + const session = CodeWhispererSessionState.instance.getSession() const fakeReferences = [ { message: '', @@ -275,12 +285,14 @@ describe('recommendationHandler', function () { }) it('should remove inline reference onEditorChange', async function () { + const session = CodeWhispererSessionState.instance.getSession() session.sessionId = 'aSessionId' RecommendationHandler.instance.requestId = 'aRequestId' await RecommendationHandler.instance.onEditorChange() assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0) }) it('should remove inline reference onFocusChange', async function () { + const session = CodeWhispererSessionState.instance.getSession() session.sessionId = 'aSessionId' RecommendationHandler.instance.requestId = 'aRequestId' await RecommendationHandler.instance.onFocusChange() diff --git a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts index b6ff97053d4..d2703677382 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts @@ -39,7 +39,6 @@ function aCompletion(): Completion { } describe('telemetryHelper', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('clientComponentLatency', function () { let sut: TelemetryHelper @@ -52,6 +51,7 @@ describe('telemetryHelper', function () { }) it('resetClientComponentLatencyTime should reset state variables', function () { + const session = CodeWhispererSessionState.instance.getSession() session.invokeSuggestionStartTime = 100 session.preprocessEndTime = 200 session.sdkApiCallStartTime = 300 @@ -290,6 +290,7 @@ describe('telemetryHelper', function () { }) it('Should call telemetry record for each recommendations with proper arguments', async function () { + const session = CodeWhispererSessionState.instance.getSession() const telemetryHelper = new TelemetryHelper() const response = [{ content: "print('Hello')" }] const requestIdList = ['test_x', 'test_x', 'test_y'] diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts index 8f88112d5bb..a9a888ef8c4 100644 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts @@ -143,13 +143,17 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept } RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) - if (acceptanceEntry.acceptIndex === 0) { - const nextSession = CodeWhispererSessionState.instance.getNextSession() - nextSession.startPos = acceptanceEntry.editor.selection.active - CodeWhispererSessionState.instance.setSession(nextSession) - if (nextSession.recommendations.length) { - await RecommendationHandler.instance.tryShowRecommendation() - } + await promoteNextSessionIfAvailable(acceptanceEntry) + } +} + +async function promoteNextSessionIfAvailable(acceptanceEntry: OnRecommendationAcceptanceEntry) { + if (acceptanceEntry.acceptIndex === 0 && acceptanceEntry.editor) { + const nextSession = CodeWhispererSessionState.instance.getNextSession() + nextSession.startPos = acceptanceEntry.editor.selection.active + CodeWhispererSessionState.instance.setSession(nextSession) + if (nextSession.recommendations.length) { + await RecommendationHandler.instance.tryShowRecommendation() } } } diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index cc93bfa21b0..9cd0dda781d 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts @@ -128,6 +128,7 @@ export class InlineCompletionService { editor, triggerType, config, + session, autoTriggerType, true, page diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index 1ef7a241f58..92595df2fdb 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -162,6 +162,7 @@ export class RecommendationHandler { editor: vscode.TextEditor, triggerType: CodewhispererTriggerType, config: ConfigurationEntry, + session: CodeWhispererSession, autoTriggerType?: CodewhispererAutomatedTriggerType, pagination: boolean = true, page: number = 0, @@ -171,7 +172,7 @@ export class RecommendationHandler { let invocationResult: 'Succeeded' | 'Failed' = 'Failed' let errorMessage: string | undefined = undefined let errorCode: string | undefined = undefined - let currentSession = CodeWhispererSessionState.instance.getSession() + let currentSession = session if (isNextSession) { getLogger().debug('pre-fetching next recommendation for model routing') currentSession = new CodeWhispererSession() @@ -202,7 +203,6 @@ export class RecommendationHandler { if (pagination && !generate) { if (page === 0) { if (isNextSession) { - const session = CodeWhispererSessionState.instance.getSession() const request = session.requestContext.request as ListRecommendationsRequest currentSession.requestContext = { request: { @@ -364,6 +364,7 @@ export class RecommendationHandler { editor, triggerType, config, + currentSession, autoTriggerType, pagination, page, @@ -729,6 +730,7 @@ export class RecommendationHandler { editor, session.triggerType, await this.getConfigEntry(), + session, session.autoTriggerType, true, 0, diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 60bc765256a..964145fd881 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -58,7 +58,6 @@ export class TelemetryHelper { // use this to distinguish DocumentChangeEvent from CWSPR or from other sources public lastSuggestionInDisplay = '' - private session = CodeWhispererSessionState.instance.getSession() constructor() {} @@ -125,22 +124,23 @@ export class TelemetryHelper { reason: string, supplementalContextMetadata?: CodeWhispererSupplementalContext | undefined ) { + const session = CodeWhispererSessionState.instance.getSession() const event = { - codewhispererAutomatedTriggerType: this.session.autoTriggerType, - codewhispererCursorOffset: this.session.startCursorOffset, + codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererCursorOffset: session.startCursorOffset, codewhispererCustomizationArn: getSelectedCustomization().arn, CodewhispererGettingStartedTask: taskType, codewhispererImportRecommendationEnabled: CodeWhispererSettings.instance.isImportRecommendationEnabled(), codewhispererLastSuggestionIndex: lastSuggestionIndex, codewhispererLanguage: language, - codewhispererLineNumber: this.session.startPos.line, + codewhispererLineNumber: session.startPos.line, codewhispererRequestId: requestId ? requestId : undefined, codewhispererSessionId: sessionId ? sessionId : undefined, codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLatency: supplementalContextMetadata?.latency, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: this.session.triggerType, + codewhispererTriggerType: session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, duration: duration || 0, reason: reason ? reason.substring(0, 200) : undefined, @@ -158,10 +158,11 @@ export class TelemetryHelper { supplementalContextMetadata?: CodeWhispererSupplementalContext | undefined ) { const selectedCustomization = getSelectedCustomization() + const session = CodeWhispererSessionState.instance.getSession() telemetry.codewhisperer_userDecision.emit({ codewhispererCompletionType: 'Line', - codewhispererGettingStartedTask: this.session.taskType, + codewhispererGettingStartedTask: session.taskType, codewhispererLanguage: language, codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[0], @@ -173,23 +174,23 @@ export class TelemetryHelper { codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: this.session.triggerType, + codewhispererTriggerType: session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, }) telemetry.codewhisperer_userTriggerDecision.emit({ - codewhispererAutomatedTriggerType: this.session.autoTriggerType, + codewhispererAutomatedTriggerType: session.autoTriggerType, codewhispererClassifierResult: this.classifierResult, codewhispererClassifierThreshold: this.classifierThreshold, codewhispererCompletionType: 'Line', - codewhispererCursorOffset: this.session.startCursorOffset, + codewhispererCursorOffset: session.startCursorOffset, codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), codewhispererFirstRequestId: requestIdList[0], - codewhispererGettingStartedTask: this.session.taskType, + codewhispererGettingStartedTask: session.taskType, codewhispererLanguage: language, - codewhispererLineNumber: this.session.startPos.line, + codewhispererLineNumber: session.startPos.line, codewhispererPreviousSuggestionState: this.prevTriggerDecision, codewhispererSessionId: sessionId, codewhispererSuggestionCount: 0, @@ -206,8 +207,8 @@ export class TelemetryHelper { codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime ? performance.now() - this.lastTriggerDecisionTime : undefined, - codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, - codewhispererTriggerType: this.session.triggerType, + codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, + codewhispererTriggerType: session.triggerType, codewhispererTypeaheadLength: this.typeAheadLength, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, @@ -226,8 +227,8 @@ export class TelemetryHelper { completionType: 'LINE', suggestionState: 'EMPTY', recommendationLatencyMilliseconds: 0, - triggerToResponseLatencyMilliseconds: this.session.timeToFirstRecommendation, - perceivedLatencyMilliseconds: this.session.perceivedLatency, + triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: session.perceivedLatency, timestamp: new Date(Date.now()), suggestionReferenceCount: 0, generatedLine: 0, @@ -278,11 +279,12 @@ export class TelemetryHelper { if (_elem.content.length === 0) { recommendationSuggestionState?.set(i, 'Empty') } + const session = CodeWhispererSessionState.instance.getSession() const event: CodewhispererUserDecision = { // TODO: maintain a list of RecommendationContexts with both recommendation and requestId in it, instead of two separate list items. codewhispererCompletionType: this.getCompletionType(i, completionTypes), - codewhispererGettingStartedTask: this.session.taskType, - codewhispererLanguage: this.session.language, + codewhispererGettingStartedTask: session.taskType, + codewhispererLanguage: session.language, codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[i], codewhispererSessionId: sessionId ? sessionId : undefined, @@ -294,7 +296,7 @@ export class TelemetryHelper { codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererTriggerType: this.session.triggerType, + codewhispererTriggerType: session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, } @@ -339,14 +341,15 @@ export class TelemetryHelper { if (!events.length) { return } + const session = CodeWhispererSessionState.instance.getSession() const aggregated: CodewhispererUserTriggerDecision = { - codewhispererAutomatedTriggerType: this.session.autoTriggerType, + codewhispererAutomatedTriggerType: session.autoTriggerType, codewhispererCompletionType: events[0].codewhispererCompletionType, - codewhispererCursorOffset: this.session.startCursorOffset, + codewhispererCursorOffset: session.startCursorOffset, codewhispererFirstRequestId: requestId, - codewhispererGettingStartedTask: this.session.taskType, + codewhispererGettingStartedTask: session.taskType, codewhispererLanguage: events[0].codewhispererLanguage, - codewhispererLineNumber: this.session.startPos.line, + codewhispererLineNumber: session.startPos.line, codewhispererSessionId: sessionId, codewhispererSuggestionCount: events.length, codewhispererSuggestionImportCount: events @@ -388,6 +391,7 @@ export class TelemetryHelper { .map((e) => e.codewhispererSuggestionCount) .reduce((a, b) => a + b, 0) + const session = CodeWhispererSessionState.instance.getSession() const aggregated: CodewhispererUserTriggerDecision = { codewhispererAutomatedTriggerType: autoTriggerType, codewhispererCharactersAccepted: acceptedRecommendationContent.length, @@ -398,7 +402,7 @@ export class TelemetryHelper { codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, - codewhispererGettingStartedTask: this.session.taskType, + codewhispererGettingStartedTask: session.taskType, codewhispererLanguage: language, codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, codewhispererPreviousSuggestionState: this.prevTriggerDecision, @@ -419,7 +423,7 @@ export class TelemetryHelper { codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime ? performance.now() - this.lastTriggerDecisionTime : undefined, - codewhispererTimeToFirstRecommendation: this.session.timeToFirstRecommendation, + codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, codewhispererTypeaheadLength: this.typeAheadLength, @@ -432,7 +436,7 @@ export class TelemetryHelper { // When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value // and client side will set this value to 0.0. - let e2eLatency = this.session.firstSuggestionShowTime - this.session.invokeSuggestionStartTime + let e2eLatency = session.firstSuggestionShowTime - session.invokeSuggestionStartTime if (aggregatedSuggestionState !== 'Reject' && aggregatedSuggestionState !== 'Accept') { e2eLatency = 0.0 } @@ -452,8 +456,8 @@ export class TelemetryHelper { completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType), suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState), recommendationLatencyMilliseconds: e2eLatency, - triggerToResponseLatencyMilliseconds: this.session.timeToFirstRecommendation, - perceivedLatencyMilliseconds: this.session.perceivedLatency, + triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: session.perceivedLatency, timestamp: new Date(Date.now()), suggestionReferenceCount: referenceCount, generatedLine: generatedLines, @@ -509,12 +513,13 @@ export class TelemetryHelper { } private resetUserTriggerDecisionTelemetry() { + const session = CodeWhispererSessionState.instance.getSession() this.sessionDecisions = [] this.triggerChar = '' this.typeAheadLength = 0 this.timeSinceLastModification = 0 - this.session.timeToFirstRecommendation = 0 - this.session.perceivedLatency = 0 + session.timeToFirstRecommendation = 0 + session.perceivedLatency = 0 this.classifierResult = undefined this.classifierThreshold = undefined } @@ -593,31 +598,35 @@ export class TelemetryHelper { } public resetClientComponentLatencyTime() { - this.session.invokeSuggestionStartTime = 0 - this.session.preprocessEndTime = 0 - this.session.sdkApiCallStartTime = 0 + const session = CodeWhispererSessionState.instance.getSession() + session.invokeSuggestionStartTime = 0 + session.preprocessEndTime = 0 + session.sdkApiCallStartTime = 0 this._sdkApiCallEndTime = 0 - this.session.fetchCredentialStartTime = 0 - this.session.firstSuggestionShowTime = 0 + session.fetchCredentialStartTime = 0 + session.firstSuggestionShowTime = 0 this._allPaginationEndTime = 0 this._firstResponseRequestId = '' } public setPreprocessEndTime() { - if (this.session.preprocessEndTime !== 0) { + const session = CodeWhispererSessionState.instance.getSession() + if (session.preprocessEndTime !== 0) { getLogger().warn(`inline completion preprocessEndTime has been set and not reset correctly`) } - this.session.preprocessEndTime = performance.now() + session.preprocessEndTime = performance.now() } /** This method is assumed to be invoked first at the start of execution **/ public setInvokeSuggestionStartTime() { + const session = CodeWhispererSessionState.instance.getSession() this.resetClientComponentLatencyTime() - this.session.invokeSuggestionStartTime = performance.now() + session.invokeSuggestionStartTime = performance.now() } public setSdkApiCallEndTime() { - if (this._sdkApiCallEndTime === 0 && this.session.sdkApiCallStartTime !== 0) { + const session = CodeWhispererSessionState.instance.getSession() + if (this._sdkApiCallEndTime === 0 && session.sdkApiCallStartTime !== 0) { this._sdkApiCallEndTime = performance.now() } } @@ -629,8 +638,9 @@ export class TelemetryHelper { } public setFirstSuggestionShowTime() { - if (this.session.firstSuggestionShowTime === 0 && this._sdkApiCallEndTime !== 0) { - this.session.firstSuggestionShowTime = performance.now() + const session = CodeWhispererSessionState.instance.getSession() + if (session.firstSuggestionShowTime === 0 && this._sdkApiCallEndTime !== 0) { + session.firstSuggestionShowTime = performance.now() } } @@ -643,23 +653,23 @@ export class TelemetryHelper { // report client component latency after all pagination call finish // and at least one suggestion is shown to the user public tryRecordClientComponentLatency() { - if (this.session.firstSuggestionShowTime === 0 || this._allPaginationEndTime === 0) { + const session = CodeWhispererSessionState.instance.getSession() + if (session.firstSuggestionShowTime === 0 || this._allPaginationEndTime === 0) { return } telemetry.codewhisperer_clientComponentLatency.emit({ - codewhispererAllCompletionsLatency: this._allPaginationEndTime - this.session.sdkApiCallStartTime, + codewhispererAllCompletionsLatency: this._allPaginationEndTime - session.sdkApiCallStartTime, codewhispererCompletionType: 'Line', - codewhispererCredentialFetchingLatency: - this.session.sdkApiCallStartTime - this.session.fetchCredentialStartTime, + codewhispererCredentialFetchingLatency: session.sdkApiCallStartTime - session.fetchCredentialStartTime, codewhispererCustomizationArn: getSelectedCustomization().arn, - codewhispererEndToEndLatency: this.session.firstSuggestionShowTime - this.session.invokeSuggestionStartTime, - codewhispererFirstCompletionLatency: this._sdkApiCallEndTime - this.session.sdkApiCallStartTime, - codewhispererLanguage: this.session.language, - codewhispererPostprocessingLatency: this.session.firstSuggestionShowTime - this._sdkApiCallEndTime, - codewhispererPreprocessingLatency: this.session.preprocessEndTime - this.session.invokeSuggestionStartTime, + codewhispererEndToEndLatency: session.firstSuggestionShowTime - session.invokeSuggestionStartTime, + codewhispererFirstCompletionLatency: this._sdkApiCallEndTime - session.sdkApiCallStartTime, + codewhispererLanguage: session.language, + codewhispererPostprocessingLatency: session.firstSuggestionShowTime - this._sdkApiCallEndTime, + codewhispererPreprocessingLatency: session.preprocessEndTime - session.invokeSuggestionStartTime, codewhispererRequestId: this._firstResponseRequestId, - codewhispererSessionId: this.session.sessionId, - codewhispererTriggerType: this.session.triggerType, + codewhispererSessionId: session.sessionId, + codewhispererTriggerType: session.triggerType, credentialStartUrl: AuthUtil.instance.startUrl, }) } diff --git a/packages/core/src/test/codewhisperer/testUtil.ts b/packages/core/src/test/codewhisperer/testUtil.ts index b27bc116c06..70a7cb05358 100644 --- a/packages/core/src/test/codewhisperer/testUtil.ts +++ b/packages/core/src/test/codewhisperer/testUtil.ts @@ -35,8 +35,6 @@ export async function resetCodeWhispererGlobalVariables() { globals.telemetry.logger.clear() const session = CodeWhispererSessionState.instance.getSession() session.reset() - const nextSession = CodeWhispererSessionState.instance.nextSession - nextSession.reset() await globals.globalState.clear() await CodeSuggestionsState.instance.setSuggestionsEnabled(true) await RecommendationHandler.instance.clearInlineCompletionStates() From 02d248072c6b475f7ec6479b7d0605035b3dacc8 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Mon, 3 Feb 2025 09:49:54 -0800 Subject: [PATCH 06/17] changelog --- .../Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json diff --git a/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json b/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json new file mode 100644 index 00000000000..84d85655d5b --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "prefetch next recommendations when possible to reduce suggestion latency" +} From 8bac14c75363b2ae2f8093769df0c4c275de03ad Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 09:19:08 -0800 Subject: [PATCH 07/17] changelog --- .../Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json b/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json index 84d85655d5b..595e48f0026 100644 --- a/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json +++ b/packages/amazonq/.changes/next-release/Feature-b63d765a-24f9-44e6-b826-b905f35e67d1.json @@ -1,4 +1,4 @@ { "type": "Feature", - "description": "prefetch next recommendations when possible to reduce suggestion latency" + "description": "Inline suggestions: Pre-fetch recommendations to reduce suggestion latency." } From 778012dfa42d7946243cfd5564f48dd4cbe1d83b Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 10:47:08 -0800 Subject: [PATCH 08/17] onAcceptance.test.ts --- .../test/unit/codewhisperer/commands/onAcceptance.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index 37fd0c4c1ed..c61ed4ba942 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -18,15 +18,14 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -72,7 +71,6 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() From 331deeabf0bfe4676af0455921d439a5ff44f850 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 10:47:58 -0800 Subject: [PATCH 09/17] onInlineAcceptance.test --- .../unit/codewhisperer/commands/onInlineAcceptance.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 8530f21f86f..4a86240687a 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -18,15 +18,14 @@ import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -51,7 +50,6 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) From cf7adae73b4f700b0fc8c6d767e373c7d65d8d81 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 10:50:56 -0800 Subject: [PATCH 10/17] telemetryHelper.test --- .../test/unit/codewhisperer/util/telemetryHelper.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts index d2703677382..99f2585a285 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts @@ -39,6 +39,7 @@ function aCompletion(): Completion { } describe('telemetryHelper', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('clientComponentLatency', function () { let sut: TelemetryHelper @@ -48,10 +49,10 @@ describe('telemetryHelper', function () { afterEach(function () { sinon.restore() + session.reset() }) it('resetClientComponentLatencyTime should reset state variables', function () { - const session = CodeWhispererSessionState.instance.getSession() session.invokeSuggestionStartTime = 100 session.preprocessEndTime = 200 session.sdkApiCallStartTime = 300 @@ -290,7 +291,6 @@ describe('telemetryHelper', function () { }) it('Should call telemetry record for each recommendations with proper arguments', async function () { - const session = CodeWhispererSessionState.instance.getSession() const telemetryHelper = new TelemetryHelper() const response = [{ content: "print('Hello')" }] const requestIdList = ['test_x', 'test_x', 'test_y'] From efce167fa738d123d30f8189732a1483c8b19482 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 10:54:37 -0800 Subject: [PATCH 11/17] codewhispererSession --- .../util/codeWhispererSession.ts | 69 +++++++------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index 29ee9130e36..a36c94c38a0 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -45,57 +45,34 @@ export class CodeWhispererSessionState { } export class CodeWhispererSession { - sessionId: string - requestIdList: string[] - startPos: Position - startCursorOffset: number - leftContextOfCurrentLine: string + sessionId: string = '' + requestIdList: string[] = [] + startPos: Position = new Position(0, 0) + startCursorOffset: number = 0 + leftContextOfCurrentLine: string = '' requestContext: { request: ListRecommendationsRequest | GenerateRecommendationsRequest supplementalMetadata: CodeWhispererSupplementalContext | undefined - } - language: CodewhispererLanguage - taskType: CodewhispererGettingStartedTask | undefined - triggerType: CodewhispererTriggerType - autoTriggerType: CodewhispererAutomatedTriggerType | undefined - recommendations: Recommendation[] - suggestionStates: Map - completionTypes: Map - fetchCredentialStartTime: number - sdkApiCallStartTime: number - invokeSuggestionStartTime: number - preprocessEndTime: number - timeToFirstRecommendation: number - firstSuggestionShowTime: number - perceivedLatency: number + } = { request: {} as any, supplementalMetadata: undefined } + language: CodewhispererLanguage = 'python' + taskType: CodewhispererGettingStartedTask | undefined = undefined + triggerType: CodewhispererTriggerType = 'OnDemand' + autoTriggerType: CodewhispererAutomatedTriggerType | undefined = undefined + // Various states of recommendations + recommendations: Recommendation[] = [] + suggestionStates: Map = new Map() + completionTypes: Map = new Map() + // Some other variables for client component latency + fetchCredentialStartTime: number = 0 + sdkApiCallStartTime: number = 0 + invokeSuggestionStartTime: number = 0 + preprocessEndTime: number = 0 + timeToFirstRecommendation: number = 0 + firstSuggestionShowTime: number = 0 + perceivedLatency: number = 0 // Per-session states - constructor() { - this.sessionId = '' - this.requestIdList = [] - this.startPos = new Position(0, 0) - this.startCursorOffset = 0 - this.leftContextOfCurrentLine = '' - this.requestContext = { request: {} as any, supplementalMetadata: undefined } - this.language = 'python' - this.taskType = undefined - this.triggerType = 'OnDemand' - this.autoTriggerType = undefined - - // Various states of recommendations - this.recommendations = [] - this.suggestionStates = new Map() - this.completionTypes = new Map() - - // Some other variables for client component latency - this.fetchCredentialStartTime = 0 - this.sdkApiCallStartTime = 0 - this.invokeSuggestionStartTime = 0 - this.preprocessEndTime = 0 - this.timeToFirstRecommendation = 0 - this.firstSuggestionShowTime = 0 - this.perceivedLatency = 0 - } + constructor() {} setFetchCredentialStart() { if (this.fetchCredentialStartTime === 0 && this.invokeSuggestionStartTime !== 0) { From 54e115caab5596cc5af58f1654e662a7e8bd6390 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 11:00:22 -0800 Subject: [PATCH 12/17] patch --- packages/core/src/codewhisperer/util/codeWhispererSession.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index a36c94c38a0..12222903568 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -71,9 +71,6 @@ export class CodeWhispererSession { firstSuggestionShowTime: number = 0 perceivedLatency: number = 0 - // Per-session states - constructor() {} - setFetchCredentialStart() { if (this.fetchCredentialStartTime === 0 && this.invokeSuggestionStartTime !== 0) { this.fetchCredentialStartTime = performance.now() From 25b6fb2d43d13a4fc8d1aa7eb3c81eeb583a8ae8 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 11:38:32 -0800 Subject: [PATCH 13/17] Revert "onInlineAcceptance.test" This reverts commit 331deeabf0bfe4676af0455921d439a5ff44f850. --- .../unit/codewhisperer/commands/onInlineAcceptance.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 4a86240687a..8530f21f86f 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -18,14 +18,15 @@ import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -50,6 +51,7 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) From 5796933e2d3b66baf27fa3bab827427faa5e01fc Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 11:38:53 -0800 Subject: [PATCH 14/17] Revert "onAcceptance.test.ts" This reverts commit 778012dfa42d7946243cfd5564f48dd4cbe1d83b. --- .../test/unit/codewhisperer/commands/onAcceptance.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index c61ed4ba942..37fd0c4c1ed 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -18,14 +18,15 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -71,6 +72,7 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() From 58b50c22f1938f79c9d739de4c2928e3e1f77964 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 11:52:09 -0800 Subject: [PATCH 15/17] retry --- .../test/unit/codewhisperer/commands/onAcceptance.test.ts | 4 +--- .../unit/codewhisperer/commands/onInlineAcceptance.test.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index 37fd0c4c1ed..c61ed4ba942 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -18,15 +18,14 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -72,7 +71,6 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 8530f21f86f..4a86240687a 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -18,15 +18,14 @@ import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { + const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -51,7 +50,6 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) From ad3eac0348646672f94cff524b4c4e263753f900 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 12:17:40 -0800 Subject: [PATCH 16/17] Revert "retry" This reverts commit 58b50c22f1938f79c9d739de4c2928e3e1f77964. --- .../test/unit/codewhisperer/commands/onAcceptance.test.ts | 4 +++- .../unit/codewhisperer/commands/onInlineAcceptance.test.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index c61ed4ba942..37fd0c4c1ed 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -18,14 +18,15 @@ import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-cor import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -71,6 +72,7 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 4a86240687a..8530f21f86f 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -18,14 +18,15 @@ import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { - const session = CodeWhispererSessionState.instance.getSession() describe('onInlineAcceptance', function () { beforeEach(async function () { + const session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() session.reset() }) afterEach(function () { + const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -50,6 +51,7 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { + const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) From b5d38dde26459a842bc5edf72b5c26a8d9f5f798 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Tue, 4 Feb 2025 12:24:41 -0800 Subject: [PATCH 17/17] fix test failure --- .../test/unit/codewhisperer/commands/onAcceptance.test.ts | 7 +++---- .../unit/codewhisperer/commands/onInlineAcceptance.test.ts | 7 +++---- packages/core/src/codewhisperer/index.ts | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index 37fd0c4c1ed..a94bbd5a3fe 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -13,20 +13,20 @@ import { CodeWhispererTracker, RecommendationHandler, AuthUtil, + CodeWhispererSession, } from 'aws-core-vscode/codewhisperer' import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('onAcceptance', function () { + let session: CodeWhispererSession describe('onAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() + session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() - session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -72,7 +72,6 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 8530f21f86f..6cff08c2ded 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -13,20 +13,20 @@ import { RecommendationHandler, AuthUtil, CodeWhispererSessionState, + CodeWhispererSession, } from 'aws-core-vscode/codewhisperer' import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' describe('onInlineAcceptance', function () { + let session: CodeWhispererSession describe('onInlineAcceptance', function () { beforeEach(async function () { - const session = CodeWhispererSessionState.instance.getSession() + session = CodeWhispererSessionState.instance.getSession() await resetCodeWhispererGlobalVariables() - session.reset() }) afterEach(function () { - const session = CodeWhispererSessionState.instance.getSession() sinon.restore() session.reset() }) @@ -51,7 +51,6 @@ describe('onInlineAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - const session = CodeWhispererSessionState.instance.getSession() await globals.globalState.update('CODEWHISPERER_USER_GROUP', { version: extensionVersion, }) diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index f0dfd97c9ca..69b7cbcf96f 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -59,7 +59,7 @@ export { onAcceptance } from './commands/onAcceptance' export { CodeWhispererTracker } from './tracker/codewhispererTracker' export { RecommendationHandler } from './service/recommendationHandler' export { CodeWhispererUserGroupSettings } from './util/userGroupUtil' -export { CodeWhispererSessionState } from './util/codeWhispererSession' +export { CodeWhispererSessionState, CodeWhispererSession } from './util/codeWhispererSession' export { onInlineAcceptance } from './commands/onInlineAcceptance' export { stopTransformByQ } from './commands/startTransformByQ' export { getCompletionItems, getCompletionItem, getLabel } from './service/completionProvider'