librechat-data-provider 0.4.2 → 0.4.4

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.
@@ -0,0 +1,567 @@
1
+ import type { TAzureGroups } from '../src/config';
2
+ import { validateAzureGroups, mapModelToAzureConfig } from '../src/azure';
3
+
4
+ describe('validateAzureGroups', () => {
5
+ it('should validate a correct configuration', () => {
6
+ const configs = [
7
+ {
8
+ group: 'us-east',
9
+ apiKey: 'prod-1234',
10
+ instanceName: 'prod-instance',
11
+ deploymentName: 'v1-deployment',
12
+ version: '2023-12-31',
13
+ baseURL: 'https://prod.example.com',
14
+ additionalHeaders: {
15
+ 'X-Custom-Header': 'value',
16
+ },
17
+ models: {
18
+ 'gpt-4-turbo': {
19
+ deploymentName: 'gpt-4-turbo-deployment',
20
+ version: '2023-11-06',
21
+ },
22
+ },
23
+ },
24
+ ];
25
+ const { isValid, modelNames, modelGroupMap, groupMap } = validateAzureGroups(configs);
26
+ expect(isValid).toBe(true);
27
+ expect(modelNames).toEqual(['gpt-4-turbo']);
28
+
29
+ const { azureOptions, baseURL, headers } = mapModelToAzureConfig({
30
+ modelName: 'gpt-4-turbo',
31
+ modelGroupMap,
32
+ groupMap,
33
+ });
34
+ expect(azureOptions).toEqual({
35
+ azureOpenAIApiKey: 'prod-1234',
36
+ azureOpenAIApiInstanceName: 'prod-instance',
37
+ azureOpenAIApiDeploymentName: 'gpt-4-turbo-deployment',
38
+ azureOpenAIApiVersion: '2023-11-06',
39
+ });
40
+ expect(baseURL).toBe('https://prod.example.com');
41
+ expect(headers).toEqual({
42
+ 'X-Custom-Header': 'value',
43
+ });
44
+ });
45
+
46
+ it('should return invalid for a configuration missing deploymentName at the model level where required', () => {
47
+ const configs = [
48
+ {
49
+ group: 'us-west',
50
+ apiKey: 'us-west-key-5678',
51
+ instanceName: 'us-west-instance',
52
+ models: {
53
+ 'gpt-5': {
54
+ version: '2023-12-01', // Missing deploymentName
55
+ },
56
+ },
57
+ },
58
+ ];
59
+ const { isValid, errors } = validateAzureGroups(configs);
60
+ expect(isValid).toBe(false);
61
+ expect(errors.length).toBe(1);
62
+ });
63
+
64
+ it('should return invalid for a configuration with a boolean model where group lacks deploymentName and version', () => {
65
+ const configs = [
66
+ {
67
+ group: 'sweden-central',
68
+ apiKey: 'sweden-central-9012',
69
+ instanceName: 'sweden-central-instance',
70
+ models: {
71
+ 'gpt-35-turbo': true, // The group lacks deploymentName and version
72
+ },
73
+ },
74
+ ];
75
+ const { isValid, errors } = validateAzureGroups(configs);
76
+ expect(isValid).toBe(false);
77
+ expect(errors.length).toBe(1);
78
+ });
79
+
80
+ it('should allow a boolean model when group has both deploymentName and version', () => {
81
+ const configs = [
82
+ {
83
+ group: 'japan-east',
84
+ apiKey: 'japan-east-3456',
85
+ instanceName: 'japan-east-instance',
86
+ deploymentName: 'default-deployment',
87
+ version: '2023-04-01',
88
+ models: {
89
+ 'gpt-5-turbo': true,
90
+ },
91
+ },
92
+ ];
93
+ const { isValid, modelNames, modelGroupMap, groupMap } = validateAzureGroups(configs);
94
+ expect(isValid).toBe(true);
95
+ const modelGroup = modelGroupMap['gpt-5-turbo'];
96
+ expect(modelGroup).toBeDefined();
97
+ expect(modelGroup.group).toBe('japan-east');
98
+ expect(groupMap[modelGroup.group]).toBeDefined();
99
+ expect(modelNames).toContain('gpt-5-turbo');
100
+ const { azureOptions } = mapModelToAzureConfig({
101
+ modelName: 'gpt-5-turbo',
102
+ modelGroupMap,
103
+ groupMap,
104
+ });
105
+ expect(azureOptions).toEqual({
106
+ azureOpenAIApiKey: 'japan-east-3456',
107
+ azureOpenAIApiInstanceName: 'japan-east-instance',
108
+ azureOpenAIApiDeploymentName: 'default-deployment',
109
+ azureOpenAIApiVersion: '2023-04-01',
110
+ });
111
+ });
112
+
113
+ it('should validate correctly when optional fields are missing', () => {
114
+ const configs = [
115
+ {
116
+ group: 'canada-central',
117
+ apiKey: 'canada-key',
118
+ instanceName: 'canada-instance',
119
+ models: {
120
+ 'gpt-6': {
121
+ deploymentName: 'gpt-6-deployment',
122
+ version: '2023-01-01',
123
+ },
124
+ },
125
+ },
126
+ ];
127
+ const { isValid, modelNames, modelGroupMap, groupMap } = validateAzureGroups(configs);
128
+ expect(isValid).toBe(true);
129
+ expect(modelNames).toEqual(['gpt-6']);
130
+ const { azureOptions } = mapModelToAzureConfig({ modelName: 'gpt-6', modelGroupMap, groupMap });
131
+ expect(azureOptions).toEqual({
132
+ azureOpenAIApiKey: 'canada-key',
133
+ azureOpenAIApiInstanceName: 'canada-instance',
134
+ azureOpenAIApiDeploymentName: 'gpt-6-deployment',
135
+ azureOpenAIApiVersion: '2023-01-01',
136
+ });
137
+ });
138
+
139
+ it('should return invalid for configurations with incorrect types', () => {
140
+ const configs = [
141
+ {
142
+ group: 123, // incorrect type
143
+ apiKey: 'key123',
144
+ instanceName: 'instance123',
145
+ models: {
146
+ 'gpt-7': true,
147
+ },
148
+ },
149
+ ];
150
+ // @ts-expect-error This error is expected because the 'group' property should be a string.
151
+ const { isValid, errors } = validateAzureGroups(configs);
152
+ expect(isValid).toBe(false);
153
+ expect(errors.length).toBe(1);
154
+ });
155
+
156
+ it('should correctly handle a mix of valid and invalid model configurations', () => {
157
+ const configs = [
158
+ {
159
+ group: 'australia-southeast',
160
+ apiKey: 'australia-key',
161
+ instanceName: 'australia-instance',
162
+ models: {
163
+ 'valid-model': {
164
+ deploymentName: 'valid-deployment',
165
+ version: '2023-02-02',
166
+ },
167
+ 'invalid-model': true, // Invalid because the group lacks deploymentName and version
168
+ },
169
+ },
170
+ ];
171
+ const { isValid, modelNames, errors } = validateAzureGroups(configs);
172
+ expect(isValid).toBe(false);
173
+ expect(modelNames).toEqual(expect.arrayContaining(['valid-model', 'invalid-model']));
174
+ expect(errors.length).toBe(1);
175
+ });
176
+
177
+ it('should return invalid for configuration missing required fields at the group level', () => {
178
+ const configs = [
179
+ {
180
+ group: 'brazil-south',
181
+ apiKey: 'brazil-key',
182
+ // Missing instanceName
183
+ models: {
184
+ 'gpt-8': {
185
+ deploymentName: 'gpt-8-deployment',
186
+ version: '2023-03-03',
187
+ },
188
+ },
189
+ },
190
+ ];
191
+ // @ts-expect-error This error is expected because the 'instanceName' property is intentionally left out.
192
+ const { isValid, errors } = validateAzureGroups(configs);
193
+ expect(isValid).toBe(false);
194
+ expect(errors.length).toBe(1);
195
+ });
196
+ });
197
+
198
+ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
199
+ const originalEnv = process.env;
200
+
201
+ beforeEach(() => {
202
+ jest.resetModules();
203
+ process.env = { ...originalEnv };
204
+ });
205
+
206
+ afterAll(() => {
207
+ process.env = originalEnv;
208
+ });
209
+
210
+ it('should provide a valid modelGroupMap and groupMap for a correct configuration', () => {
211
+ const validConfigs: TAzureGroups = [
212
+ {
213
+ group: 'us-east',
214
+ apiKey: 'prod-1234',
215
+ instanceName: 'prod-instance',
216
+ deploymentName: 'v1-deployment',
217
+ version: '2023-12-31',
218
+ baseURL: 'https://prod.example.com',
219
+ additionalHeaders: {
220
+ 'X-Custom-Header': 'value',
221
+ },
222
+ models: {
223
+ 'gpt-4-turbo': {
224
+ deploymentName: 'gpt-4-turbo-deployment',
225
+ version: '2023-11-06',
226
+ },
227
+ },
228
+ },
229
+ {
230
+ group: 'us-west',
231
+ apiKey: 'prod-12345',
232
+ instanceName: 'prod-instance',
233
+ deploymentName: 'v1-deployment',
234
+ version: '2023-12-31',
235
+ baseURL: 'https://prod.example.com',
236
+ additionalHeaders: {
237
+ 'X-Custom-Header': 'value',
238
+ },
239
+ models: {
240
+ 'gpt-5-turbo': {
241
+ deploymentName: 'gpt-5-turbo-deployment',
242
+ version: '2023-11-06',
243
+ },
244
+ },
245
+ },
246
+ ];
247
+ const { isValid, modelGroupMap, groupMap } = validateAzureGroups(validConfigs);
248
+ expect(isValid).toBe(true);
249
+ expect(modelGroupMap['gpt-4-turbo']).toBeDefined();
250
+ expect(modelGroupMap['gpt-4-turbo'].group).toBe('us-east');
251
+ expect(groupMap['us-east']).toBeDefined();
252
+ expect(groupMap['us-east'].apiKey).toBe('prod-1234');
253
+ expect(groupMap['us-east'].models['gpt-4-turbo']).toBeDefined();
254
+ const { azureOptions, baseURL, headers } = mapModelToAzureConfig({
255
+ modelName: 'gpt-4-turbo',
256
+ modelGroupMap,
257
+ groupMap,
258
+ });
259
+ expect(azureOptions).toEqual({
260
+ azureOpenAIApiKey: 'prod-1234',
261
+ azureOpenAIApiInstanceName: 'prod-instance',
262
+ azureOpenAIApiDeploymentName: 'gpt-4-turbo-deployment',
263
+ azureOpenAIApiVersion: '2023-11-06',
264
+ });
265
+ expect(baseURL).toBe('https://prod.example.com');
266
+ expect(headers).toEqual({
267
+ 'X-Custom-Header': 'value',
268
+ });
269
+ });
270
+
271
+ it('should not allow duplicate group names', () => {
272
+ const duplicateGroups: TAzureGroups = [
273
+ {
274
+ group: 'us-east',
275
+ apiKey: 'prod-1234',
276
+ instanceName: 'prod-instance',
277
+ deploymentName: 'v1-deployment',
278
+ version: '2023-12-31',
279
+ baseURL: 'https://prod.example.com',
280
+ additionalHeaders: {
281
+ 'X-Custom-Header': 'value',
282
+ },
283
+ models: {
284
+ 'gpt-4-turbo': {
285
+ deploymentName: 'gpt-4-turbo-deployment',
286
+ version: '2023-11-06',
287
+ },
288
+ },
289
+ },
290
+ {
291
+ group: 'us-east',
292
+ apiKey: 'prod-1234',
293
+ instanceName: 'prod-instance',
294
+ deploymentName: 'v1-deployment',
295
+ version: '2023-12-31',
296
+ baseURL: 'https://prod.example.com',
297
+ additionalHeaders: {
298
+ 'X-Custom-Header': 'value',
299
+ },
300
+ models: {
301
+ 'gpt-5-turbo': {
302
+ deploymentName: 'gpt-4-turbo-deployment',
303
+ version: '2023-11-06',
304
+ },
305
+ },
306
+ },
307
+ ];
308
+ const { isValid } = validateAzureGroups(duplicateGroups);
309
+ expect(isValid).toBe(false);
310
+ });
311
+ it('should not allow duplicate models across groups', () => {
312
+ const duplicateGroups: TAzureGroups = [
313
+ {
314
+ group: 'us-east',
315
+ apiKey: 'prod-1234',
316
+ instanceName: 'prod-instance',
317
+ deploymentName: 'v1-deployment',
318
+ version: '2023-12-31',
319
+ baseURL: 'https://prod.example.com',
320
+ additionalHeaders: {
321
+ 'X-Custom-Header': 'value',
322
+ },
323
+ models: {
324
+ 'gpt-4-turbo': {
325
+ deploymentName: 'gpt-4-turbo-deployment',
326
+ version: '2023-11-06',
327
+ },
328
+ },
329
+ },
330
+ {
331
+ group: 'us-west',
332
+ apiKey: 'prod-1234',
333
+ instanceName: 'prod-instance',
334
+ deploymentName: 'v1-deployment',
335
+ version: '2023-12-31',
336
+ baseURL: 'https://prod.example.com',
337
+ additionalHeaders: {
338
+ 'X-Custom-Header': 'value',
339
+ },
340
+ models: {
341
+ 'gpt-4-turbo': {
342
+ deploymentName: 'gpt-4-turbo-deployment',
343
+ version: '2023-11-06',
344
+ },
345
+ },
346
+ },
347
+ ];
348
+ const { isValid } = validateAzureGroups(duplicateGroups);
349
+ expect(isValid).toBe(false);
350
+ });
351
+
352
+ it('should throw an error if environment variables are set but not configured', () => {
353
+ const validConfigs: TAzureGroups = [
354
+ {
355
+ group: 'librechat-westus',
356
+ apiKey: '${WESTUS_API_KEY}',
357
+ instanceName: 'librechat-westus',
358
+ version: '2023-12-01-preview',
359
+ models: {
360
+ 'gpt-4-vision-preview': {
361
+ deploymentName: 'gpt-4-vision-preview',
362
+ version: '2024-02-15-preview',
363
+ },
364
+ 'gpt-3.5-turbo': {
365
+ deploymentName: 'gpt-35-turbo',
366
+ },
367
+ 'gpt-3.5-turbo-1106': {
368
+ deploymentName: 'gpt-35-turbo-1106',
369
+ },
370
+ 'gpt-4': {
371
+ deploymentName: 'gpt-4',
372
+ },
373
+ 'gpt-4-1106-preview': {
374
+ deploymentName: 'gpt-4-1106-preview',
375
+ },
376
+ },
377
+ },
378
+ {
379
+ group: 'librechat-eastus',
380
+ apiKey: '${EASTUS_API_KEY}',
381
+ instanceName: 'librechat-eastus',
382
+ deploymentName: 'gpt-4-turbo',
383
+ version: '2024-02-15-preview',
384
+ models: {
385
+ 'gpt-4-turbo': true,
386
+ },
387
+ },
388
+ ];
389
+ const { isValid, modelGroupMap, groupMap } = validateAzureGroups(validConfigs);
390
+ expect(isValid).toBe(true);
391
+ expect(() =>
392
+ mapModelToAzureConfig({ modelName: 'gpt-4-turbo', modelGroupMap, groupMap }),
393
+ ).toThrow();
394
+ });
395
+
396
+ it('should list all expected models in both modelGroupMap and groupMap', () => {
397
+ process.env.WESTUS_API_KEY = 'westus-key';
398
+ process.env.EASTUS_API_KEY = 'eastus-key';
399
+
400
+ const validConfigs: TAzureGroups = [
401
+ {
402
+ group: 'librechat-westus',
403
+ apiKey: '${WESTUS_API_KEY}',
404
+ instanceName: 'librechat-westus',
405
+ version: '2023-12-01-preview',
406
+ models: {
407
+ 'gpt-4-vision-preview': {
408
+ deploymentName: 'gpt-4-vision-preview',
409
+ version: '2024-02-15-preview',
410
+ },
411
+ 'gpt-3.5-turbo': {
412
+ deploymentName: 'gpt-35-turbo',
413
+ },
414
+ 'gpt-3.5-turbo-1106': {
415
+ deploymentName: 'gpt-35-turbo-1106',
416
+ },
417
+ 'gpt-4': {
418
+ deploymentName: 'gpt-4',
419
+ },
420
+ 'gpt-4-1106-preview': {
421
+ deploymentName: 'gpt-4-1106-preview',
422
+ },
423
+ },
424
+ },
425
+ {
426
+ group: 'librechat-eastus',
427
+ apiKey: '${EASTUS_API_KEY}',
428
+ instanceName: 'librechat-eastus',
429
+ deploymentName: 'gpt-4-turbo',
430
+ version: '2024-02-15-preview',
431
+ models: {
432
+ 'gpt-4-turbo': true,
433
+ },
434
+ baseURL: 'https://eastus.example.com',
435
+ additionalHeaders: {
436
+ 'x-api-key': 'x-api-key-value',
437
+ },
438
+ },
439
+ ];
440
+ const { isValid, modelGroupMap, groupMap, modelNames } = validateAzureGroups(validConfigs);
441
+ expect(isValid).toBe(true);
442
+ expect(modelNames).toEqual([
443
+ 'gpt-4-vision-preview',
444
+ 'gpt-3.5-turbo',
445
+ 'gpt-3.5-turbo-1106',
446
+ 'gpt-4',
447
+ 'gpt-4-1106-preview',
448
+ 'gpt-4-turbo',
449
+ ]);
450
+
451
+ // Check modelGroupMap
452
+ modelNames.forEach((modelName) => {
453
+ expect(modelGroupMap[modelName]).toBeDefined();
454
+ });
455
+
456
+ // Check groupMap for 'librechat-westus'
457
+ expect(groupMap).toHaveProperty('librechat-westus');
458
+ expect(groupMap['librechat-westus']).toEqual(
459
+ expect.objectContaining({
460
+ apiKey: '${WESTUS_API_KEY}',
461
+ instanceName: 'librechat-westus',
462
+ version: '2023-12-01-preview',
463
+ models: expect.objectContaining({
464
+ 'gpt-4-vision-preview': expect.any(Object),
465
+ 'gpt-3.5-turbo': expect.any(Object),
466
+ 'gpt-3.5-turbo-1106': expect.any(Object),
467
+ 'gpt-4': expect.any(Object),
468
+ 'gpt-4-1106-preview': expect.any(Object),
469
+ }),
470
+ }),
471
+ );
472
+
473
+ // Check groupMap for 'librechat-eastus'
474
+ expect(groupMap).toHaveProperty('librechat-eastus');
475
+ expect(groupMap['librechat-eastus']).toEqual(
476
+ expect.objectContaining({
477
+ apiKey: '${EASTUS_API_KEY}',
478
+ instanceName: 'librechat-eastus',
479
+ deploymentName: 'gpt-4-turbo',
480
+ version: '2024-02-15-preview',
481
+ models: expect.objectContaining({
482
+ 'gpt-4-turbo': true,
483
+ }),
484
+ }),
485
+ );
486
+
487
+ const { azureOptions: azureOptions1 } = mapModelToAzureConfig({
488
+ modelName: 'gpt-4-vision-preview',
489
+ modelGroupMap,
490
+ groupMap,
491
+ });
492
+ expect(azureOptions1).toEqual({
493
+ azureOpenAIApiKey: 'westus-key',
494
+ azureOpenAIApiInstanceName: 'librechat-westus',
495
+ azureOpenAIApiDeploymentName: 'gpt-4-vision-preview',
496
+ azureOpenAIApiVersion: '2024-02-15-preview',
497
+ });
498
+
499
+ const {
500
+ azureOptions: azureOptions2,
501
+ baseURL,
502
+ headers,
503
+ } = mapModelToAzureConfig({
504
+ modelName: 'gpt-4-turbo',
505
+ modelGroupMap,
506
+ groupMap,
507
+ });
508
+ expect(azureOptions2).toEqual({
509
+ azureOpenAIApiKey: 'eastus-key',
510
+ azureOpenAIApiInstanceName: 'librechat-eastus',
511
+ azureOpenAIApiDeploymentName: 'gpt-4-turbo',
512
+ azureOpenAIApiVersion: '2024-02-15-preview',
513
+ });
514
+ expect(baseURL).toBe('https://eastus.example.com');
515
+ expect(headers).toEqual({
516
+ 'x-api-key': 'x-api-key-value',
517
+ });
518
+
519
+ const { azureOptions: azureOptions3 } = mapModelToAzureConfig({
520
+ modelName: 'gpt-4',
521
+ modelGroupMap,
522
+ groupMap,
523
+ });
524
+ expect(azureOptions3).toEqual({
525
+ azureOpenAIApiKey: 'westus-key',
526
+ azureOpenAIApiInstanceName: 'librechat-westus',
527
+ azureOpenAIApiDeploymentName: 'gpt-4',
528
+ azureOpenAIApiVersion: '2023-12-01-preview',
529
+ });
530
+
531
+ const { azureOptions: azureOptions4 } = mapModelToAzureConfig({
532
+ modelName: 'gpt-3.5-turbo',
533
+ modelGroupMap,
534
+ groupMap,
535
+ });
536
+ expect(azureOptions4).toEqual({
537
+ azureOpenAIApiKey: 'westus-key',
538
+ azureOpenAIApiInstanceName: 'librechat-westus',
539
+ azureOpenAIApiDeploymentName: 'gpt-35-turbo',
540
+ azureOpenAIApiVersion: '2023-12-01-preview',
541
+ });
542
+
543
+ const { azureOptions: azureOptions5 } = mapModelToAzureConfig({
544
+ modelName: 'gpt-3.5-turbo-1106',
545
+ modelGroupMap,
546
+ groupMap,
547
+ });
548
+ expect(azureOptions5).toEqual({
549
+ azureOpenAIApiKey: 'westus-key',
550
+ azureOpenAIApiInstanceName: 'librechat-westus',
551
+ azureOpenAIApiDeploymentName: 'gpt-35-turbo-1106',
552
+ azureOpenAIApiVersion: '2023-12-01-preview',
553
+ });
554
+
555
+ const { azureOptions: azureOptions6 } = mapModelToAzureConfig({
556
+ modelName: 'gpt-4-1106-preview',
557
+ modelGroupMap,
558
+ groupMap,
559
+ });
560
+ expect(azureOptions6).toEqual({
561
+ azureOpenAIApiKey: 'westus-key',
562
+ azureOpenAIApiInstanceName: 'librechat-westus',
563
+ azureOpenAIApiDeploymentName: 'gpt-4-1106-preview',
564
+ azureOpenAIApiVersion: '2023-12-01-preview',
565
+ });
566
+ });
567
+ });
@@ -0,0 +1,48 @@
1
+ import { extractEnvVariable } from '../src/parsers';
2
+
3
+ describe('extractEnvVariable', () => {
4
+ const originalEnv = process.env;
5
+
6
+ beforeEach(() => {
7
+ jest.resetModules();
8
+ process.env = { ...originalEnv };
9
+ });
10
+
11
+ afterAll(() => {
12
+ process.env = originalEnv;
13
+ });
14
+
15
+ test('should return the value of the environment variable', () => {
16
+ process.env.TEST_VAR = 'test_value';
17
+ expect(extractEnvVariable('${TEST_VAR}')).toBe('test_value');
18
+ });
19
+
20
+ test('should return the original string if the envrionment variable is not defined correctly', () => {
21
+ process.env.TEST_VAR = 'test_value';
22
+ expect(extractEnvVariable('${ TEST_VAR }')).toBe('${ TEST_VAR }');
23
+ });
24
+
25
+ test('should return the original string if environment variable is not set', () => {
26
+ expect(extractEnvVariable('${NON_EXISTENT_VAR}')).toBe('${NON_EXISTENT_VAR}');
27
+ });
28
+
29
+ test('should return the original string if it does not contain an environment variable', () => {
30
+ expect(extractEnvVariable('some_string')).toBe('some_string');
31
+ });
32
+
33
+ test('should handle empty strings', () => {
34
+ expect(extractEnvVariable('')).toBe('');
35
+ });
36
+
37
+ test('should handle strings without variable format', () => {
38
+ expect(extractEnvVariable('no_var_here')).toBe('no_var_here');
39
+ });
40
+
41
+ test('should not process multiple variable formats', () => {
42
+ process.env.FIRST_VAR = 'first';
43
+ process.env.SECOND_VAR = 'second';
44
+ expect(extractEnvVariable('${FIRST_VAR} and ${SECOND_VAR}')).toBe(
45
+ '${FIRST_VAR} and ${SECOND_VAR}',
46
+ );
47
+ });
48
+ });