Skip to content

Commit 7c0541a

Browse files
committed
Improves AI error messages more (#4227)
1 parent 45bbbf0 commit 7c0541a

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
@@ -916,26 +916,34 @@ export class AIProviderService implements Disposable {
916916

917917
return result;
918918
} catch (ex) {
919-
this.container.telemetry.sendEvent(
920-
telementry.key,
921-
{
922-
...telementry.data,
923-
duration: Date.now() - start,
924-
...(ex instanceof CancellationError
925-
? { 'failed.reason': 'user-cancelled' }
926-
: { 'failed.reason': 'error', 'failed.error': String(ex) }),
927-
},
928-
source,
929-
);
919+
if (ex instanceof CancellationError) {
920+
this.container.telemetry.sendEvent(
921+
telementry.key,
922+
{ ...telementry.data, duration: Date.now() - start, 'failed.reason': 'user-cancelled' },
923+
source,
924+
);
930925

931-
if (ex instanceof CancellationError) return undefined;
926+
return undefined;
927+
}
932928
if (ex instanceof AIError) {
929+
this.container.telemetry.sendEvent(
930+
telementry.key,
931+
{
932+
...telementry.data,
933+
duration: Date.now() - start,
934+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
935+
'failed.error': String(ex),
936+
'failed.error.detail': String(ex.original),
937+
},
938+
source,
939+
);
940+
933941
switch (ex.reason) {
934942
case AIErrorReason.NoRequestData:
935943
void window.showErrorMessage(ex.message);
936944
return undefined;
937945

938-
case AIErrorReason.Entitlement: {
946+
case AIErrorReason.NoEntitlement: {
939947
const sub = await this.container.subscription.getSubscription();
940948

941949
const plan = isSubscriptionPaid(sub)
@@ -1021,7 +1029,7 @@ export class AIProviderService implements Disposable {
10211029
}
10221030
return undefined;
10231031
}
1024-
case AIErrorReason.ModelUserUnauthorized: {
1032+
case AIErrorReason.Unauthorized: {
10251033
const switchModel: MessageItem = { title: 'Switch Model' };
10261034
const result = await window.showErrorMessage(
10271035
'You do not have access to the selected model. Please select a different model and try again.',
@@ -1032,7 +1040,7 @@ export class AIProviderService implements Disposable {
10321040
}
10331041
return undefined;
10341042
}
1035-
case AIErrorReason.ModelUserDeniedAccess: {
1043+
case AIErrorReason.DeniedByUser: {
10361044
const switchModel: MessageItem = { title: 'Switch Model' };
10371045
const result = await window.showErrorMessage(
10381046
'You have denied access to the selected model. Please provide access or select a different model, and then try again.',
@@ -1048,6 +1056,16 @@ export class AIProviderService implements Disposable {
10481056
return undefined;
10491057
}
10501058

1059+
this.container.telemetry.sendEvent(
1060+
telementry.key,
1061+
{
1062+
...telementry.data,
1063+
duration: Date.now() - start,
1064+
'failed.error': String(ex),
1065+
'failed.error.detail': ex.original ? String(ex.original) : undefined,
1066+
},
1067+
source,
1068+
);
10511069
throw ex;
10521070
}
10531071
}

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)