async getRecommendations()

in packages/core/src/codewhisperer/service/recommendationHandler.ts [167:434]


    async getRecommendations(
        client: DefaultCodeWhispererClient,
        editor: vscode.TextEditor,
        triggerType: CodewhispererTriggerType,
        config: ConfigurationEntry,
        autoTriggerType?: CodewhispererAutomatedTriggerType,
        pagination: boolean = true,
        page: number = 0,
        generate: boolean = isIamConnection(AuthUtil.instance.conn)
    ): Promise<GetRecommendationsResponse> {
        let invocationResult: 'Succeeded' | 'Failed' = 'Failed'
        let errorMessage: string | undefined = undefined
        let errorCode: string | undefined = undefined

        if (!editor) {
            return Promise.resolve<GetRecommendationsResponse>({
                result: invocationResult,
                errorMessage: errorMessage,
                recommendationCount: 0,
            })
        }
        let recommendations: RecommendationsList = []
        let requestId = ''
        let sessionId = ''
        let reason = ''
        let startTime = 0
        let latency = 0
        let nextToken = ''
        let shouldRecordServiceInvocation = true
        session.language = runtimeLanguageContext.getLanguageContext(
            editor.document.languageId,
            path.extname(editor.document.fileName)
        ).language
        session.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName)

        if (pagination && !generate) {
            if (page === 0) {
                session.requestContext = await EditorContext.buildListRecommendationRequest(
                    editor as vscode.TextEditor,
                    this.nextToken,
                    config.isSuggestionsWithCodeReferencesEnabled
                )
            } else {
                session.requestContext = {
                    request: {
                        ...session.requestContext.request,
                        // Putting nextToken assignment in the end so it overwrites the existing nextToken
                        nextToken: this.nextToken,
                    },
                    supplementalMetadata: session.requestContext.supplementalMetadata,
                }
            }
        } else {
            session.requestContext = await EditorContext.buildGenerateRecommendationRequest(editor as vscode.TextEditor)
        }
        const request = session.requestContext.request
        // record preprocessing end time
        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

            /**
             * Validate request
             */
            if (!EditorContext.validateRequest(request)) {
                getLogger().verbose('Invalid Request: %O', request)
                const languageName = request.fileContext.programmingLanguage.languageName
                if (!runtimeLanguageContext.isLanguageSupported(languageName)) {
                    errorMessage = `${languageName} is currently not supported by Amazon Q inline suggestions`
                }
                return Promise.resolve<GetRecommendationsResponse>({
                    result: invocationResult,
                    errorMessage: errorMessage,
                    recommendationCount: 0,
                })
            }
        }

        try {
            startTime = performance.now()
            this.lastInvocationTime = startTime
            const mappedReq = runtimeLanguageContext.mapToRuntimeLanguage(request)
            const codewhispererPromise =
                pagination && !generate
                    ? client.listRecommendations(mappedReq)
                    : client.generateRecommendations(mappedReq)
            const resp = await this.getServerResponse(triggerType, config.isManualTriggerEnabled, codewhispererPromise)
            TelemetryHelper.instance.setSdkApiCallEndTime()
            latency = startTime !== 0 ? performance.now() - startTime : 0
            if ('recommendations' in resp) {
                recommendations = (resp && resp.recommendations) || []
            } else {
                recommendations = (resp && resp.completions) || []
            }
            invocationResult = 'Succeeded'
            requestId = resp?.$response && resp?.$response?.requestId
            nextToken = resp?.nextToken ? resp?.nextToken : ''
            sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid']
            TelemetryHelper.instance.setFirstResponseRequestId(requestId)
            if (page === 0) {
                session.setTimeToFirstRecommendation(performance.now())
            }
            if (nextToken === '') {
                TelemetryHelper.instance.setAllPaginationEndTime()
            }
        } catch (error) {
            if (error instanceof CognitoCredentialsError) {
                shouldRecordServiceInvocation = false
            }
            if (latency === 0) {
                latency = startTime !== 0 ? performance.now() - startTime : 0
            }
            getLogger().error('amazonq inline-suggest: Invocation Exception : %s', (error as Error).message)
            if (isAwsError(error)) {
                errorMessage = error.message
                requestId = error.requestId || ''
                errorCode = error.code
                reason = `CodeWhisperer Invocation Exception: ${error?.code ?? error?.name ?? 'unknown'}`
                await this.onThrottlingException(error, triggerType)

                if (error?.code === 'AccessDeniedException' && errorMessage?.includes('no identity-based policy')) {
                    getLogger().error('amazonq inline-suggest: AccessDeniedException : %s', (error as Error).message)
                    void vscode.window
                        .showErrorMessage(`CodeWhisperer: ${error?.message}`, CodeWhispererConstants.settingsLearnMore)
                        .then(async (resp) => {
                            if (resp === CodeWhispererConstants.settingsLearnMore) {
                                void openUrl(vscode.Uri.parse(CodeWhispererConstants.learnMoreUri))
                            }
                        })
                    await vscode.commands.executeCommand('aws.amazonq.enableCodeSuggestions', false)
                }
            } else {
                errorMessage = error instanceof Error ? error.message : String(error)
                reason = error ? String(error) : 'unknown'
            }
        } finally {
            const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone

            let msg = indent(
                `codewhisperer: request-id: ${requestId},
                timestamp(epoch): ${Date.now()},
                timezone: ${timezone},
                datetime: ${new Date().toLocaleString([], { timeZone: timezone })},
                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},
                latency: ${latency} ms.
                Recommendations:`,
                4,
                true
            ).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)
            }
            getLogger().debug(msg)
            if (invocationResult === 'Succeeded') {
                CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount()
                UserWrittenCodeTracker.instance.onQFeatureInvoked()
            } else {
                if (
                    (errorMessage?.includes(invalidCustomizationMessage) && errorCode === 'AccessDeniedException') ||
                    errorCode === 'ResourceNotFoundException'
                ) {
                    getLogger()
                        .debug(`The selected customization is no longer available. Retrying with the default model.
                    Failed request id: ${requestId}`)
                    await switchToBaseCustomizationAndNotify()
                    await this.getRecommendations(
                        client,
                        editor,
                        triggerType,
                        config,
                        autoTriggerType,
                        pagination,
                        page,
                        true
                    )
                }
            }

            if (shouldRecordServiceInvocation) {
                TelemetryHelper.instance.recordServiceInvocationTelemetry(
                    requestId,
                    sessionId,
                    session.recommendations.length + recommendations.length - 1,
                    invocationResult,
                    latency,
                    session.language,
                    session.taskType,
                    reason,
                    session.requestContext.supplementalMetadata
                )
            }
        }

        if (this.isCancellationRequested()) {
            return Promise.resolve<GetRecommendationsResponse>({
                result: invocationResult,
                errorMessage: errorMessage,
                recommendationCount: session.recommendations.length,
            })
        }

        const typedPrefix = editor.document
            .getText(new vscode.Range(session.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
                if (
                    !r.content.startsWith(typedPrefix) &&
                    session.getSuggestionState(recommendationIndex) === undefined
                ) {
                    session.setSuggestionState(recommendationIndex, 'Discard')
                }
                session.setCompletionType(recommendationIndex, r)
            }
            session.recommendations = pagination ? session.recommendations.concat(recommendations) : recommendations
            if (isInlineCompletionEnabled() && this.hasAtLeastOneValidSuggestion(typedPrefix)) {
                this._onDidReceiveRecommendation.fire()
            }
        }

        this.requestId = requestId
        session.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)
                // Received an empty list of recommendations
                TelemetryHelper.instance.recordUserDecisionTelemetryForEmptyList(
                    session.requestIdList,
                    sessionId,
                    page,
                    runtimeLanguageContext.getLanguageContext(
                        editor.document.languageId,
                        path.extname(editor.document.fileName)
                    ).language,
                    session.requestContext.supplementalMetadata
                )
            }
            // case 2: non empty list of suggestion but with (a) empty content or (b) non-matching typeahead
            else if (!this.hasAtLeastOneValidSuggestion(typedPrefix)) {
                this.reportUserDecisions(-1)
            }
        }
        return Promise.resolve<GetRecommendationsResponse>({
            result: invocationResult,
            errorMessage: errorMessage,
            recommendationCount: session.recommendations.length,
        })
    }