diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts index 3dce5c00c9..d2ecac59c0 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts @@ -6,6 +6,7 @@ import * as path from 'path' import { ChatTriggerType, + CodeWhispererStreamingServiceException, GenerateAssistantResponseCommandInput, GenerateAssistantResponseCommandOutput, SendMessageCommandInput, @@ -68,7 +69,7 @@ import { ChatSessionManagementService } from '../chat/chatSessionManagementServi import { ChatTelemetryController } from '../chat/telemetry/chatTelemetryController' import { QuickAction } from '../chat/quickActions' import { Metric } from '../../shared/telemetry/metric' -import { getErrorMessage, isAwsError, isNullish, isObject } from '../../shared/utils' +import { getErrorMessage, getHttpStatusCode, isAwsError, isNullish, isObject } from '../../shared/utils' import { HELP_MESSAGE } from '../chat/constants' import { TelemetryService } from '../../shared/telemetry/telemetryService' import { @@ -1094,9 +1095,22 @@ export class AgenticChatController implements ChatHandlers { tabId: string, metric: Metric ): ChatResult | ResponseError { - if (isAwsError(err) || (isObject(err) && 'statusCode' in err && typeof err.statusCode === 'number')) { - metric.setDimension('cwsprChatRepsonseCode', err.statusCode ?? 400) - this.#telemetryController.emitMessageResponseError(tabId, metric.metric, err.requestId, err.message) + if (isAwsError(err) || (isObject(err) && typeof getHttpStatusCode(err) === 'number')) { + let errorMessage: string + let requestID: string | undefined + + if (err instanceof CodeWhispererStreamingServiceException) { + errorMessage = err.message + requestID = err.$metadata.requestId + } else { + errorMessage = 'Not a CodeWhispererStreamingServiceException.' + if (err instanceof Error || err?.message) { + errorMessage += ` Error is: ${err.message}` + } + } + + metric.setDimension('cwsprChatResponseCode', getHttpStatusCode(err) ?? 0) + this.#telemetryController.emitMessageResponseError(tabId, metric.metric, requestID, errorMessage) } // return non-model errors back to the client as errors diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/telemetry/chatTelemetryController.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/telemetry/chatTelemetryController.ts index eb87238ddd..826763f6b9 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/chat/telemetry/chatTelemetryController.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/chat/telemetry/chatTelemetryController.ts @@ -250,25 +250,25 @@ export class ChatTelemetryController { requestId?: string, errorReason?: string ) { - this.emitConversationMetric( - { - name: ChatTelemetryEventName.MessageResponseError, - data: { - cwsprChatHasCodeSnippet: metric.cwsprChatHasCodeSnippet, - cwsprChatTriggerInteraction: metric.cwsprChatTriggerInteraction, - cwsprChatUserIntent: metric.cwsprChatUserIntent, - cwsprChatProgrammingLanguage: metric.cwsprChatProgrammingLanguage, - cwsprChatActiveEditorTotalCharacters: metric.cwsprChatActiveEditorTotalCharacters, - cwsprChatActiveEditorImportCount: metric.cwsprChatActiveEditorImportCount, - cwsprChatRepsonseCode: metric.cwsprChatRepsonseCode, - cwsprChatRequestLength: metric.cwsprChatRequestLength, - cwsprChatConversationType: metric.cwsprChatConversationType, - requestId: requestId, - reasonDesc: getTelemetryReasonDesc(errorReason), - }, + this.#telemetry.emitMetric({ + name: ChatTelemetryEventName.MessageResponseError, + data: { + cwsprChatHasCodeSnippet: metric.cwsprChatHasCodeSnippet, + cwsprChatTriggerInteraction: metric.cwsprChatTriggerInteraction, + cwsprChatUserIntent: metric.cwsprChatUserIntent, + cwsprChatProgrammingLanguage: metric.cwsprChatProgrammingLanguage, + cwsprChatActiveEditorTotalCharacters: metric.cwsprChatActiveEditorTotalCharacters, + cwsprChatActiveEditorImportCount: metric.cwsprChatActiveEditorImportCount, + cwsprChatRepsonseCode: metric.cwsprChatRepsonseCode, + cwsprChatRequestLength: metric.cwsprChatRequestLength, + cwsprChatConversationType: metric.cwsprChatConversationType, + requestId: requestId, + reasonDesc: getTelemetryReasonDesc(errorReason), + credentialStartUrl: this.#credentialsProvider.getConnectionMetadata()?.sso?.startUrl, + result: 'Succeeded', + [CONVERSATION_ID_METRIC_KEY]: this.getConversationId(tabId), }, - tabId - ) + }) } public enqueueCodeDiffEntry(params: Omit) { diff --git a/server/aws-lsp-codewhisperer/src/shared/utils.ts b/server/aws-lsp-codewhisperer/src/shared/utils.ts index 7c4d30ca0a..23418d2579 100644 --- a/server/aws-lsp-codewhisperer/src/shared/utils.ts +++ b/server/aws-lsp-codewhisperer/src/shared/utils.ts @@ -4,6 +4,8 @@ import { distance } from 'fastest-levenshtein' import { Suggestion } from './codeWhispererService' import { CodewhispererCompletionType } from './telemetry/types' import { BUILDER_ID_START_URL, crashMonitoringDirName, driveLetterRegex, MISSING_BEARER_TOKEN_ERROR } from './constants' +import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming' +import { ServiceException } from '@smithy/smithy-client' import { getAuthFollowUpType } from '../language-server/chat/utils' export type SsoConnectionType = 'builderId' | 'identityCenter' | 'none' @@ -303,6 +305,27 @@ export function isStringOrNull(object: any): object is string | null { return typeof object === 'string' || object === null } +// Port of implementation in AWS Toolkit for VSCode +// https://github.com/aws/aws-toolkit-vscode/blob/c22efa03e73b241564c8051c35761eb8620edb83/packages/core/src/shared/errors.ts#L648 +export function getHttpStatusCode(err: unknown): number | undefined { + if (hasResponse(err) && err?.$response?.statusCode !== undefined) { + return err?.$response?.statusCode + } + if (hasMetadata(err) && err.$metadata?.httpStatusCode !== undefined) { + return err.$metadata?.httpStatusCode + } + + return undefined +} + +function hasResponse(error: T): error is T & Pick { + return typeof (error as { $response?: unknown })?.$response === 'object' +} + +function hasMetadata(error: T): error is T & Pick { + return typeof (error as { $metadata?: unknown })?.$metadata === 'object' +} + export function hasConnectionExpired(error: any) { if (error instanceof Error) { const authFollowType = getAuthFollowUpType(error)