@llumiverse/drivers 1.0.0-dev.20260224.234313Z → 1.0.0-dev.20260331.080752Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/bedrock/converse.js +86 -12
- package/lib/cjs/bedrock/converse.js.map +1 -1
- package/lib/cjs/bedrock/index.js +208 -1
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/groq/index.js +7 -4
- package/lib/cjs/groq/index.js.map +1 -1
- package/lib/cjs/openai/index.js +457 -26
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/openai/openai_compatible.js +1 -0
- package/lib/cjs/openai/openai_compatible.js.map +1 -1
- package/lib/cjs/vertexai/index.js +42 -0
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +230 -2
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +261 -41
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/cjs/vertexai/models.js +1 -1
- package/lib/cjs/vertexai/models.js.map +1 -1
- package/lib/esm/bedrock/converse.js +80 -6
- package/lib/esm/bedrock/converse.js.map +1 -1
- package/lib/esm/bedrock/index.js +207 -2
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/groq/index.js +7 -4
- package/lib/esm/groq/index.js.map +1 -1
- package/lib/esm/openai/index.js +456 -27
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/openai/openai_compatible.js +1 -0
- package/lib/esm/openai/openai_compatible.js.map +1 -1
- package/lib/esm/vertexai/index.js +43 -1
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +229 -3
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +262 -43
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/vertexai/models.js +1 -1
- package/lib/esm/vertexai/models.js.map +1 -1
- package/lib/types/bedrock/converse.d.ts +1 -2
- package/lib/types/bedrock/converse.d.ts.map +1 -1
- package/lib/types/bedrock/index.d.ts +53 -1
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/openai/index.d.ts +96 -1
- package/lib/types/openai/index.d.ts.map +1 -1
- package/lib/types/openai/openai_compatible.d.ts +5 -0
- package/lib/types/openai/openai_compatible.d.ts.map +1 -1
- package/lib/types/openai/openai_format.d.ts +1 -1
- package/lib/types/vertexai/index.d.ts +11 -1
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/claude.d.ts +64 -1
- package/lib/types/vertexai/models/claude.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts +61 -1
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/lib/types/vertexai/models.d.ts +6 -1
- package/lib/types/vertexai/models.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/bedrock/converse.ts +85 -10
- package/src/bedrock/error-handling.test.ts +352 -0
- package/src/bedrock/index.ts +225 -1
- package/src/groq/index.ts +9 -4
- package/src/openai/error-handling.test.ts +567 -0
- package/src/openai/index.ts +505 -29
- package/src/openai/openai_compatible.ts +7 -0
- package/src/openai/openai_format.ts +1 -1
- package/src/vertexai/index.ts +56 -5
- package/src/vertexai/models/claude-error-handling.test.ts +432 -0
- package/src/vertexai/models/claude.ts +273 -7
- package/src/vertexai/models/gemini-error-handling.test.ts +353 -0
- package/src/vertexai/models/gemini.ts +304 -48
- package/src/vertexai/models.ts +7 -2
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { BaseOpenAIDriver } from './index.js';
|
|
3
|
+
import { LlumiverseError, LlumiverseErrorContext, Providers } from '@llumiverse/core';
|
|
4
|
+
import OpenAI from 'openai';
|
|
5
|
+
import {
|
|
6
|
+
APIError,
|
|
7
|
+
BadRequestError,
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
PermissionDeniedError,
|
|
10
|
+
NotFoundError,
|
|
11
|
+
ConflictError,
|
|
12
|
+
UnprocessableEntityError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
InternalServerError,
|
|
15
|
+
APIConnectionError,
|
|
16
|
+
APIConnectionTimeoutError,
|
|
17
|
+
LengthFinishReasonError,
|
|
18
|
+
ContentFilterFinishReasonError,
|
|
19
|
+
OpenAIError,
|
|
20
|
+
} from 'openai/error';
|
|
21
|
+
|
|
22
|
+
// Test implementation of BaseOpenAIDriver
|
|
23
|
+
class TestOpenAIDriver extends BaseOpenAIDriver {
|
|
24
|
+
provider: Providers.openai = Providers.openai;
|
|
25
|
+
service: OpenAI;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super({});
|
|
29
|
+
this.service = new OpenAI({ apiKey: 'test-key' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('BaseOpenAIDriver Error Handling', () => {
|
|
34
|
+
let driver: TestOpenAIDriver;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
driver = new TestOpenAIDriver();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('formatLlumiverseError', () => {
|
|
41
|
+
it('should handle BadRequestError with status code and parameter', () => {
|
|
42
|
+
const headers = new Headers();
|
|
43
|
+
headers.set('x-request-id', 'req_test_123');
|
|
44
|
+
|
|
45
|
+
const openaiError = new BadRequestError(
|
|
46
|
+
400,
|
|
47
|
+
{
|
|
48
|
+
message: "Invalid 'temperature': decimal above maximum value. Expected a value <= 2, but got 31313 instead.",
|
|
49
|
+
type: 'invalid_request_error',
|
|
50
|
+
param: 'temperature',
|
|
51
|
+
code: 'decimal_above_max_value'
|
|
52
|
+
},
|
|
53
|
+
"Invalid 'temperature': decimal above maximum value. Expected a value <= 2, but got 31313 instead.",
|
|
54
|
+
headers
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
58
|
+
provider: 'openai',
|
|
59
|
+
model: 'gpt-4',
|
|
60
|
+
operation: 'execute',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(error).toBeInstanceOf(LlumiverseError);
|
|
64
|
+
expect(error.code).toBe(400);
|
|
65
|
+
expect(error.message).toContain('[400]');
|
|
66
|
+
expect(error.message).toContain('temperature');
|
|
67
|
+
expect(error.message).toContain('decimal_above_max_value');
|
|
68
|
+
expect(error.message).toContain('req_test_123');
|
|
69
|
+
expect(error.name).toBe('BadRequestError');
|
|
70
|
+
expect(error.retryable).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle RateLimitError as retryable', () => {
|
|
74
|
+
const headers = new Headers();
|
|
75
|
+
const openaiError = new RateLimitError(
|
|
76
|
+
429,
|
|
77
|
+
{
|
|
78
|
+
message: 'Rate limit exceeded',
|
|
79
|
+
type: 'rate_limit_error',
|
|
80
|
+
code: 'rate_limit_exceeded'
|
|
81
|
+
},
|
|
82
|
+
'Rate limit exceeded',
|
|
83
|
+
headers
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
87
|
+
provider: 'openai',
|
|
88
|
+
model: 'gpt-4',
|
|
89
|
+
operation: 'execute',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(error.code).toBe(429);
|
|
93
|
+
expect(error.retryable).toBe(true);
|
|
94
|
+
expect(error.name).toBe('RateLimitError');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle InternalServerError as retryable', () => {
|
|
98
|
+
const openaiError = new InternalServerError(
|
|
99
|
+
500,
|
|
100
|
+
{
|
|
101
|
+
message: 'Internal server error',
|
|
102
|
+
type: 'server_error',
|
|
103
|
+
code: 'server_error'
|
|
104
|
+
},
|
|
105
|
+
'Internal server error',
|
|
106
|
+
new Headers()
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
110
|
+
provider: 'openai',
|
|
111
|
+
model: 'gpt-4',
|
|
112
|
+
operation: 'execute',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(error.code).toBe(500);
|
|
116
|
+
expect(error.retryable).toBe(true);
|
|
117
|
+
expect(error.name).toBe('InternalServerError');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle AuthenticationError as not retryable', () => {
|
|
121
|
+
const openaiError = new AuthenticationError(
|
|
122
|
+
401,
|
|
123
|
+
{
|
|
124
|
+
message: 'Invalid API key',
|
|
125
|
+
type: 'authentication_error',
|
|
126
|
+
code: 'invalid_api_key'
|
|
127
|
+
},
|
|
128
|
+
'Invalid API key',
|
|
129
|
+
new Headers()
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
133
|
+
provider: 'openai',
|
|
134
|
+
model: 'gpt-4',
|
|
135
|
+
operation: 'execute',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(error.code).toBe(401);
|
|
139
|
+
expect(error.retryable).toBe(false);
|
|
140
|
+
expect(error.name).toBe('AuthenticationError');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle PermissionDeniedError as not retryable', () => {
|
|
144
|
+
const openaiError = new PermissionDeniedError(
|
|
145
|
+
403,
|
|
146
|
+
{
|
|
147
|
+
message: 'Insufficient permissions',
|
|
148
|
+
type: 'permission_error'
|
|
149
|
+
},
|
|
150
|
+
'Insufficient permissions',
|
|
151
|
+
new Headers()
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
155
|
+
provider: 'openai',
|
|
156
|
+
model: 'gpt-4',
|
|
157
|
+
operation: 'execute',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(error.code).toBe(403);
|
|
161
|
+
expect(error.retryable).toBe(false);
|
|
162
|
+
expect(error.name).toBe('PermissionDeniedError');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle NotFoundError as not retryable', () => {
|
|
166
|
+
const openaiError = new NotFoundError(
|
|
167
|
+
404,
|
|
168
|
+
{
|
|
169
|
+
message: 'Model not found',
|
|
170
|
+
type: 'invalid_request_error',
|
|
171
|
+
code: 'model_not_found'
|
|
172
|
+
},
|
|
173
|
+
'Model not found',
|
|
174
|
+
new Headers()
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
178
|
+
provider: 'openai',
|
|
179
|
+
model: 'gpt-invalid',
|
|
180
|
+
operation: 'execute',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(error.code).toBe(404);
|
|
184
|
+
expect(error.retryable).toBe(false);
|
|
185
|
+
expect(error.name).toBe('NotFoundError');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should handle ConflictError as not retryable', () => {
|
|
189
|
+
const openaiError = new ConflictError(
|
|
190
|
+
409,
|
|
191
|
+
{
|
|
192
|
+
message: 'Resource conflict',
|
|
193
|
+
type: 'conflict_error'
|
|
194
|
+
},
|
|
195
|
+
'Resource conflict',
|
|
196
|
+
new Headers()
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
200
|
+
provider: 'openai',
|
|
201
|
+
model: 'gpt-4',
|
|
202
|
+
operation: 'execute',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(error.code).toBe(409);
|
|
206
|
+
expect(error.retryable).toBe(false);
|
|
207
|
+
expect(error.name).toBe('ConflictError');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle UnprocessableEntityError as not retryable', () => {
|
|
211
|
+
const openaiError = new UnprocessableEntityError(
|
|
212
|
+
422,
|
|
213
|
+
{
|
|
214
|
+
message: 'Validation failed',
|
|
215
|
+
type: 'validation_error'
|
|
216
|
+
},
|
|
217
|
+
'Validation failed',
|
|
218
|
+
new Headers()
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
222
|
+
provider: 'openai',
|
|
223
|
+
model: 'gpt-4',
|
|
224
|
+
operation: 'execute',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(error.code).toBe(422);
|
|
228
|
+
expect(error.retryable).toBe(false);
|
|
229
|
+
expect(error.name).toBe('UnprocessableEntityError');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should handle APIConnectionTimeoutError as retryable', () => {
|
|
233
|
+
const openaiError = new APIConnectionTimeoutError({ message: 'Request timed out' });
|
|
234
|
+
|
|
235
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
236
|
+
provider: 'openai',
|
|
237
|
+
model: 'gpt-4',
|
|
238
|
+
operation: 'execute',
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(error.code).toBeUndefined();
|
|
242
|
+
expect(error.retryable).toBe(true);
|
|
243
|
+
expect(error.name).toBe('APIConnectionTimeoutError');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle APIConnectionError as retryable', () => {
|
|
247
|
+
const openaiError = new APIConnectionError({ message: 'Connection failed' });
|
|
248
|
+
|
|
249
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
250
|
+
provider: 'openai',
|
|
251
|
+
model: 'gpt-4',
|
|
252
|
+
operation: 'execute',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(error.code).toBeUndefined();
|
|
256
|
+
expect(error.retryable).toBe(true);
|
|
257
|
+
expect(error.name).toBe('APIConnectionError');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle LengthFinishReasonError as not retryable', () => {
|
|
261
|
+
const openaiError = new LengthFinishReasonError();
|
|
262
|
+
|
|
263
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
264
|
+
provider: 'openai',
|
|
265
|
+
model: 'gpt-4',
|
|
266
|
+
operation: 'execute',
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(error.retryable).toBe(false);
|
|
270
|
+
expect(error.name).toBe('LengthFinishReasonError');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should handle ContentFilterFinishReasonError as not retryable', () => {
|
|
274
|
+
const openaiError = new ContentFilterFinishReasonError();
|
|
275
|
+
|
|
276
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
277
|
+
provider: 'openai',
|
|
278
|
+
model: 'gpt-4',
|
|
279
|
+
operation: 'execute',
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(error.retryable).toBe(false);
|
|
283
|
+
expect(error.name).toBe('ContentFilterFinishReasonError');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should include error code in message', () => {
|
|
287
|
+
const headers = new Headers();
|
|
288
|
+
const openaiError = new BadRequestError(
|
|
289
|
+
400,
|
|
290
|
+
{
|
|
291
|
+
message: 'Invalid parameter',
|
|
292
|
+
code: 'invalid_parameter'
|
|
293
|
+
},
|
|
294
|
+
'Invalid parameter',
|
|
295
|
+
headers
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
299
|
+
provider: 'openai',
|
|
300
|
+
model: 'gpt-4',
|
|
301
|
+
operation: 'execute',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(error.message).toContain('invalid_parameter');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should include parameter in message when available', () => {
|
|
308
|
+
const headers = new Headers();
|
|
309
|
+
const openaiError = new BadRequestError(
|
|
310
|
+
400,
|
|
311
|
+
{
|
|
312
|
+
message: 'Invalid value',
|
|
313
|
+
param: 'max_tokens',
|
|
314
|
+
code: 'invalid_value'
|
|
315
|
+
},
|
|
316
|
+
'Invalid value',
|
|
317
|
+
headers
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
321
|
+
provider: 'openai',
|
|
322
|
+
model: 'gpt-4',
|
|
323
|
+
operation: 'execute',
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(error.message).toContain('max_tokens');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should include request ID when available', () => {
|
|
330
|
+
const headers = new Headers();
|
|
331
|
+
headers.set('x-request-id', 'req_xyz789');
|
|
332
|
+
|
|
333
|
+
const openaiError = new BadRequestError(
|
|
334
|
+
400,
|
|
335
|
+
{ message: 'Bad request' },
|
|
336
|
+
'Bad request',
|
|
337
|
+
headers
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
341
|
+
provider: 'openai',
|
|
342
|
+
model: 'gpt-4',
|
|
343
|
+
operation: 'execute',
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(error.message).toContain('req_xyz789');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should throw for non-OpenAI errors', () => {
|
|
350
|
+
const regularError = new Error('Regular error');
|
|
351
|
+
|
|
352
|
+
expect(() => {
|
|
353
|
+
driver.formatLlumiverseError( regularError, {
|
|
354
|
+
provider: 'openai',
|
|
355
|
+
model: 'gpt-4',
|
|
356
|
+
operation: 'execute',
|
|
357
|
+
});
|
|
358
|
+
}).toThrow('Regular error');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should preserve original error for debugging', () => {
|
|
362
|
+
const openaiError = new BadRequestError(
|
|
363
|
+
400,
|
|
364
|
+
{ message: 'Test error' },
|
|
365
|
+
'Test error',
|
|
366
|
+
new Headers()
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
370
|
+
provider: 'openai',
|
|
371
|
+
model: 'gpt-4',
|
|
372
|
+
operation: 'execute',
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(error.originalError).toBe(openaiError);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('isOpenAIErrorRetryable', () => {
|
|
380
|
+
it('should classify retryable error types correctly', () => {
|
|
381
|
+
const retryableErrors = [
|
|
382
|
+
new RateLimitError(429, {}, 'Rate limit', new Headers()),
|
|
383
|
+
new InternalServerError(500, {}, 'Server error', new Headers()),
|
|
384
|
+
new APIConnectionTimeoutError({ message: 'Timeout' }),
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
for (const error of retryableErrors) {
|
|
388
|
+
const result = (driver as any).isOpenAIErrorRetryable(error, error.status, null, undefined);
|
|
389
|
+
expect(result, `${error.constructor.name} should be retryable`).toBe(true);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should classify non-retryable error types correctly', () => {
|
|
394
|
+
const nonRetryableErrors = [
|
|
395
|
+
new BadRequestError(400, {}, 'Bad request', new Headers()),
|
|
396
|
+
new AuthenticationError(401, {}, 'Auth error', new Headers()),
|
|
397
|
+
new PermissionDeniedError(403, {}, 'Permission denied', new Headers()),
|
|
398
|
+
new NotFoundError(404, {}, 'Not found', new Headers()),
|
|
399
|
+
new ConflictError(409, {}, 'Conflict', new Headers()),
|
|
400
|
+
new UnprocessableEntityError(422, {}, 'Validation error', new Headers()),
|
|
401
|
+
new LengthFinishReasonError(),
|
|
402
|
+
new ContentFilterFinishReasonError(),
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
for (const error of nonRetryableErrors) {
|
|
406
|
+
const status = 'status' in error ? (error as any).status : undefined;
|
|
407
|
+
const result = (driver as any).isOpenAIErrorRetryable(error, status, null, undefined);
|
|
408
|
+
expect(result, `${error.constructor.name} should not be retryable`).toBe(false);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should classify retryable error codes correctly', () => {
|
|
413
|
+
const apiError = new APIError(undefined as any, {}, 'Error', new Headers());
|
|
414
|
+
|
|
415
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'timeout', undefined)).toBe(true);
|
|
416
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'server_error', undefined)).toBe(true);
|
|
417
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'service_unavailable', undefined)).toBe(true);
|
|
418
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'rate_limit_exceeded', undefined)).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should classify non-retryable error codes correctly', () => {
|
|
422
|
+
const apiError = new APIError(undefined as any, {}, 'Error', new Headers());
|
|
423
|
+
|
|
424
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'invalid_api_key', undefined)).toBe(false);
|
|
425
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'invalid_request_error', undefined)).toBe(false);
|
|
426
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'model_not_found', undefined)).toBe(false);
|
|
427
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'insufficient_quota', undefined)).toBe(false);
|
|
428
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'invalid_model', undefined)).toBe(false);
|
|
429
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, 'invalid_parameter', undefined)).toBe(false);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should classify error types correctly', () => {
|
|
433
|
+
const apiError = new APIError(undefined as any, {}, 'Error', new Headers());
|
|
434
|
+
|
|
435
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, null, 'invalid_request_error')).toBe(false);
|
|
436
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, null, 'authentication_error')).toBe(false);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should use HTTP status codes when available', () => {
|
|
440
|
+
const apiError = new APIError(429, {}, 'Too many requests', new Headers());
|
|
441
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, 429, null, undefined)).toBe(true);
|
|
442
|
+
|
|
443
|
+
const apiError2 = new APIError(408, {}, 'Request timeout', new Headers());
|
|
444
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError2, 408, null, undefined)).toBe(true);
|
|
445
|
+
|
|
446
|
+
const apiError3 = new APIError(502, {}, 'Bad gateway', new Headers());
|
|
447
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError3, 502, null, undefined)).toBe(true);
|
|
448
|
+
|
|
449
|
+
const apiError4 = new APIError(503, {}, 'Service unavailable', new Headers());
|
|
450
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError4, 503, null, undefined)).toBe(true);
|
|
451
|
+
|
|
452
|
+
const apiError5 = new APIError(504, {}, 'Gateway timeout', new Headers());
|
|
453
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError5, 504, null, undefined)).toBe(true);
|
|
454
|
+
|
|
455
|
+
const apiError6 = new APIError(529, {}, 'Overloaded', new Headers());
|
|
456
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError6, 529, null, undefined)).toBe(true);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should classify 4xx as non-retryable', () => {
|
|
460
|
+
const apiError = new APIError(400, {}, 'Bad request', new Headers());
|
|
461
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, 400, null, undefined)).toBe(false);
|
|
462
|
+
|
|
463
|
+
const apiError2 = new APIError(403, {}, 'Forbidden', new Headers());
|
|
464
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError2, 403, null, undefined)).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should classify 5xx as retryable', () => {
|
|
468
|
+
const apiError = new APIError(500, {}, 'Internal error', new Headers());
|
|
469
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, 500, null, undefined)).toBe(true);
|
|
470
|
+
|
|
471
|
+
const apiError2 = new APIError(502, {}, 'Bad gateway', new Headers());
|
|
472
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError2, 502, null, undefined)).toBe(true);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should classify APIConnectionError (non-timeout) as retryable', () => {
|
|
476
|
+
const connectionError = new APIConnectionError({ message: 'Network failure' });
|
|
477
|
+
expect((driver as any).isOpenAIErrorRetryable(connectionError, undefined, null, undefined)).toBe(true);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should return undefined for unknown errors', () => {
|
|
481
|
+
const apiError = new APIError(undefined as any, {}, 'Unknown error', undefined as any);
|
|
482
|
+
expect((driver as any).isOpenAIErrorRetryable(apiError, undefined, null, undefined)).toBeUndefined();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('Cross-provider compatibility', () => {
|
|
487
|
+
it('should work with Azure OpenAI provider context', () => {
|
|
488
|
+
const openaiError = new RateLimitError(
|
|
489
|
+
429,
|
|
490
|
+
{ message: 'Rate limit exceeded' },
|
|
491
|
+
'Rate limit exceeded',
|
|
492
|
+
new Headers()
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
496
|
+
provider: 'azure_openai',
|
|
497
|
+
model: 'gpt-4',
|
|
498
|
+
operation: 'execute',
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
expect(error).toBeInstanceOf(LlumiverseError);
|
|
502
|
+
expect(error.code).toBe(429);
|
|
503
|
+
expect(error.retryable).toBe(true);
|
|
504
|
+
expect(error.message).toContain('[azure_openai]');
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should work with xAI provider context', () => {
|
|
508
|
+
const openaiError = new BadRequestError(
|
|
509
|
+
400,
|
|
510
|
+
{ message: 'Invalid parameter' },
|
|
511
|
+
'Invalid parameter',
|
|
512
|
+
new Headers()
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
516
|
+
provider: 'xai',
|
|
517
|
+
model: 'grok-2',
|
|
518
|
+
operation: 'execute',
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
expect(error).toBeInstanceOf(LlumiverseError);
|
|
522
|
+
expect(error.code).toBe(400);
|
|
523
|
+
expect(error.retryable).toBe(false);
|
|
524
|
+
expect(error.message).toContain('[xai]');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should work with Azure Foundry provider context', () => {
|
|
528
|
+
const openaiError = new InternalServerError(
|
|
529
|
+
500,
|
|
530
|
+
{ message: 'Server error' },
|
|
531
|
+
'Server error',
|
|
532
|
+
new Headers()
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
536
|
+
provider: 'azure_foundry',
|
|
537
|
+
model: 'gpt-4',
|
|
538
|
+
operation: 'execute',
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
expect(error).toBeInstanceOf(LlumiverseError);
|
|
542
|
+
expect(error.code).toBe(500);
|
|
543
|
+
expect(error.retryable).toBe(true);
|
|
544
|
+
expect(error.message).toContain('[azure_foundry]');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should work with OpenAI-compatible provider context', () => {
|
|
548
|
+
const openaiError = new AuthenticationError(
|
|
549
|
+
401,
|
|
550
|
+
{ message: 'Invalid API key' },
|
|
551
|
+
'Invalid API key',
|
|
552
|
+
new Headers()
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
const error = driver.formatLlumiverseError( openaiError, {
|
|
556
|
+
provider: 'openai_compatible',
|
|
557
|
+
model: 'custom-model',
|
|
558
|
+
operation: 'execute',
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
expect(error).toBeInstanceOf(LlumiverseError);
|
|
562
|
+
expect(error.code).toBe(401);
|
|
563
|
+
expect(error.retryable).toBe(false);
|
|
564
|
+
expect(error.message).toContain('[openai_compatible]');
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
});
|