librechat-data-provider 0.7.430 → 0.7.692

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librechat-data-provider",
3
- "version": "0.7.430",
3
+ "version": "0.7.692",
4
4
  "description": "data services for librechat apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
@@ -39,11 +39,8 @@
39
39
  },
40
40
  "homepage": "https://librechat.ai",
41
41
  "dependencies": {
42
- "@types/js-yaml": "^4.0.9",
43
42
  "axios": "^1.7.7",
44
43
  "js-yaml": "^4.1.0",
45
- "openai": "4.11.1",
46
- "openapi-types": "^12.1.3",
47
44
  "zod": "^3.22.4"
48
45
  },
49
46
  "devDependencies": {
@@ -57,10 +54,13 @@
57
54
  "@rollup/plugin-replace": "^5.0.5",
58
55
  "@rollup/plugin-terser": "^0.4.4",
59
56
  "@types/jest": "^29.5.2",
57
+ "@types/js-yaml": "^4.0.9",
60
58
  "@types/node": "^20.3.0",
61
59
  "@types/react": "^18.2.18",
62
60
  "jest": "^29.5.0",
63
61
  "jest-junit": "^16.0.0",
62
+ "openai": "^4.76.3",
63
+ "openapi-types": "^12.1.3",
64
64
  "rimraf": "^5.0.1",
65
65
  "rollup": "^4.22.4",
66
66
  "rollup-plugin-generate-package-json": "^3.2.0",
@@ -10,7 +10,7 @@ const entryPath = path.resolve(rootPath, 'api/server/index.js');
10
10
 
11
11
  console.log('entryPath', entryPath);
12
12
 
13
- // Define your custom aliases here
13
+ // Define custom aliases here
14
14
  const customAliases = {
15
15
  entries: [{ find: '~', replacement: rootServerPath }],
16
16
  };
@@ -18,7 +18,7 @@ const customAliases = {
18
18
  export default {
19
19
  input: entryPath,
20
20
  output: {
21
- file: 'test_bundle/bundle.js',
21
+ dir: 'test_bundle',
22
22
  format: 'cjs',
23
23
  },
24
24
  plugins: [
@@ -65,7 +65,7 @@ describe('ActionRequest', () => {
65
65
  false,
66
66
  'application/json',
67
67
  );
68
- await actionRequest.setParams({ param1: 'value1' });
68
+ actionRequest.setParams({ param1: 'value1' });
69
69
  const response = await actionRequest.execute();
70
70
  expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', expect.anything());
71
71
  expect(response.data).toEqual({ success: true, method: 'GET' });
@@ -90,7 +90,7 @@ describe('ActionRequest', () => {
90
90
  false,
91
91
  'application/json',
92
92
  );
93
- await actionRequest.setParams({ param: 'test' });
93
+ actionRequest.setParams({ param: 'test' });
94
94
  const response = await actionRequest.execute();
95
95
  expect(mockedAxios.get).toHaveBeenCalled();
96
96
  expect(response.data.success).toBe(true);
@@ -106,7 +106,7 @@ describe('ActionRequest', () => {
106
106
  false,
107
107
  'application/json',
108
108
  );
109
- await actionRequest.setParams({ param: 'test' });
109
+ actionRequest.setParams({ param: 'test' });
110
110
  const response = await actionRequest.execute();
111
111
  expect(mockedAxios.post).toHaveBeenCalled();
112
112
  expect(response.data.success).toBe(true);
@@ -122,7 +122,7 @@ describe('ActionRequest', () => {
122
122
  false,
123
123
  'application/json',
124
124
  );
125
- await actionRequest.setParams({ param: 'test' });
125
+ actionRequest.setParams({ param: 'test' });
126
126
  const response = await actionRequest.execute();
127
127
  expect(mockedAxios.put).toHaveBeenCalled();
128
128
  expect(response.data.success).toBe(true);
@@ -138,7 +138,7 @@ describe('ActionRequest', () => {
138
138
  false,
139
139
  'application/json',
140
140
  );
141
- await actionRequest.setParams({ param: 'test' });
141
+ actionRequest.setParams({ param: 'test' });
142
142
  const response = await actionRequest.execute();
143
143
  expect(mockedAxios.delete).toHaveBeenCalled();
144
144
  expect(response.data.success).toBe(true);
@@ -154,7 +154,7 @@ describe('ActionRequest', () => {
154
154
  false,
155
155
  'application/json',
156
156
  );
157
- await actionRequest.setParams({ param: 'test' });
157
+ actionRequest.setParams({ param: 'test' });
158
158
  const response = await actionRequest.execute();
159
159
  expect(mockedAxios.patch).toHaveBeenCalled();
160
160
  expect(response.data.success).toBe(true);
@@ -169,7 +169,7 @@ describe('ActionRequest', () => {
169
169
  false,
170
170
  'application/json',
171
171
  );
172
- await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: INVALID');
172
+ await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
173
173
  });
174
174
 
175
175
  it('replaces path parameters with values from toolInput', async () => {
@@ -182,20 +182,21 @@ describe('ActionRequest', () => {
182
182
  'application/json',
183
183
  );
184
184
 
185
- await actionRequest.setParams({
185
+ const executor = actionRequest.createExecutor();
186
+ executor.setParams({
186
187
  stocksTicker: 'AAPL',
187
188
  multiplier: 5,
188
189
  startDate: '2023-01-01',
189
190
  endDate: '2023-12-31',
190
191
  });
191
192
 
192
- expect(actionRequest.path).toBe('/stocks/AAPL/bars/5');
193
- expect(actionRequest.params).toEqual({
193
+ expect(executor.path).toBe('/stocks/AAPL/bars/5');
194
+ expect(executor.params).toEqual({
194
195
  startDate: '2023-01-01',
195
196
  endDate: '2023-12-31',
196
197
  });
197
198
 
198
- await actionRequest.execute();
199
+ await executor.execute();
199
200
  expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/stocks/AAPL/bars/5', {
200
201
  headers: expect.anything(),
201
202
  params: {
@@ -215,7 +216,271 @@ describe('ActionRequest', () => {
215
216
  false,
216
217
  'application/json',
217
218
  );
218
- await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: INVALID');
219
+ await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
220
+ });
221
+
222
+ describe('ActionRequest Concurrent Execution', () => {
223
+ beforeEach(() => {
224
+ jest.clearAllMocks();
225
+ mockedAxios.get.mockImplementation(async (url, config) => ({
226
+ data: { url, params: config?.params, headers: config?.headers },
227
+ }));
228
+ });
229
+
230
+ it('maintains isolated state between concurrent executions with different parameters', async () => {
231
+ const actionRequest = new ActionRequest(
232
+ 'https://example.com',
233
+ '/math/sqrt/{number}',
234
+ 'GET',
235
+ 'getSqrt',
236
+ false,
237
+ 'application/json',
238
+ );
239
+
240
+ // Simulate concurrent requests with different numbers
241
+ const numbers = [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30];
242
+ const requests = numbers.map((num) => ({
243
+ number: num.toString(),
244
+ precision: '2',
245
+ }));
246
+
247
+ const responses = await Promise.all(
248
+ requests.map((params) => {
249
+ const executor = actionRequest.createExecutor();
250
+ return executor.setParams(params).execute();
251
+ }),
252
+ );
253
+
254
+ // Verify each response used the correct path parameter
255
+ responses.forEach((response, index) => {
256
+ const expectedUrl = `https://example.com/math/sqrt/${numbers[index]}`;
257
+ expect(response.data.url).toBe(expectedUrl);
258
+ expect(response.data.params).toEqual({ precision: '2' });
259
+ });
260
+
261
+ // Verify the correct number of calls were made
262
+ expect(mockedAxios.get).toHaveBeenCalledTimes(numbers.length);
263
+ });
264
+
265
+ it('maintains isolated authentication state between concurrent executions', async () => {
266
+ const actionRequest = new ActionRequest(
267
+ 'https://example.com',
268
+ '/secure/resource/{id}',
269
+ 'GET',
270
+ 'getResource',
271
+ false,
272
+ 'application/json',
273
+ );
274
+
275
+ const requests = [
276
+ {
277
+ params: { id: '1' },
278
+ auth: {
279
+ auth: {
280
+ type: AuthTypeEnum.ServiceHttp,
281
+ authorization_type: AuthorizationTypeEnum.Bearer,
282
+ },
283
+ api_key: 'token1',
284
+ },
285
+ },
286
+ {
287
+ params: { id: '2' },
288
+ auth: {
289
+ auth: {
290
+ type: AuthTypeEnum.ServiceHttp,
291
+ authorization_type: AuthorizationTypeEnum.Bearer,
292
+ },
293
+ api_key: 'token2',
294
+ },
295
+ },
296
+ ];
297
+
298
+ const responses = await Promise.all(
299
+ requests.map(async ({ params, auth }) => {
300
+ const executor = actionRequest.createExecutor();
301
+ return (await executor.setParams(params).setAuth(auth)).execute();
302
+ }),
303
+ );
304
+
305
+ // Verify each response had its own auth token
306
+ responses.forEach((response, index) => {
307
+ const expectedUrl = `https://example.com/secure/resource/${index + 1}`;
308
+ expect(response.data.url).toBe(expectedUrl);
309
+ expect(response.data.headers).toMatchObject({
310
+ Authorization: `Bearer token${index + 1}`,
311
+ });
312
+ });
313
+ });
314
+
315
+ it('handles mixed authentication types concurrently', async () => {
316
+ const actionRequest = new ActionRequest(
317
+ 'https://example.com',
318
+ '/api/{version}/data',
319
+ 'GET',
320
+ 'getData',
321
+ false,
322
+ 'application/json',
323
+ );
324
+
325
+ const requests = [
326
+ {
327
+ params: { version: 'v1' },
328
+ auth: {
329
+ auth: {
330
+ type: AuthTypeEnum.ServiceHttp,
331
+ authorization_type: AuthorizationTypeEnum.Bearer,
332
+ },
333
+ api_key: 'bearer_token',
334
+ },
335
+ },
336
+ {
337
+ params: { version: 'v2' },
338
+ auth: {
339
+ auth: {
340
+ type: AuthTypeEnum.ServiceHttp,
341
+ authorization_type: AuthorizationTypeEnum.Basic,
342
+ },
343
+ api_key: 'basic:auth',
344
+ },
345
+ },
346
+ {
347
+ params: { version: 'v3' },
348
+ auth: {
349
+ auth: {
350
+ type: AuthTypeEnum.ServiceHttp,
351
+ authorization_type: AuthorizationTypeEnum.Custom,
352
+ custom_auth_header: 'X-API-Key',
353
+ },
354
+ api_key: 'custom_key',
355
+ },
356
+ },
357
+ ];
358
+
359
+ const responses = await Promise.all(
360
+ requests.map(async ({ params, auth }) => {
361
+ const executor = actionRequest.createExecutor();
362
+ return (await executor.setParams(params).setAuth(auth)).execute();
363
+ }),
364
+ );
365
+
366
+ // Verify each response had the correct auth type and headers
367
+ expect(responses[0].data.headers).toMatchObject({
368
+ Authorization: 'Bearer bearer_token',
369
+ });
370
+
371
+ expect(responses[1].data.headers).toMatchObject({
372
+ Authorization: `Basic ${Buffer.from('basic:auth').toString('base64')}`,
373
+ });
374
+
375
+ expect(responses[2].data.headers).toMatchObject({
376
+ 'X-API-Key': 'custom_key',
377
+ });
378
+ });
379
+
380
+ it('maintains parameter integrity during concurrent path parameter replacement', async () => {
381
+ const actionRequest = new ActionRequest(
382
+ 'https://example.com',
383
+ '/users/{userId}/posts/{postId}',
384
+ 'GET',
385
+ 'getUserPost',
386
+ false,
387
+ 'application/json',
388
+ );
389
+
390
+ const requests = [
391
+ { userId: '1', postId: 'a', filter: 'recent' },
392
+ { userId: '2', postId: 'b', filter: 'popular' },
393
+ { userId: '3', postId: 'c', filter: 'trending' },
394
+ ];
395
+
396
+ const responses = await Promise.all(
397
+ requests.map((params) => {
398
+ const executor = actionRequest.createExecutor();
399
+ return executor.setParams(params).execute();
400
+ }),
401
+ );
402
+
403
+ responses.forEach((response, index) => {
404
+ const expectedUrl = `https://example.com/users/${requests[index].userId}/posts/${requests[index].postId}`;
405
+ expect(response.data.url).toBe(expectedUrl);
406
+ expect(response.data.params).toEqual({ filter: requests[index].filter });
407
+ });
408
+ });
409
+
410
+ it('preserves original ActionRequest state after multiple executions', async () => {
411
+ const actionRequest = new ActionRequest(
412
+ 'https://example.com',
413
+ '/original/{param}',
414
+ 'GET',
415
+ 'testOp',
416
+ false,
417
+ 'application/json',
418
+ );
419
+
420
+ // Store original values
421
+ const originalPath = actionRequest.path;
422
+ const originalDomain = actionRequest.domain;
423
+ const originalMethod = actionRequest.method;
424
+
425
+ // Perform multiple concurrent executions
426
+ await Promise.all([
427
+ actionRequest.createExecutor().setParams({ param: '1' }).execute(),
428
+ actionRequest.createExecutor().setParams({ param: '2' }).execute(),
429
+ actionRequest.createExecutor().setParams({ param: '3' }).execute(),
430
+ ]);
431
+
432
+ // Verify original ActionRequest remains unchanged
433
+ expect(actionRequest.path).toBe(originalPath);
434
+ expect(actionRequest.domain).toBe(originalDomain);
435
+ expect(actionRequest.method).toBe(originalMethod);
436
+ });
437
+
438
+ it('shares immutable configuration between executors from the same ActionRequest', () => {
439
+ const actionRequest = new ActionRequest(
440
+ 'https://example.com',
441
+ '/api/{version}/data',
442
+ 'GET',
443
+ 'getData',
444
+ false,
445
+ 'application/json',
446
+ );
447
+
448
+ // Create multiple executors
449
+ const executor1 = actionRequest.createExecutor();
450
+ const executor2 = actionRequest.createExecutor();
451
+ const executor3 = actionRequest.createExecutor();
452
+
453
+ // Test that the configuration properties are shared
454
+ [executor1, executor2, executor3].forEach((executor) => {
455
+ expect(executor.getConfig()).toBeDefined();
456
+ expect(executor.getConfig()).toEqual({
457
+ domain: 'https://example.com',
458
+ basePath: '/api/{version}/data',
459
+ method: 'GET',
460
+ operation: 'getData',
461
+ isConsequential: false,
462
+ contentType: 'application/json',
463
+ });
464
+ });
465
+
466
+ // Verify that config objects are the exact same instance (shared reference)
467
+ expect(executor1.getConfig()).toBe(executor2.getConfig());
468
+ expect(executor2.getConfig()).toBe(executor3.getConfig());
469
+
470
+ // Verify that modifying mutable state doesn't affect other executors
471
+ executor1.setParams({ version: 'v1' });
472
+ executor2.setParams({ version: 'v2' });
473
+ executor3.setParams({ version: 'v3' });
474
+
475
+ expect(executor1.path).toBe('/api/v1/data');
476
+ expect(executor2.path).toBe('/api/v2/data');
477
+ expect(executor3.path).toBe('/api/v3/data');
478
+
479
+ // Verify that the original config remains unchanged
480
+ expect(executor1.getConfig().basePath).toBe('/api/{version}/data');
481
+ expect(executor2.getConfig().basePath).toBe('/api/{version}/data');
482
+ expect(executor3.getConfig().basePath).toBe('/api/{version}/data');
483
+ });
219
484
  });
220
485
  });
221
486
 
@@ -233,7 +498,8 @@ describe('Authentication Handling', () => {
233
498
  const api_key = 'user:pass';
234
499
  const encodedCredentials = Buffer.from('user:pass').toString('base64');
235
500
 
236
- actionRequest.setAuth({
501
+ const executor = actionRequest.createExecutor();
502
+ await executor.setParams({ param1: 'value1' }).setAuth({
237
503
  auth: {
238
504
  type: AuthTypeEnum.ServiceHttp,
239
505
  authorization_type: AuthorizationTypeEnum.Basic,
@@ -241,13 +507,13 @@ describe('Authentication Handling', () => {
241
507
  api_key,
242
508
  });
243
509
 
244
- await actionRequest.setParams({ param1: 'value1' });
245
- await actionRequest.execute();
510
+ await executor.execute();
246
511
  expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
247
512
  headers: expect.objectContaining({
248
513
  Authorization: `Basic ${encodedCredentials}`,
514
+ 'Content-Type': 'application/json',
249
515
  }),
250
- params: expect.anything(),
516
+ params: { param1: 'value1' },
251
517
  });
252
518
  });
253
519
 
@@ -260,20 +526,23 @@ describe('Authentication Handling', () => {
260
526
  false,
261
527
  'application/json',
262
528
  );
263
- actionRequest.setAuth({
529
+
530
+ const executor = actionRequest.createExecutor();
531
+ await executor.setParams({ param1: 'value1' }).setAuth({
264
532
  auth: {
265
533
  type: AuthTypeEnum.ServiceHttp,
266
534
  authorization_type: AuthorizationTypeEnum.Bearer,
267
535
  },
268
536
  api_key: 'token123',
269
537
  });
270
- await actionRequest.setParams({ param1: 'value1' });
271
- await actionRequest.execute();
538
+
539
+ await executor.execute();
272
540
  expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
273
541
  headers: expect.objectContaining({
274
542
  Authorization: 'Bearer token123',
543
+ 'Content-Type': 'application/json',
275
544
  }),
276
- params: expect.anything(),
545
+ params: { param1: 'value1' },
277
546
  });
278
547
  });
279
548
 
@@ -286,22 +555,24 @@ describe('Authentication Handling', () => {
286
555
  false,
287
556
  'application/json',
288
557
  );
289
- // Updated to match ActionMetadata structure
290
- actionRequest.setAuth({
558
+
559
+ const executor = actionRequest.createExecutor();
560
+ await executor.setParams({ param1: 'value1' }).setAuth({
291
561
  auth: {
292
- type: AuthTypeEnum.ServiceHttp, // Assuming this is a valid enum or value for your context
293
- authorization_type: AuthorizationTypeEnum.Custom, // Assuming Custom means using a custom header
562
+ type: AuthTypeEnum.ServiceHttp,
563
+ authorization_type: AuthorizationTypeEnum.Custom,
294
564
  custom_auth_header: 'X-API-KEY',
295
565
  },
296
566
  api_key: 'abc123',
297
567
  });
298
- await actionRequest.setParams({ param1: 'value1' });
299
- await actionRequest.execute();
568
+
569
+ await executor.execute();
300
570
  expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
301
571
  headers: expect.objectContaining({
302
572
  'X-API-KEY': 'abc123',
573
+ 'Content-Type': 'application/json',
303
574
  }),
304
- params: expect.anything(),
575
+ params: { param1: 'value1' },
305
576
  });
306
577
  });
307
578
  });
@@ -312,7 +583,7 @@ describe('resolveRef', () => {
312
583
  const flowchartRequestRef = (
313
584
  openapiSpec.paths['/ai.chatgpt.render-flowchart']?.post
314
585
  ?.requestBody as OpenAPIV3.RequestBodyObject
315
- )?.content['application/json'].schema;
586
+ ).content['application/json'].schema;
316
587
  expect(flowchartRequestRef).toBeDefined();
317
588
  const resolvedFlowchartRequest = resolveRef(
318
589
  flowchartRequestRef as OpenAPIV3.RequestBodyObject,
@@ -94,8 +94,8 @@ describe('validateAzureGroups', () => {
94
94
  expect(isValid).toBe(true);
95
95
  const modelGroup = modelGroupMap['gpt-5-turbo'];
96
96
  expect(modelGroup).toBeDefined();
97
- expect(modelGroup.group).toBe('japan-east');
98
- expect(groupMap[modelGroup.group]).toBeDefined();
97
+ expect(modelGroup?.group).toBe('japan-east');
98
+ expect(groupMap[modelGroup?.group ?? '']).toBeDefined();
99
99
  expect(modelNames).toContain('gpt-5-turbo');
100
100
  const { azureOptions } = mapModelToAzureConfig({
101
101
  modelName: 'gpt-5-turbo',
@@ -323,6 +323,7 @@ describe('validateAzureGroups for Serverless Configurations', () => {
323
323
 
324
324
  expect(azureOptions).toEqual({
325
325
  azureOpenAIApiKey: 'def456',
326
+ azureOpenAIApiVersion: '',
326
327
  });
327
328
  expect(baseURL).toEqual('https://new-serverless.example.com/v1/completions');
328
329
  expect(serverless).toBe(true);
@@ -381,10 +382,10 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
381
382
  const { isValid, modelGroupMap, groupMap } = validateAzureGroups(validConfigs);
382
383
  expect(isValid).toBe(true);
383
384
  expect(modelGroupMap['gpt-4-turbo']).toBeDefined();
384
- expect(modelGroupMap['gpt-4-turbo'].group).toBe('us-east');
385
+ expect(modelGroupMap['gpt-4-turbo']?.group).toBe('us-east');
385
386
  expect(groupMap['us-east']).toBeDefined();
386
- expect(groupMap['us-east'].apiKey).toBe('prod-1234');
387
- expect(groupMap['us-east'].models['gpt-4-turbo']).toBeDefined();
387
+ expect(groupMap['us-east']?.apiKey).toBe('prod-1234');
388
+ expect(groupMap['us-east']?.models['gpt-4-turbo']).toBeDefined();
388
389
  const { azureOptions, baseURL, headers } = mapModelToAzureConfig({
389
390
  modelName: 'gpt-4-turbo',
390
391
  modelGroupMap,
@@ -765,6 +766,7 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
765
766
  );
766
767
  expect(azureOptions7).toEqual({
767
768
  azureOpenAIApiKey: 'mistral-key',
769
+ azureOpenAIApiVersion: '',
768
770
  });
769
771
 
770
772
  const {
@@ -782,6 +784,7 @@ describe('validateAzureGroups with modelGroupMap and groupMap', () => {
782
784
  );
783
785
  expect(azureOptions8).toEqual({
784
786
  azureOpenAIApiKey: 'llama-key',
787
+ azureOpenAIApiVersion: '',
785
788
  });
786
789
  });
787
790
  });