@bernierllc/ai-provider-openai 1.0.0 → 1.0.2
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/CHANGELOG.md +17 -0
- package/__tests__/OpenAIProvider.test.ts +32 -26
- package/__tests__/openai-specific.test.ts +13 -9
- package/jest.config.cjs +2 -1
- package/package.json +18 -14
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @bernierllc/ai-provider-openai
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`73c1d1e`](https://github.com/bernierllc/tools/commit/73c1d1e46114cf2ccfd5b74b83743361247f4502)]:
|
|
8
|
+
- @bernierllc/logger@1.1.0
|
|
9
|
+
- @bernierllc/ai-provider-core@1.0.3
|
|
10
|
+
|
|
11
|
+
## 1.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`24899fa`](https://github.com/bernierllc/tools/commit/24899fa4eaf80ae329f7bd74483cf97a132f5d4c)]:
|
|
16
|
+
- @bernierllc/logger@1.0.4
|
|
17
|
+
- @bernierllc/ai-provider-core@1.0.2
|
|
@@ -27,24 +27,30 @@ describe('OpenAIProvider', () => {
|
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
jest.clearAllMocks();
|
|
29
29
|
|
|
30
|
-
// Setup mock OpenAI client
|
|
30
|
+
// Setup mock OpenAI client with properly typed Jest mocks
|
|
31
|
+
const mockChatCompletionsCreate = jest.fn() as jest.Mock;
|
|
32
|
+
const mockEmbeddingsCreate = jest.fn() as jest.Mock;
|
|
33
|
+
const mockModerationsCreate = jest.fn() as jest.Mock;
|
|
34
|
+
const mockModelsList = jest.fn() as jest.Mock;
|
|
35
|
+
const mockModelsRetrieve = jest.fn() as jest.Mock;
|
|
36
|
+
|
|
31
37
|
mockClient = {
|
|
32
38
|
chat: {
|
|
33
39
|
completions: {
|
|
34
|
-
create:
|
|
40
|
+
create: mockChatCompletionsCreate as any
|
|
35
41
|
}
|
|
36
42
|
},
|
|
37
43
|
embeddings: {
|
|
38
|
-
create:
|
|
44
|
+
create: mockEmbeddingsCreate as any
|
|
39
45
|
},
|
|
40
46
|
moderations: {
|
|
41
|
-
create:
|
|
47
|
+
create: mockModerationsCreate as any
|
|
42
48
|
},
|
|
43
49
|
models: {
|
|
44
|
-
list:
|
|
45
|
-
retrieve:
|
|
50
|
+
list: mockModelsList as any,
|
|
51
|
+
retrieve: mockModelsRetrieve as any
|
|
46
52
|
}
|
|
47
|
-
} as
|
|
53
|
+
} as unknown as jest.Mocked<OpenAI>;
|
|
48
54
|
|
|
49
55
|
(OpenAI as jest.MockedClass<typeof OpenAI>).mockImplementation(() => mockClient);
|
|
50
56
|
|
|
@@ -110,7 +116,7 @@ describe('OpenAIProvider', () => {
|
|
|
110
116
|
}
|
|
111
117
|
};
|
|
112
118
|
|
|
113
|
-
mockClient.chat.completions.create.mockResolvedValue(mockResponse as any);
|
|
119
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
114
120
|
|
|
115
121
|
const result = await provider.complete({
|
|
116
122
|
messages: [
|
|
@@ -145,7 +151,7 @@ describe('OpenAIProvider', () => {
|
|
|
145
151
|
]
|
|
146
152
|
};
|
|
147
153
|
|
|
148
|
-
mockClient.chat.completions.create.mockResolvedValue(mockResponse as any);
|
|
154
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
149
155
|
|
|
150
156
|
const result = await provider.complete({
|
|
151
157
|
messages: [{ role: 'user', content: 'Hello' }]
|
|
@@ -156,7 +162,7 @@ describe('OpenAIProvider', () => {
|
|
|
156
162
|
});
|
|
157
163
|
|
|
158
164
|
it('should pass all request parameters to OpenAI', async () => {
|
|
159
|
-
mockClient.chat.completions.create.mockResolvedValue({
|
|
165
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue({
|
|
160
166
|
choices: [{ message: { content: 'test' }, finish_reason: 'stop' }]
|
|
161
167
|
} as any);
|
|
162
168
|
|
|
@@ -186,7 +192,7 @@ describe('OpenAIProvider', () => {
|
|
|
186
192
|
});
|
|
187
193
|
|
|
188
194
|
it('should use default model if not specified', async () => {
|
|
189
|
-
mockClient.chat.completions.create.mockResolvedValue({
|
|
195
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue({
|
|
190
196
|
choices: [{ message: { content: 'test' }, finish_reason: 'stop' }]
|
|
191
197
|
} as any);
|
|
192
198
|
|
|
@@ -225,7 +231,7 @@ describe('OpenAIProvider', () => {
|
|
|
225
231
|
{}
|
|
226
232
|
);
|
|
227
233
|
|
|
228
|
-
mockClient.chat.completions.create.mockRejectedValue(apiError);
|
|
234
|
+
(mockClient.chat.completions.create as jest.Mock).mockRejectedValue(apiError);
|
|
229
235
|
|
|
230
236
|
const result = await provider.complete({
|
|
231
237
|
messages: [{ role: 'user', content: 'Test' }]
|
|
@@ -264,7 +270,7 @@ describe('OpenAIProvider', () => {
|
|
|
264
270
|
}
|
|
265
271
|
];
|
|
266
272
|
|
|
267
|
-
mockClient.chat.completions.create.mockResolvedValue({
|
|
273
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue({
|
|
268
274
|
async *[Symbol.asyncIterator]() {
|
|
269
275
|
for (const chunk of mockChunks) {
|
|
270
276
|
yield chunk;
|
|
@@ -297,7 +303,7 @@ describe('OpenAIProvider', () => {
|
|
|
297
303
|
{ choices: [{ delta: { content: 'test' }, finish_reason: 'stop' }] }
|
|
298
304
|
];
|
|
299
305
|
|
|
300
|
-
mockClient.chat.completions.create.mockResolvedValue({
|
|
306
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue({
|
|
301
307
|
async *[Symbol.asyncIterator]() {
|
|
302
308
|
for (const chunk of mockChunks) {
|
|
303
309
|
yield chunk;
|
|
@@ -339,7 +345,7 @@ describe('OpenAIProvider', () => {
|
|
|
339
345
|
}
|
|
340
346
|
};
|
|
341
347
|
|
|
342
|
-
mockClient.embeddings.create.mockResolvedValue(mockResponse as any);
|
|
348
|
+
(mockClient.embeddings.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
343
349
|
|
|
344
350
|
const result = await provider.generateEmbeddings({
|
|
345
351
|
input: ['text 1', 'text 2']
|
|
@@ -358,7 +364,7 @@ describe('OpenAIProvider', () => {
|
|
|
358
364
|
});
|
|
359
365
|
|
|
360
366
|
it('should use default embedding model if not specified', async () => {
|
|
361
|
-
mockClient.embeddings.create.mockResolvedValue({
|
|
367
|
+
(mockClient.embeddings.create as jest.Mock).mockResolvedValue({
|
|
362
368
|
data: [{ embedding: [1, 2, 3] }],
|
|
363
369
|
usage: { prompt_tokens: 5, total_tokens: 5 }
|
|
364
370
|
} as any);
|
|
@@ -375,7 +381,7 @@ describe('OpenAIProvider', () => {
|
|
|
375
381
|
});
|
|
376
382
|
|
|
377
383
|
it('should handle API errors', async () => {
|
|
378
|
-
mockClient.embeddings.create.mockRejectedValue(new Error('API Error'));
|
|
384
|
+
(mockClient.embeddings.create as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
379
385
|
|
|
380
386
|
const result = await provider.generateEmbeddings({
|
|
381
387
|
input: 'test'
|
|
@@ -422,7 +428,7 @@ describe('OpenAIProvider', () => {
|
|
|
422
428
|
]
|
|
423
429
|
};
|
|
424
430
|
|
|
425
|
-
mockClient.moderations.create.mockResolvedValue(mockResponse as any);
|
|
431
|
+
(mockClient.moderations.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
426
432
|
|
|
427
433
|
const result = await provider.moderate('test content');
|
|
428
434
|
|
|
@@ -443,7 +449,7 @@ describe('OpenAIProvider', () => {
|
|
|
443
449
|
]
|
|
444
450
|
};
|
|
445
451
|
|
|
446
|
-
mockClient.moderations.create.mockResolvedValue(mockResponse as any);
|
|
452
|
+
(mockClient.moderations.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
447
453
|
|
|
448
454
|
const result = await provider.moderate('clean content');
|
|
449
455
|
|
|
@@ -452,7 +458,7 @@ describe('OpenAIProvider', () => {
|
|
|
452
458
|
});
|
|
453
459
|
|
|
454
460
|
it('should handle API errors', async () => {
|
|
455
|
-
mockClient.moderations.create.mockRejectedValue(new Error('API Error'));
|
|
461
|
+
(mockClient.moderations.create as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
456
462
|
|
|
457
463
|
const result = await provider.moderate('test');
|
|
458
464
|
|
|
@@ -474,7 +480,7 @@ describe('OpenAIProvider', () => {
|
|
|
474
480
|
]
|
|
475
481
|
};
|
|
476
482
|
|
|
477
|
-
mockClient.models.list.mockResolvedValue(mockResponse as any);
|
|
483
|
+
(mockClient.models.list as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
478
484
|
|
|
479
485
|
const models = await provider.getAvailableModels();
|
|
480
486
|
|
|
@@ -486,7 +492,7 @@ describe('OpenAIProvider', () => {
|
|
|
486
492
|
});
|
|
487
493
|
|
|
488
494
|
it('should return cached models if API fails', async () => {
|
|
489
|
-
mockClient.models.list.mockRejectedValue(new Error('API Error'));
|
|
495
|
+
(mockClient.models.list as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
490
496
|
|
|
491
497
|
const models = await provider.getAvailableModels();
|
|
492
498
|
|
|
@@ -497,7 +503,7 @@ describe('OpenAIProvider', () => {
|
|
|
497
503
|
|
|
498
504
|
describe('checkHealth()', () => {
|
|
499
505
|
it('should return healthy status when API is accessible', async () => {
|
|
500
|
-
mockClient.models.retrieve.mockResolvedValue({ id: 'gpt-3.5-turbo' } as any);
|
|
506
|
+
(mockClient.models.retrieve as jest.Mock).mockResolvedValue({ id: 'gpt-3.5-turbo' } as any);
|
|
501
507
|
|
|
502
508
|
const health = await provider.checkHealth();
|
|
503
509
|
|
|
@@ -507,7 +513,7 @@ describe('OpenAIProvider', () => {
|
|
|
507
513
|
});
|
|
508
514
|
|
|
509
515
|
it('should return unavailable status when API fails', async () => {
|
|
510
|
-
mockClient.models.retrieve.mockRejectedValue(new Error('Connection failed'));
|
|
516
|
+
(mockClient.models.retrieve as jest.Mock).mockRejectedValue(new Error('Connection failed'));
|
|
511
517
|
|
|
512
518
|
const health = await provider.checkHealth();
|
|
513
519
|
|
|
@@ -519,7 +525,7 @@ describe('OpenAIProvider', () => {
|
|
|
519
525
|
|
|
520
526
|
describe('isAvailable()', () => {
|
|
521
527
|
it('should return true when provider is healthy', async () => {
|
|
522
|
-
mockClient.models.retrieve.mockResolvedValue({ id: 'gpt-3.5-turbo' } as any);
|
|
528
|
+
(mockClient.models.retrieve as jest.Mock).mockResolvedValue({ id: 'gpt-3.5-turbo' } as any);
|
|
523
529
|
|
|
524
530
|
const available = await provider.isAvailable();
|
|
525
531
|
|
|
@@ -527,7 +533,7 @@ describe('OpenAIProvider', () => {
|
|
|
527
533
|
});
|
|
528
534
|
|
|
529
535
|
it('should return false when provider is unavailable', async () => {
|
|
530
|
-
mockClient.models.retrieve.mockRejectedValue(new Error('API Error'));
|
|
536
|
+
(mockClient.models.retrieve as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
531
537
|
|
|
532
538
|
const available = await provider.isAvailable();
|
|
533
539
|
|
|
@@ -25,13 +25,16 @@ describe('OpenAI-Specific Features', () => {
|
|
|
25
25
|
beforeEach(() => {
|
|
26
26
|
jest.clearAllMocks();
|
|
27
27
|
|
|
28
|
+
// Setup mock OpenAI client with properly typed Jest mocks
|
|
29
|
+
const mockChatCompletionsCreate = jest.fn() as jest.Mock;
|
|
30
|
+
|
|
28
31
|
mockClient = {
|
|
29
32
|
chat: {
|
|
30
33
|
completions: {
|
|
31
|
-
create:
|
|
34
|
+
create: mockChatCompletionsCreate as any
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
|
-
} as
|
|
37
|
+
} as unknown as jest.Mocked<OpenAI>;
|
|
35
38
|
|
|
36
39
|
(OpenAI as jest.MockedClass<typeof OpenAI>).mockImplementation(() => mockClient);
|
|
37
40
|
|
|
@@ -79,7 +82,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
79
82
|
}
|
|
80
83
|
};
|
|
81
84
|
|
|
82
|
-
mockClient.chat.completions.create.mockResolvedValue(mockResponse as any);
|
|
85
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
83
86
|
|
|
84
87
|
const result = await provider.completionWithFunctions({
|
|
85
88
|
messages: [{ role: 'user', content: 'What is the weather in San Francisco?' }],
|
|
@@ -132,7 +135,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
132
135
|
}
|
|
133
136
|
};
|
|
134
137
|
|
|
135
|
-
mockClient.chat.completions.create.mockResolvedValue(mockResponse as any);
|
|
138
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
136
139
|
|
|
137
140
|
const result = await provider.completionWithFunctions({
|
|
138
141
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
@@ -185,7 +188,8 @@ describe('OpenAI-Specific Features', () => {
|
|
|
185
188
|
});
|
|
186
189
|
|
|
187
190
|
expect(result.success).toBe(true);
|
|
188
|
-
expect(result.metadata?.functionCall
|
|
191
|
+
expect(result.metadata?.functionCall).toBeDefined();
|
|
192
|
+
expect((result.metadata?.functionCall as any)?.name).toBe('get_time');
|
|
189
193
|
});
|
|
190
194
|
|
|
191
195
|
it('should handle API errors', async () => {
|
|
@@ -200,7 +204,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
200
204
|
}
|
|
201
205
|
];
|
|
202
206
|
|
|
203
|
-
mockClient.chat.completions.create.mockRejectedValue(new Error('API Error'));
|
|
207
|
+
(mockClient.chat.completions.create as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
204
208
|
|
|
205
209
|
const result = await provider.completionWithFunctions({
|
|
206
210
|
messages: [{ role: 'user', content: 'Test' }],
|
|
@@ -232,7 +236,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
232
236
|
}
|
|
233
237
|
};
|
|
234
238
|
|
|
235
|
-
mockClient.chat.completions.create.mockResolvedValue(mockResponse as any);
|
|
239
|
+
(mockClient.chat.completions.create as jest.Mock).mockResolvedValue(mockResponse as any);
|
|
236
240
|
|
|
237
241
|
const result = await provider.analyzeImage(
|
|
238
242
|
'https://example.com/sunset.jpg',
|
|
@@ -300,7 +304,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
300
304
|
{}
|
|
301
305
|
);
|
|
302
306
|
|
|
303
|
-
mockClient.chat.completions.create.mockRejectedValue(apiError);
|
|
307
|
+
(mockClient.chat.completions.create as jest.Mock).mockRejectedValue(apiError);
|
|
304
308
|
|
|
305
309
|
const result = await provider.analyzeImage(
|
|
306
310
|
'invalid-url',
|
|
@@ -319,7 +323,7 @@ describe('OpenAI-Specific Features', () => {
|
|
|
319
323
|
{}
|
|
320
324
|
);
|
|
321
325
|
|
|
322
|
-
mockClient.chat.completions.create.mockRejectedValue(rateLimitError);
|
|
326
|
+
(mockClient.chat.completions.create as jest.Mock).mockRejectedValue(rateLimitError);
|
|
323
327
|
|
|
324
328
|
const result = await provider.analyzeImage(
|
|
325
329
|
'https://example.com/image.jpg',
|
package/jest.config.cjs
CHANGED
|
@@ -8,7 +8,8 @@ Redistribution or use in other products or commercial offerings is not permitted
|
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
preset: 'ts-jest',
|
|
11
|
-
|
|
11
|
+
// Use fixed environment to handle Node.js v25+ localStorage issue
|
|
12
|
+
testEnvironment: '<rootDir>/../../../jest-environment-node-fixed.cjs',
|
|
12
13
|
roots: ['<rootDir>/__tests__'],
|
|
13
14
|
testMatch: ['**/__tests__/**/*.test.ts'],
|
|
14
15
|
collectCoverageFrom: [
|
package/package.json
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bernierllc/ai-provider-openai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "OpenAI API adapter implementing the unified AI provider interface",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"test": "jest --watch",
|
|
10
|
-
"test:run": "jest",
|
|
11
|
-
"test:coverage": "jest --coverage",
|
|
12
|
-
"lint": "eslint src --ext .ts",
|
|
13
|
-
"clean": "rm -rf dist"
|
|
14
|
-
},
|
|
15
7
|
"keywords": [
|
|
16
8
|
"ai",
|
|
17
9
|
"openai",
|
|
@@ -27,10 +19,10 @@
|
|
|
27
19
|
"author": "Bernier LLC",
|
|
28
20
|
"license": "SEE LICENSE IN LICENSE",
|
|
29
21
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"@bernierllc/
|
|
32
|
-
"@bernierllc/
|
|
33
|
-
"
|
|
22
|
+
"openai": "^4.20.0",
|
|
23
|
+
"@bernierllc/ai-provider-core": "1.0.3",
|
|
24
|
+
"@bernierllc/retry-policy": "0.1.6",
|
|
25
|
+
"@bernierllc/logger": "1.1.0"
|
|
34
26
|
},
|
|
35
27
|
"devDependencies": {
|
|
36
28
|
"@types/jest": "^29.5.0",
|
|
@@ -59,5 +51,17 @@
|
|
|
59
51
|
"logger": "required",
|
|
60
52
|
"docs-suite": "ready"
|
|
61
53
|
}
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public",
|
|
57
|
+
"registry": "https://registry.npmjs.org/"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc",
|
|
61
|
+
"test": "jest --watch",
|
|
62
|
+
"test:run": "jest",
|
|
63
|
+
"test:coverage": "jest --coverage",
|
|
64
|
+
"lint": "eslint src --ext .ts",
|
|
65
|
+
"clean": "rm -rf dist"
|
|
62
66
|
}
|
|
63
|
-
}
|
|
67
|
+
}
|