Skip to content

Commit 5ec7329

Browse files
committed
telemetry(amazonq): Update /dev error classification logic
1 parent 6d5ec44 commit 5ec7329

File tree

9 files changed

+155
-92
lines changed

9 files changed

+155
-92
lines changed

packages/core/src/amazonq/errors.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,37 @@
99
* When thrown from common components, individual agents can catch and transform this error
1010
* to provide their own customized error messages.
1111
*/
12-
import { ToolkitError } from '../shared/errors'
12+
import { ErrorInformation, ToolkitError } from '../shared/errors'
1313

14-
export class ContentLengthError extends ToolkitError {
15-
constructor(message: string) {
16-
super(message, { code: 'ContentLengthError' })
14+
/**
15+
* Errors extending this class are considered "errors" in service metrics.
16+
*/
17+
export class ClientError extends ToolkitError {
18+
constructor(message: string, info: ErrorInformation = {}) {
19+
super(message, info)
20+
}
21+
}
22+
23+
/**
24+
* Errors extending this class are considered "faults" in service metrics.
25+
*/
26+
export class ServiceError extends ToolkitError {
27+
constructor(message: string, info: ErrorInformation = {}) {
28+
super(message, info)
29+
}
30+
}
31+
32+
/**
33+
* Errors extending this class are considered "LLM failures" in service metrics.
34+
*/
35+
export class LlmError extends ToolkitError {
36+
constructor(message: string, info: ErrorInformation = {}) {
37+
super(message, info)
38+
}
39+
}
40+
41+
export class ContentLengthError extends ClientError {
42+
constructor(message: string, info: ErrorInformation = { code: 'ContentLengthError' }) {
43+
super(message, info)
1744
}
1845
}

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ export class LspController {
357357
amazonqIndexMemoryUsageInMB: usage ? usage.memoryUsage / (1024 * 1024) : undefined,
358358
amazonqIndexCpuUsagePercentage: usage ? usage.cpuUsage : undefined,
359359
amazonqIndexFileSizeInMB: totalSizeBytes / (1024 * 1024),
360+
amazonqVectorIndexEnabled: buildIndexConfig.isVectorIndexEnabled,
360361
credentialStartUrl: buildIndexConfig.startUrl,
361362
})
362363
} else {
@@ -366,6 +367,7 @@ export class LspController {
366367
result: 'Failed',
367368
amazonqIndexFileCount: 0,
368369
amazonqIndexFileSizeInMB: 0,
370+
amazonqVectorIndexEnabled: buildIndexConfig.isVectorIndexEnabled,
369371
reason: `Unknown`,
370372
})
371373
}
@@ -377,6 +379,7 @@ export class LspController {
377379
result: 'Failed',
378380
amazonqIndexFileCount: 0,
379381
amazonqIndexFileSizeInMB: 0,
382+
amazonqVectorIndexEnabled: buildIndexConfig.isVectorIndexEnabled,
380383
reason: `${error instanceof Error ? error.name : 'Unknown'}`,
381384
reasonDesc: `Error when building index. ${error instanceof Error ? error.message : error}`,
382385
})

packages/core/src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import { featureName, startTaskAssistLimitReachedMessage } from '../constants'
1414
import { CodeReference } from '../../amazonq/webview/ui/connector'
1515
import {
1616
ApiError,
17+
ApiServiceError,
1718
CodeIterationLimitError,
1819
ContentLengthError,
20+
FeatureDevServiceError,
1921
MonthlyConversationLimitError,
2022
UnknownApiError,
2123
} from '../errors'
22-
import { ToolkitError, isAwsError } from '../../shared/errors'
24+
import { isAwsError } from '../../shared/errors'
2325
import { getCodewhispererConfig } from '../../codewhisperer/client/codewhisperer'
2426
import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient'
2527
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
@@ -93,7 +95,7 @@ export class FeatureDevClient implements FeatureClient {
9395
) {
9496
throw new MonthlyConversationLimitError(e.message)
9597
}
96-
throw new ApiError(e.message, 'CreateConversation', e.code, e.statusCode ?? 400)
98+
throw ApiError.of(e.message, 'CreateConversation', e.code, e.statusCode ?? 500)
9799
}
98100

99101
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateConversation')
@@ -136,7 +138,7 @@ export class FeatureDevClient implements FeatureClient {
136138
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
137139
throw new ContentLengthError()
138140
}
139-
throw new ApiError(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 400)
141+
throw ApiError.of(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 500)
140142
}
141143

142144
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateUploadUrl')
@@ -198,8 +200,10 @@ export class FeatureDevClient implements FeatureClient {
198200
) {
199201
throw new CodeIterationLimitError()
200202
}
203+
throw ApiError.of(e.message, 'StartTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
201204
}
202-
throw new ToolkitError((e as Error).message, { code: 'StartCodeGenerationFailed' })
205+
206+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'StartTaskAssistCodeGeneration')
203207
}
204208
}
205209

@@ -220,7 +224,12 @@ export class FeatureDevClient implements FeatureClient {
220224
(e as any).requestId
221225
}`
222226
)
223-
throw new ToolkitError((e as Error).message, { code: 'GetCodeGenerationFailed' })
227+
228+
if (isAwsError(e)) {
229+
throw ApiError.of(e.message, 'GetTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
230+
}
231+
232+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'GetTaskAssistCodeGeneration')
224233
}
225234
}
226235

@@ -235,7 +244,12 @@ export class FeatureDevClient implements FeatureClient {
235244
const archiveResponse = await streamingClient.exportResultArchive(params)
236245
const buffer: number[] = []
237246
if (archiveResponse.body === undefined) {
238-
throw new ToolkitError('Empty response from CodeWhisperer Streaming service.')
247+
throw new ApiServiceError(
248+
'Empty response from CodeWhisperer Streaming service.',
249+
'ExportResultArchive',
250+
'EmptyResponse',
251+
500
252+
)
239253
}
240254
for await (const chunk of archiveResponse.body) {
241255
if (chunk.internalServerException !== undefined) {
@@ -274,7 +288,12 @@ export class FeatureDevClient implements FeatureClient {
274288
(e as any).requestId
275289
}`
276290
)
277-
throw new ToolkitError((e as Error).message, { code: 'ExportResultArchiveFailed' })
291+
292+
if (isAwsError(e)) {
293+
throw ApiError.of(e.message, 'ExportResultArchive', e.code, e.statusCode ?? 500)
294+
}
295+
296+
throw new FeatureDevServiceError(e instanceof Error ? e.message : 'Unknown error', 'ExportResultArchive')
278297
}
279298
}
280299

packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { randomUUID } from '../../../shared/crypto'
5656
import { FollowUpTypes } from '../../../amazonq/commons/types'
5757
import { Messenger } from '../../../amazonq/commons/connector/baseMessenger'
5858
import { BaseChatSessionStorage } from '../../../amazonq/commons/baseChatStorage'
59+
import { ClientError, LlmError, ServiceError } from '../../../amazonq/errors'
5960

6061
export interface ChatControllerEventEmitters {
6162
readonly processHumanChatMessage: EventEmitter<any>
@@ -551,32 +552,20 @@ export class FeatureDevController {
551552
} catch (err: any) {
552553
getLogger().error(`${featureName}: Error during code generation: ${err}`)
553554

554-
let result: string
555-
switch (err.constructor.name) {
556-
case FeatureDevServiceError.name:
557-
if (err.code === 'EmptyPatchException') {
558-
result = MetricDataResult.LlmFailure
559-
} else if (err.code === 'GuardrailsException' || err.code === 'ThrottlingException') {
560-
result = MetricDataResult.Error
561-
} else {
562-
result = MetricDataResult.Fault
563-
}
564-
break
565-
case MonthlyConversationLimitError.name:
566-
case CodeIterationLimitError.name:
567-
case PromptRefusalException.name:
568-
case NoChangeRequiredException.name:
555+
let result: string = MetricDataResult.Fault
556+
if (err instanceof ClientError) {
557+
result = MetricDataResult.Error
558+
} else if (err instanceof ServiceError) {
559+
result = MetricDataResult.Fault
560+
} else if (err instanceof LlmError) {
561+
result = MetricDataResult.LlmFailure
562+
} else {
563+
if (isAPIClientError(err)) {
569564
result = MetricDataResult.Error
570-
break
571-
default:
572-
if (isAPIClientError(err)) {
573-
result = MetricDataResult.Error
574-
} else {
575-
result = MetricDataResult.Fault
576-
}
577-
break
565+
} else {
566+
result = MetricDataResult.Fault
567+
}
578568
}
579-
580569
await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, result)
581570
throw err
582571
} finally {

packages/core/src/amazonqFeatureDev/errors.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,140 +3,156 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { ToolkitError } from '../shared/errors'
7-
import {
8-
featureName,
9-
clientErrorMessages,
10-
startCodeGenClientErrorMessages,
11-
startTaskAssistLimitReachedMessage,
12-
} from './constants'
6+
import { featureName, clientErrorMessages, startTaskAssistLimitReachedMessage } from './constants'
137
import { uploadCodeError } from './userFacingText'
148
import { i18n } from '../shared/i18n-helper'
9+
import { ClientError, ServiceError, ContentLengthError as SharedContentLengthError } from '../amazonq/errors'
1510

16-
export class ConversationIdNotFoundError extends ToolkitError {
11+
export class ConversationIdNotFoundError extends ServiceError {
1712
constructor() {
1813
super(i18n('AWS.amazonq.featureDev.error.conversationIdNotFoundError'), {
1914
code: 'ConversationIdNotFound',
2015
})
2116
}
2217
}
2318

24-
export class TabIdNotFoundError extends ToolkitError {
19+
export class TabIdNotFoundError extends ServiceError {
2520
constructor() {
2621
super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), {
2722
code: 'TabIdNotFound',
2823
})
2924
}
3025
}
3126

32-
export class WorkspaceFolderNotFoundError extends ToolkitError {
27+
export class WorkspaceFolderNotFoundError extends ServiceError {
3328
constructor() {
3429
super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), {
3530
code: 'WorkspaceFolderNotFound',
3631
})
3732
}
3833
}
3934

40-
export class UserMessageNotFoundError extends ToolkitError {
35+
export class UserMessageNotFoundError extends ServiceError {
4136
constructor() {
4237
super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), {
4338
code: 'MessageNotFound',
4439
})
4540
}
4641
}
4742

48-
export class SelectedFolderNotInWorkspaceFolderError extends ToolkitError {
43+
export class SelectedFolderNotInWorkspaceFolderError extends ClientError {
4944
constructor() {
5045
super(i18n('AWS.amazonq.featureDev.error.selectedFolderNotInWorkspaceFolderError'), {
5146
code: 'SelectedFolderNotInWorkspaceFolder',
5247
})
5348
}
5449
}
5550

56-
export class PromptRefusalException extends ToolkitError {
51+
export class PromptRefusalException extends ClientError {
5752
constructor() {
5853
super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), {
5954
code: 'PromptRefusalException',
6055
})
6156
}
6257
}
6358

64-
export class NoChangeRequiredException extends ToolkitError {
59+
export class NoChangeRequiredException extends ClientError {
6560
constructor() {
6661
super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
6762
code: 'NoChangeRequiredException',
6863
})
6964
}
7065
}
7166

72-
export class FeatureDevServiceError extends ToolkitError {
67+
export class FeatureDevServiceError extends ServiceError {
7368
constructor(message: string, code: string) {
7469
super(message, { code })
7570
}
7671
}
7772

78-
export class PrepareRepoFailedError extends ToolkitError {
73+
export class PrepareRepoFailedError extends ServiceError {
7974
constructor() {
8075
super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), {
8176
code: 'PrepareRepoFailed',
8277
})
8378
}
8479
}
8580

86-
export class UploadCodeError extends ToolkitError {
81+
export class UploadCodeError extends ServiceError {
8782
constructor(statusCode: string) {
8883
super(uploadCodeError, { code: `UploadCode-${statusCode}` })
8984
}
9085
}
9186

92-
export class UploadURLExpired extends ToolkitError {
87+
export class UploadURLExpired extends ClientError {
9388
constructor() {
9489
super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' })
9590
}
9691
}
9792

98-
export class IllegalStateTransition extends ToolkitError {
93+
export class IllegalStateTransition extends ServiceError {
9994
constructor() {
10095
super(i18n('AWS.amazonq.featureDev.error.illegalStateTransition'), { code: 'IllegalStateTransition' })
10196
}
10297
}
10398

104-
export class ContentLengthError extends ToolkitError {
99+
export class IllegalStateError extends ServiceError {
100+
constructor(message: string) {
101+
super(message, { code: 'IllegalStateTransition' })
102+
}
103+
}
104+
105+
export class ContentLengthError extends SharedContentLengthError {
105106
constructor() {
106107
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name })
107108
}
108109
}
109110

110-
export class ZipFileError extends ToolkitError {
111+
export class ZipFileError extends ServiceError {
111112
constructor() {
112113
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name })
113114
}
114115
}
115116

116-
export class CodeIterationLimitError extends ToolkitError {
117+
export class CodeIterationLimitError extends ClientError {
117118
constructor() {
118119
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name })
119120
}
120121
}
121122

122-
export class MonthlyConversationLimitError extends ToolkitError {
123+
export class MonthlyConversationLimitError extends ClientError {
123124
constructor(message: string) {
124125
super(message, { code: MonthlyConversationLimitError.name })
125126
}
126127
}
127128

128-
export class UnknownApiError extends ToolkitError {
129+
export class UnknownApiError extends ServiceError {
129130
constructor(message: string, api: string) {
130131
super(message, { code: `${api}-Unknown` })
131132
}
132133
}
133134

134-
export class ApiError extends ToolkitError {
135+
export class ApiClientError extends ClientError {
136+
constructor(message: string, api: string, errorName: string, errorCode: number) {
137+
super(message, { code: `${api}-${errorName}-${errorCode}` })
138+
}
139+
}
140+
141+
export class ApiServiceError extends ServiceError {
135142
constructor(message: string, api: string, errorName: string, errorCode: number) {
136143
super(message, { code: `${api}-${errorName}-${errorCode}` })
137144
}
138145
}
139146

147+
export class ApiError {
148+
static of(message: string, api: string, errorName: string, errorCode: number) {
149+
if (errorCode >= 400 && errorCode < 500) {
150+
return new ApiClientError(message, api, errorName, errorCode)
151+
}
152+
return new ApiServiceError(message, api, errorName, errorCode)
153+
}
154+
}
155+
140156
export const denyListedErrors: string[] = ['Deserialization error', 'Inaccessible host']
141157

142158
export function createUserFacingErrorMessage(message: string) {
@@ -148,8 +164,6 @@ export function createUserFacingErrorMessage(message: string) {
148164

149165
export function isAPIClientError(error: { code?: string; message: string }): boolean {
150166
return (
151-
(error.code === 'StartCodeGenerationFailed' &&
152-
startCodeGenClientErrorMessages.some((msg: string) => error.message.includes(msg))) ||
153167
clientErrorMessages.some((msg: string) => error.message.includes(msg)) ||
154168
error.message.includes(startTaskAssistLimitReachedMessage)
155169
)

0 commit comments

Comments
 (0)