Skip to content

Commit 82df06d

Browse files
eamodiod13
authored andcommitted
Improves AI error messages more (#4227)
1 parent fa45bb3 commit 82df06d

File tree

7 files changed

+68
-29
lines changed

7 files changed

+68
-29
lines changed

docs/telemetry-events.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
'duration': number,
121121
'failed.cancelled.reason': 'large-prompt',
122122
'failed.error': string,
123+
'failed.error.detail': string,
123124
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
124125
'input.length': number,
125126
'model.id': string,
@@ -150,6 +151,7 @@
150151
'duration': number,
151152
'failed.cancelled.reason': 'large-prompt',
152153
'failed.error': string,
154+
'failed.error.detail': string,
153155
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
154156
'input.length': number,
155157
'model.id': string,
@@ -179,6 +181,7 @@ or
179181
'duration': number,
180182
'failed.cancelled.reason': 'large-prompt',
181183
'failed.error': string,
184+
'failed.error.detail': string,
182185
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
183186
'input.length': number,
184187
'model.id': string,
@@ -207,6 +210,7 @@ or
207210
'duration': number,
208211
'failed.cancelled.reason': 'large-prompt',
209212
'failed.error': string,
213+
'failed.error.detail': string,
210214
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
211215
'input.length': number,
212216
'model.id': string,
@@ -235,6 +239,7 @@ or
235239
'duration': number,
236240
'failed.cancelled.reason': 'large-prompt',
237241
'failed.error': string,
242+
'failed.error.detail': string,
238243
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
239244
'input.length': number,
240245
'model.id': string,
@@ -263,6 +268,7 @@ or
263268
'duration': number,
264269
'failed.cancelled.reason': 'large-prompt',
265270
'failed.error': string,
271+
'failed.error.detail': string,
266272
'failed.reason': 'user-declined' | 'user-cancelled' | 'error',
267273
'input.length': number,
268274
'model.id': string,

src/constants.telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ interface AIEventDataBase {
328328
'failed.reason'?: 'user-declined' | 'user-cancelled' | 'error';
329329
'failed.cancelled.reason'?: 'large-prompt';
330330
'failed.error'?: string;
331+
'failed.error.detail'?: string;
331332
}
332333

333334
interface AIExplainEvent extends AIEventDataBase {

src/errors.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,17 @@ export class RequiresIntegrationError extends Error {
278278
}
279279

280280
export const enum AIErrorReason {
281+
DeniedByOrganization,
282+
DeniedByUser,
283+
NoEntitlement,
281284
NoRequestData,
282-
Entitlement,
283-
RequestTooLarge,
284-
UserQuotaExceeded,
285285
RateLimitExceeded,
286286
RateLimitOrFundsExceeded,
287-
ServiceCapacityExceeded,
287+
RequestTooLarge,
288288
ModelNotSupported,
289-
ModelUserUnauthorized,
290-
ModelUserDeniedAccess,
289+
ServiceCapacityExceeded,
290+
Unauthorized,
291+
UserQuotaExceeded,
291292
}
292293

293294
export class AIError extends Error {
@@ -297,7 +298,7 @@ export class AIError extends Error {
297298
constructor(reason: AIErrorReason, original?: Error) {
298299
let message;
299300
switch (reason) {
300-
case AIErrorReason.Entitlement:
301+
case AIErrorReason.NoEntitlement:
301302
message = 'You do not have the required entitlement to use this feature';
302303
break;
303304
case AIErrorReason.RequestTooLarge:
@@ -321,11 +322,14 @@ export class AIError extends Error {
321322
case AIErrorReason.ModelNotSupported:
322323
message = 'Model not supported for this request';
323324
break;
324-
case AIErrorReason.ModelUserUnauthorized:
325-
message = 'User is not authorized to use the specified model';
325+
case AIErrorReason.Unauthorized:
326+
message = 'You are not authorized to use the specified provider or model';
327+
break;
328+
case AIErrorReason.DeniedByOrganization:
329+
message = 'Your organization has denied access to the specified provider or model';
326330
break;
327-
case AIErrorReason.ModelUserDeniedAccess:
328-
message = 'User denied access to the specified model';
331+
case AIErrorReason.DeniedByUser:
332+
message = 'You have denied access to the specified provider or model';
329333
break;
330334
default:
331335
message = original?.message ?? 'An unknown error occurred';

src/plus/ai/aiProviderService.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -906,26 +906,34 @@ export class AIProviderService implements Disposable {
906906

907907
return result;
908908
} catch (ex) {
909-
this.container.telemetry.sendEvent(
910-
telementry.key,
911-
{
912-
...telementry.data,
913-
duration: Date.now() - start,
914-
...(ex instanceof CancellationError
915-
? { 'failed.reason': 'user-cancelled' }
916-
: { 'failed.reason': 'error', 'failed.error': String(ex) }),
917-
},
918-
source,
919-
);
909+
if (ex instanceof CancellationError) {
910+
this.container.telemetry.sendEvent(
911+
telementry.key,
912+
{ ...telementry.data, duration: Date.now() - start, 'failed.reason': 'user-cancelled' },
913+
source,
914+
);
920915

921-
if (ex instanceof CancellationError) return undefined;
916+
return undefined;
917+
}
922918
if (ex instanceof AIError) {
919+
this.container.telemetry.sendEvent(
920+
telementry.key,
921+
{
922+
...telementry.data,
923+
duration: Date.now() - start,
924+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
925+
'failed.error': String(ex),
926+
'failed.error.detail': String(ex.original),
927+
},
928+
source,
929+
);
930+
923931
switch (ex.reason) {
924932
case AIErrorReason.NoRequestData:
925933
void window.showErrorMessage(ex.message);
926934
return undefined;
927935

928-
case AIErrorReason.Entitlement: {
936+
case AIErrorReason.NoEntitlement: {
929937
const sub = await this.container.subscription.getSubscription();
930938

931939
const plan = isSubscriptionPaid(sub)
@@ -1011,7 +1019,7 @@ export class AIProviderService implements Disposable {
10111019
}
10121020
return undefined;
10131021
}
1014-
case AIErrorReason.ModelUserUnauthorized: {
1022+
case AIErrorReason.Unauthorized: {
10151023
const switchModel: MessageItem = { title: 'Switch Model' };
10161024
const result = await window.showErrorMessage(
10171025
'You do not have access to the selected model. Please select a different model and try again.',
@@ -1022,7 +1030,7 @@ export class AIProviderService implements Disposable {
10221030
}
10231031
return undefined;
10241032
}
1025-
case AIErrorReason.ModelUserDeniedAccess: {
1033+
case AIErrorReason.DeniedByUser: {
10261034
const switchModel: MessageItem = { title: 'Switch Model' };
10271035
const result = await window.showErrorMessage(
10281036
'You have denied access to the selected model. Please provide access or select a different model, and then try again.',
@@ -1038,6 +1046,16 @@ export class AIProviderService implements Disposable {
10381046
return undefined;
10391047
}
10401048

1049+
this.container.telemetry.sendEvent(
1050+
telementry.key,
1051+
{
1052+
...telementry.data,
1053+
duration: Date.now() - start,
1054+
'failed.error': String(ex),
1055+
'failed.error.detail': ex.original ? String(ex.original) : undefined,
1056+
},
1057+
source,
1058+
);
10411059
throw ex;
10421060
}
10431061
}

src/plus/ai/gitkrakenProvider.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class GitKrakenProvider extends OpenAICompatibleProvider<typeof provider.
135135
case 403:
136136
// CodeAuthorization = "403.1"
137137
// CodeEntitlement = "403.2"
138+
// CodeOrganization = "403.3"
138139

139140
// Entitlement Error
140141
if (code === 2) {
@@ -152,10 +153,19 @@ export class GitKrakenProvider extends OpenAICompatibleProvider<typeof provider.
152153

153154
throw new AIError(
154155
// If there is an `entitlementValue` then we are over the limit, otherwise it is an entitlement error
155-
data?.entitlementValue ? AIErrorReason.UserQuotaExceeded : AIErrorReason.Entitlement,
156+
data?.entitlementValue ? AIErrorReason.UserQuotaExceeded : AIErrorReason.NoEntitlement,
157+
new Error(`(${this.name}) ${status}.${code}: ${message}`),
158+
);
159+
} else if (code === 3) {
160+
throw new AIError(
161+
AIErrorReason.DeniedByOrganization,
156162
new Error(`(${this.name}) ${status}.${code}: ${message}`),
157163
);
158164
}
165+
throw new AIError(
166+
AIErrorReason.Unauthorized,
167+
new Error(`(${this.name}) ${status}.${code}: ${message}`),
168+
);
159169
throw new Error(`(${this.name}) ${status}.${code}: ${message}`);
160170
case 404:
161171
// CodeNotFound = "404.1"

src/plus/ai/openAICompatibleProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export abstract class OpenAICompatibleProvider<T extends AIProviders> implements
165165
): Promise<{ retry: true; maxInputTokens: number }> {
166166
if (rsp.status === 404) {
167167
throw new AIError(
168-
AIErrorReason.ModelUserUnauthorized,
168+
AIErrorReason.Unauthorized,
169169
new Error(`Your API key doesn't seem to have access to the selected '${model.id}' model`),
170170
);
171171
}

src/plus/ai/vscodeProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export class VSCodeAIProvider implements AIProvider<typeof provider.id> {
123123

124124
if (ex instanceof Error && 'code' in ex && ex.code === 'NoPermissions') {
125125
Logger.error(ex, scope, `User denied access to ${model.provider.name}`);
126-
throw new AIError(AIErrorReason.ModelUserDeniedAccess, ex);
126+
throw new AIError(AIErrorReason.DeniedByUser, ex);
127127
}
128128

129129
if (ex instanceof Error && 'cause' in ex && ex.cause instanceof Error) {

0 commit comments

Comments
 (0)