diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index 6c22cb06b84..b78e6806f8d 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -30,6 +30,7 @@ import { extractAuthFollowUp } from 'aws-core-vscode/amazonq' import { InlineChatParams, InlineChatResult } from '@aws/language-server-runtimes-types' import { decryptResponse, encryptRequest } from '../../lsp/encryption' import { getCursorState } from '../../lsp/utils' +import { CwsprChatTriggerInteraction, telemetry } from 'aws-core-vscode/telemetry' export class InlineChatProvider { private readonly editorContextExtractor: EditorContextExtractor @@ -69,6 +70,8 @@ export class InlineChatProvider { } public async processPromptMessageLSP(message: PromptMessage): Promise { + this.throwOnIamSession(message) + // TODO: handle partial responses. getLogger().info('Making inline chat request with message %O', message) const params = this.getCurrentEditorParams(message.message ?? '') @@ -83,6 +86,8 @@ export class InlineChatProvider { // TODO: remove in favor of LSP implementation. public async processPromptMessage(message: PromptMessage) { + this.throwOnIamSession(message) + return this.editorContextExtractor .extractContextForTrigger('ChatMessage') .then((context) => { @@ -225,6 +230,34 @@ export class InlineChatProvider { }) } + private throwOnIamSession(message: PromptMessage) { + const triggerEvent = this.triggerEventsStorage.getLastTriggerEventByTabID(message.tabID) + let triggerInteraction: CwsprChatTriggerInteraction + switch (triggerEvent?.type) { + case 'editor_context_command': + triggerInteraction = triggerEvent.command?.triggerType === 'keybinding' ? 'hotkeys' : 'contextMenu' + break + case 'follow_up': + case 'chat_message': + default: + triggerInteraction = 'click' + break + } + + if (!AuthUtil.instance.isSsoSession()) { + telemetry.amazonq_messageResponseError.emit({ + result: 'Failed', + cwsprChatConversationType: 'InlineChat', + cwsprChatRequestLength: message.message?.length ?? 0, + cwsprChatResponseCode: 401, + cwsprChatTriggerInteraction: triggerInteraction, + reason: 'AuthenticationError', + reasonDesc: 'Inline chat requires SSO authentication, but current session is not', + }) + throw new ToolkitError('Inline chat is only available with SSO authentication') + } + } + public sendTelemetryEvent(inlineChatEvent: InlineChatEvent, currentTask?: InlineTask) { codeWhispererClient .sendTelemetryEvent({ diff --git a/packages/core/src/codewhispererChat/index.ts b/packages/core/src/codewhispererChat/index.ts index b47115fbc4a..a008cd13d72 100644 --- a/packages/core/src/codewhispererChat/index.ts +++ b/packages/core/src/codewhispererChat/index.ts @@ -12,6 +12,6 @@ export { ChatTriggerType, PromptMessage, TriggerPayload } from './controllers/ch export { UserIntentRecognizer } from './controllers/chat/userIntent/userIntentRecognizer' export { EditorContextExtractor } from './editor/context/extractor' export { ChatSessionStorage } from './storages/chatSession' -export { TriggerEventsStorage } from './storages/triggerEvents' +export { TriggerEventsStorage, TriggerEvent } from './storages/triggerEvents' export { ReferenceLogController } from './view/messages/referenceLogController' export { extractLanguageNameFromFile } from './editor/context/file/languages' diff --git a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts index 36338318431..48cf184c0bd 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -20,7 +20,7 @@ import { builderIdStartUrl } from '../../../../auth/sso/constants' import { RegionProfile, vsCodeState } from '../../../../codewhisperer/models/model' import { randomUUID } from '../../../../shared/crypto' import globals from '../../../../shared/extensionGlobals' -import { telemetry } from '../../../../shared/telemetry/telemetry' +import { CredentialType, telemetry } from '../../../../shared/telemetry/telemetry' import { ProfileSwitchIntent } from '../../../../codewhisperer/region/regionProfileManager' const className = 'AmazonQLoginWebview' @@ -204,6 +204,15 @@ export class AmazonQLoginWebview extends CommonAuthWebview { // Defining separate auth function to emit telemetry before returning from this method await globals.globalState.update('recentIamKeys', { accessKey: accessKey }) await globals.globalState.update('recentRoleArn', { roleArn: roleArn }) + let credentialsType: CredentialType | undefined + if (!sessionToken && !roleArn) { + credentialsType = 'staticProfile' + } else if (roleArn) { + credentialsType = 'assumeRoleProfile' + } else { + credentialsType = 'staticSessionProfile' + } + const runAuth = async (): Promise => { try { await AuthUtil.instance.loginIam({ accessKey, secretKey, sessionToken, roleArn }) @@ -224,7 +233,10 @@ export class AmazonQLoginWebview extends CommonAuthWebview { const result = await runAuth() this.storeMetricMetadata({ credentialSourceId: 'sharedCredentials', - authEnabledFeatures: 'codewhisperer', + featureId: 'codewhisperer', + credentialType: credentialsType, + isReAuth: false, + isAggregated: false, ...this.getResultForMetrics(result), }) this.emitAuthMetric() diff --git a/packages/core/src/login/webview/vue/login.vue b/packages/core/src/login/webview/vue/login.vue index d58ce53b69a..ad92aab7f1b 100644 --- a/packages/core/src/login/webview/vue/login.vue +++ b/packages/core/src/login/webview/vue/login.vue @@ -500,6 +500,9 @@ export default defineComponent({ } } } else if (this.selectedLoginOption === LoginOption.IAM_CREDENTIAL) { + // Emit telemetry when IAM Credentials option is selected and Continue is clicked + void client.emitUiClick('auth_credentialsOption') + this.stage = 'AWS_PROFILE' this.$nextTick(() => document.getElementById('profileName')!.focus()) }