Skip to content

Commit 1a4b1a8

Browse files
committed
chore(amazonq): Update /dev error classification logic
1 parent 6d5ec44 commit 1a4b1a8

File tree

6 files changed

+131
-70
lines changed

6 files changed

+131
-70
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/amazonqFeatureDev/client/featureDev.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import * as FeatureDevProxyClient from './featuredevproxyclient'
1313
import { featureName, startTaskAssistLimitReachedMessage } from '../constants'
1414
import { CodeReference } from '../../amazonq/webview/ui/connector'
1515
import {
16-
ApiError,
16+
ApiServiceError,
1717
CodeIterationLimitError,
1818
ContentLengthError,
19+
FeatureDevServiceError,
1920
MonthlyConversationLimitError,
21+
throwApiError,
2022
UnknownApiError,
2123
} from '../errors'
2224
import { ToolkitError, isAwsError } from '../../shared/errors'
@@ -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+
throwApiError(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+
throwApiError(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+
throwApiError(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+
throwApiError(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+
throwApiError(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: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { ToolkitError } from '../shared/errors'
76
import {
87
featureName,
98
clientErrorMessages,
@@ -12,131 +11,151 @@ import {
1211
} from './constants'
1312
import { uploadCodeError } from './userFacingText'
1413
import { i18n } from '../shared/i18n-helper'
14+
import { ClientError, ServiceError, ContentLengthError as SharedContentLengthError } from '../amazonq/errors'
1515

16-
export class ConversationIdNotFoundError extends ToolkitError {
16+
export class ConversationIdNotFoundError extends ServiceError {
1717
constructor() {
1818
super(i18n('AWS.amazonq.featureDev.error.conversationIdNotFoundError'), {
1919
code: 'ConversationIdNotFound',
2020
})
2121
}
2222
}
2323

24-
export class TabIdNotFoundError extends ToolkitError {
24+
export class TabIdNotFoundError extends ServiceError {
2525
constructor() {
2626
super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), {
2727
code: 'TabIdNotFound',
2828
})
2929
}
3030
}
3131

32-
export class WorkspaceFolderNotFoundError extends ToolkitError {
32+
export class WorkspaceFolderNotFoundError extends ServiceError {
3333
constructor() {
3434
super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), {
3535
code: 'WorkspaceFolderNotFound',
3636
})
3737
}
3838
}
3939

40-
export class UserMessageNotFoundError extends ToolkitError {
40+
export class UserMessageNotFoundError extends ServiceError {
4141
constructor() {
4242
super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), {
4343
code: 'MessageNotFound',
4444
})
4545
}
4646
}
4747

48-
export class SelectedFolderNotInWorkspaceFolderError extends ToolkitError {
48+
export class SelectedFolderNotInWorkspaceFolderError extends ClientError {
4949
constructor() {
5050
super(i18n('AWS.amazonq.featureDev.error.selectedFolderNotInWorkspaceFolderError'), {
5151
code: 'SelectedFolderNotInWorkspaceFolder',
5252
})
5353
}
5454
}
5555

56-
export class PromptRefusalException extends ToolkitError {
56+
export class PromptRefusalException extends ClientError {
5757
constructor() {
5858
super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), {
5959
code: 'PromptRefusalException',
6060
})
6161
}
6262
}
6363

64-
export class NoChangeRequiredException extends ToolkitError {
64+
export class NoChangeRequiredException extends ClientError {
6565
constructor() {
6666
super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
6767
code: 'NoChangeRequiredException',
6868
})
6969
}
7070
}
7171

72-
export class FeatureDevServiceError extends ToolkitError {
72+
export class FeatureDevServiceError extends ServiceError {
7373
constructor(message: string, code: string) {
7474
super(message, { code })
7575
}
7676
}
7777

78-
export class PrepareRepoFailedError extends ToolkitError {
78+
export class PrepareRepoFailedError extends ServiceError {
7979
constructor() {
8080
super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), {
8181
code: 'PrepareRepoFailed',
8282
})
8383
}
8484
}
8585

86-
export class UploadCodeError extends ToolkitError {
86+
export class UploadCodeError extends ServiceError {
8787
constructor(statusCode: string) {
8888
super(uploadCodeError, { code: `UploadCode-${statusCode}` })
8989
}
9090
}
9191

92-
export class UploadURLExpired extends ToolkitError {
92+
export class UploadURLExpired extends ClientError {
9393
constructor() {
9494
super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' })
9595
}
9696
}
9797

98-
export class IllegalStateTransition extends ToolkitError {
98+
export class IllegalStateTransition extends ServiceError {
9999
constructor() {
100100
super(i18n('AWS.amazonq.featureDev.error.illegalStateTransition'), { code: 'IllegalStateTransition' })
101101
}
102102
}
103103

104-
export class ContentLengthError extends ToolkitError {
104+
export class IllegalStateError extends ServiceError {
105+
constructor(message: string) {
106+
super(message, { code: 'IllegalStateTransition' })
107+
}
108+
}
109+
110+
export class ContentLengthError extends SharedContentLengthError {
105111
constructor() {
106112
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name })
107113
}
108114
}
109115

110-
export class ZipFileError extends ToolkitError {
116+
export class ZipFileError extends ServiceError {
111117
constructor() {
112118
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name })
113119
}
114120
}
115121

116-
export class CodeIterationLimitError extends ToolkitError {
122+
export class CodeIterationLimitError extends ClientError {
117123
constructor() {
118124
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name })
119125
}
120126
}
121127

122-
export class MonthlyConversationLimitError extends ToolkitError {
128+
export class MonthlyConversationLimitError extends ClientError {
123129
constructor(message: string) {
124130
super(message, { code: MonthlyConversationLimitError.name })
125131
}
126132
}
127133

128-
export class UnknownApiError extends ToolkitError {
134+
export class UnknownApiError extends ServiceError {
129135
constructor(message: string, api: string) {
130136
super(message, { code: `${api}-Unknown` })
131137
}
132138
}
133139

134-
export class ApiError extends ToolkitError {
140+
export class ApiClientError extends ClientError {
141+
constructor(message: string, api: string, errorName: string, errorCode: number) {
142+
super(message, { code: `${api}-${errorName}-${errorCode}` })
143+
}
144+
}
145+
146+
export class ApiServiceError extends ServiceError {
135147
constructor(message: string, api: string, errorName: string, errorCode: number) {
136148
super(message, { code: `${api}-${errorName}-${errorCode}` })
137149
}
138150
}
139151

152+
export function throwApiError(message: string, api: string, errorName: string, errorCode: number) {
153+
if (errorCode >= 400 && errorCode < 500) {
154+
throw new ApiClientError(message, api, errorName, errorCode)
155+
}
156+
throw new ApiServiceError(message, api, errorName, errorCode)
157+
}
158+
140159
export const denyListedErrors: string[] = ['Deserialization error', 'Inaccessible host']
141160

142161
export function createUserFacingErrorMessage(message: string) {
@@ -148,8 +167,6 @@ export function createUserFacingErrorMessage(message: string) {
148167

149168
export function isAPIClientError(error: { code?: string; message: string }): boolean {
150169
return (
151-
(error.code === 'StartCodeGenerationFailed' &&
152-
startCodeGenClientErrorMessages.some((msg: string) => error.message.includes(msg))) ||
153170
clientErrorMessages.some((msg: string) => error.message.includes(msg)) ||
154171
error.message.includes(startTaskAssistLimitReachedMessage)
155172
)

packages/core/src/amazonqFeatureDev/session/session.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
type SessionStateConfig,
1515
UpdateFilesPathsParams,
1616
} from '../../amazonq/commons/types'
17-
import { ContentLengthError, ConversationIdNotFoundError } from '../errors'
17+
import { ContentLengthError, ConversationIdNotFoundError, IllegalStateError, IllegalStateTransition } from '../errors'
1818
import { featureDevChat, referenceLogText, featureDevScheme } from '../constants'
1919
import fs from '../../shared/fs/fs'
2020
import { FeatureDevClient } from '../client/featureDev'
@@ -353,7 +353,7 @@ export class Session {
353353

354354
get state() {
355355
if (!this._state) {
356-
throw new Error("State should be initialized before it's read")
356+
throw new IllegalStateError("State should be initialized before it's read")
357357
}
358358
return this._state
359359
}
@@ -364,7 +364,7 @@ export class Session {
364364

365365
get uploadId() {
366366
if (!('uploadId' in this.state)) {
367-
throw new Error("UploadId has to be initialized before it's read")
367+
throw new IllegalStateError("UploadId has to be initialized before it's read")
368368
}
369369
return this.state.uploadId
370370
}

0 commit comments

Comments
 (0)