librechat-data-provider 0.7.41 → 0.7.52
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/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/dist/react-query/package.json +1 -1
- package/package.json +3 -3
- package/react-query/package.json +1 -1
- package/specs/actions.spec.ts +575 -29
- package/specs/openapiSpecs.ts +127 -0
- package/src/actions.ts +207 -61
- package/src/api-endpoints.ts +22 -2
- package/src/artifacts.ts +3104 -0
- package/src/bedrock.ts +147 -0
- package/src/config.ts +174 -22
- package/src/data-service.ts +218 -75
- package/src/file-config.ts +4 -1
- package/src/generate.ts +30 -1
- package/src/index.ts +5 -0
- package/src/keys.ts +10 -0
- package/src/parsers.ts +85 -27
- package/src/react-query/react-query-service.ts +32 -7
- package/src/request.ts +3 -0
- package/src/roles.ts +59 -2
- package/src/schemas.ts +293 -184
- package/src/types/agents.ts +220 -0
- package/src/types/assistants.ts +152 -27
- package/src/types/files.ts +6 -0
- package/src/types/mutations.ts +72 -0
- package/src/types/queries.ts +14 -11
- package/src/types/runs.ts +22 -0
- package/src/types.ts +35 -4
package/specs/actions.spec.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { OpenAPIV3 } from 'openapi-types';
|
|
3
4
|
import {
|
|
4
5
|
createURL,
|
|
@@ -8,7 +9,12 @@ import {
|
|
|
8
9
|
FunctionSignature,
|
|
9
10
|
validateAndParseOpenAPISpec,
|
|
10
11
|
} from '../src/actions';
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getWeatherOpenapiSpec,
|
|
14
|
+
whimsicalOpenapiSpec,
|
|
15
|
+
scholarAIOpenapiSpec,
|
|
16
|
+
swapidev,
|
|
17
|
+
} from './openapiSpecs';
|
|
12
18
|
import { AuthorizationTypeEnum, AuthTypeEnum } from '../src/types/assistants';
|
|
13
19
|
import type { FlowchartSchema } from './openapiSpecs';
|
|
14
20
|
import type { ParametersSchema } from '../src/actions';
|
|
@@ -59,7 +65,7 @@ describe('ActionRequest', () => {
|
|
|
59
65
|
false,
|
|
60
66
|
'application/json',
|
|
61
67
|
);
|
|
62
|
-
|
|
68
|
+
actionRequest.setParams({ param1: 'value1' });
|
|
63
69
|
const response = await actionRequest.execute();
|
|
64
70
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', expect.anything());
|
|
65
71
|
expect(response.data).toEqual({ success: true, method: 'GET' });
|
|
@@ -84,7 +90,7 @@ describe('ActionRequest', () => {
|
|
|
84
90
|
false,
|
|
85
91
|
'application/json',
|
|
86
92
|
);
|
|
87
|
-
|
|
93
|
+
actionRequest.setParams({ param: 'test' });
|
|
88
94
|
const response = await actionRequest.execute();
|
|
89
95
|
expect(mockedAxios.get).toHaveBeenCalled();
|
|
90
96
|
expect(response.data.success).toBe(true);
|
|
@@ -100,7 +106,7 @@ describe('ActionRequest', () => {
|
|
|
100
106
|
false,
|
|
101
107
|
'application/json',
|
|
102
108
|
);
|
|
103
|
-
|
|
109
|
+
actionRequest.setParams({ param: 'test' });
|
|
104
110
|
const response = await actionRequest.execute();
|
|
105
111
|
expect(mockedAxios.post).toHaveBeenCalled();
|
|
106
112
|
expect(response.data.success).toBe(true);
|
|
@@ -116,7 +122,7 @@ describe('ActionRequest', () => {
|
|
|
116
122
|
false,
|
|
117
123
|
'application/json',
|
|
118
124
|
);
|
|
119
|
-
|
|
125
|
+
actionRequest.setParams({ param: 'test' });
|
|
120
126
|
const response = await actionRequest.execute();
|
|
121
127
|
expect(mockedAxios.put).toHaveBeenCalled();
|
|
122
128
|
expect(response.data.success).toBe(true);
|
|
@@ -132,7 +138,7 @@ describe('ActionRequest', () => {
|
|
|
132
138
|
false,
|
|
133
139
|
'application/json',
|
|
134
140
|
);
|
|
135
|
-
|
|
141
|
+
actionRequest.setParams({ param: 'test' });
|
|
136
142
|
const response = await actionRequest.execute();
|
|
137
143
|
expect(mockedAxios.delete).toHaveBeenCalled();
|
|
138
144
|
expect(response.data.success).toBe(true);
|
|
@@ -148,7 +154,7 @@ describe('ActionRequest', () => {
|
|
|
148
154
|
false,
|
|
149
155
|
'application/json',
|
|
150
156
|
);
|
|
151
|
-
|
|
157
|
+
actionRequest.setParams({ param: 'test' });
|
|
152
158
|
const response = await actionRequest.execute();
|
|
153
159
|
expect(mockedAxios.patch).toHaveBeenCalled();
|
|
154
160
|
expect(response.data.success).toBe(true);
|
|
@@ -163,7 +169,7 @@ describe('ActionRequest', () => {
|
|
|
163
169
|
false,
|
|
164
170
|
'application/json',
|
|
165
171
|
);
|
|
166
|
-
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method:
|
|
172
|
+
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
|
|
167
173
|
});
|
|
168
174
|
|
|
169
175
|
it('replaces path parameters with values from toolInput', async () => {
|
|
@@ -176,20 +182,21 @@ describe('ActionRequest', () => {
|
|
|
176
182
|
'application/json',
|
|
177
183
|
);
|
|
178
184
|
|
|
179
|
-
|
|
185
|
+
const executor = actionRequest.createExecutor();
|
|
186
|
+
executor.setParams({
|
|
180
187
|
stocksTicker: 'AAPL',
|
|
181
188
|
multiplier: 5,
|
|
182
189
|
startDate: '2023-01-01',
|
|
183
190
|
endDate: '2023-12-31',
|
|
184
191
|
});
|
|
185
192
|
|
|
186
|
-
expect(
|
|
187
|
-
expect(
|
|
193
|
+
expect(executor.path).toBe('/stocks/AAPL/bars/5');
|
|
194
|
+
expect(executor.params).toEqual({
|
|
188
195
|
startDate: '2023-01-01',
|
|
189
196
|
endDate: '2023-12-31',
|
|
190
197
|
});
|
|
191
198
|
|
|
192
|
-
await
|
|
199
|
+
await executor.execute();
|
|
193
200
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/stocks/AAPL/bars/5', {
|
|
194
201
|
headers: expect.anything(),
|
|
195
202
|
params: {
|
|
@@ -209,7 +216,271 @@ describe('ActionRequest', () => {
|
|
|
209
216
|
false,
|
|
210
217
|
'application/json',
|
|
211
218
|
);
|
|
212
|
-
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method:
|
|
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
|
+
});
|
|
213
484
|
});
|
|
214
485
|
});
|
|
215
486
|
|
|
@@ -227,7 +498,8 @@ describe('Authentication Handling', () => {
|
|
|
227
498
|
const api_key = 'user:pass';
|
|
228
499
|
const encodedCredentials = Buffer.from('user:pass').toString('base64');
|
|
229
500
|
|
|
230
|
-
actionRequest.
|
|
501
|
+
const executor = actionRequest.createExecutor();
|
|
502
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
231
503
|
auth: {
|
|
232
504
|
type: AuthTypeEnum.ServiceHttp,
|
|
233
505
|
authorization_type: AuthorizationTypeEnum.Basic,
|
|
@@ -235,13 +507,13 @@ describe('Authentication Handling', () => {
|
|
|
235
507
|
api_key,
|
|
236
508
|
});
|
|
237
509
|
|
|
238
|
-
await
|
|
239
|
-
await actionRequest.execute();
|
|
510
|
+
await executor.execute();
|
|
240
511
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
241
512
|
headers: expect.objectContaining({
|
|
242
513
|
Authorization: `Basic ${encodedCredentials}`,
|
|
514
|
+
'Content-Type': 'application/json',
|
|
243
515
|
}),
|
|
244
|
-
params:
|
|
516
|
+
params: { param1: 'value1' },
|
|
245
517
|
});
|
|
246
518
|
});
|
|
247
519
|
|
|
@@ -254,20 +526,23 @@ describe('Authentication Handling', () => {
|
|
|
254
526
|
false,
|
|
255
527
|
'application/json',
|
|
256
528
|
);
|
|
257
|
-
|
|
529
|
+
|
|
530
|
+
const executor = actionRequest.createExecutor();
|
|
531
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
258
532
|
auth: {
|
|
259
533
|
type: AuthTypeEnum.ServiceHttp,
|
|
260
534
|
authorization_type: AuthorizationTypeEnum.Bearer,
|
|
261
535
|
},
|
|
262
536
|
api_key: 'token123',
|
|
263
537
|
});
|
|
264
|
-
|
|
265
|
-
await
|
|
538
|
+
|
|
539
|
+
await executor.execute();
|
|
266
540
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
267
541
|
headers: expect.objectContaining({
|
|
268
542
|
Authorization: 'Bearer token123',
|
|
543
|
+
'Content-Type': 'application/json',
|
|
269
544
|
}),
|
|
270
|
-
params:
|
|
545
|
+
params: { param1: 'value1' },
|
|
271
546
|
});
|
|
272
547
|
});
|
|
273
548
|
|
|
@@ -280,22 +555,24 @@ describe('Authentication Handling', () => {
|
|
|
280
555
|
false,
|
|
281
556
|
'application/json',
|
|
282
557
|
);
|
|
283
|
-
|
|
284
|
-
actionRequest.
|
|
558
|
+
|
|
559
|
+
const executor = actionRequest.createExecutor();
|
|
560
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
285
561
|
auth: {
|
|
286
|
-
type: AuthTypeEnum.ServiceHttp,
|
|
287
|
-
authorization_type: AuthorizationTypeEnum.Custom,
|
|
562
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
563
|
+
authorization_type: AuthorizationTypeEnum.Custom,
|
|
288
564
|
custom_auth_header: 'X-API-KEY',
|
|
289
565
|
},
|
|
290
566
|
api_key: 'abc123',
|
|
291
567
|
});
|
|
292
|
-
|
|
293
|
-
await
|
|
568
|
+
|
|
569
|
+
await executor.execute();
|
|
294
570
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
295
571
|
headers: expect.objectContaining({
|
|
296
572
|
'X-API-KEY': 'abc123',
|
|
573
|
+
'Content-Type': 'application/json',
|
|
297
574
|
}),
|
|
298
|
-
params:
|
|
575
|
+
params: { param1: 'value1' },
|
|
299
576
|
});
|
|
300
577
|
});
|
|
301
578
|
});
|
|
@@ -306,7 +583,7 @@ describe('resolveRef', () => {
|
|
|
306
583
|
const flowchartRequestRef = (
|
|
307
584
|
openapiSpec.paths['/ai.chatgpt.render-flowchart']?.post
|
|
308
585
|
?.requestBody as OpenAPIV3.RequestBodyObject
|
|
309
|
-
)
|
|
586
|
+
).content['application/json'].schema;
|
|
310
587
|
expect(flowchartRequestRef).toBeDefined();
|
|
311
588
|
const resolvedFlowchartRequest = resolveRef(
|
|
312
589
|
flowchartRequestRef as OpenAPIV3.RequestBodyObject,
|
|
@@ -548,4 +825,273 @@ describe('createURL', () => {
|
|
|
548
825
|
'https://example.com/subdirectory/api/v1/users',
|
|
549
826
|
);
|
|
550
827
|
});
|
|
828
|
+
|
|
829
|
+
describe('openapiToFunction zodSchemas', () => {
|
|
830
|
+
describe('getWeatherOpenapiSpec', () => {
|
|
831
|
+
const { zodSchemas } = openapiToFunction(getWeatherOpenapiSpec, true);
|
|
832
|
+
|
|
833
|
+
it('generates correct Zod schema for GetCurrentWeather', () => {
|
|
834
|
+
expect(zodSchemas).toBeDefined();
|
|
835
|
+
expect(zodSchemas?.GetCurrentWeather).toBeDefined();
|
|
836
|
+
|
|
837
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather;
|
|
838
|
+
|
|
839
|
+
expect(GetCurrentWeatherSchema instanceof z.ZodObject).toBe(true);
|
|
840
|
+
|
|
841
|
+
if (!(GetCurrentWeatherSchema instanceof z.ZodObject)) {
|
|
842
|
+
throw new Error('GetCurrentWeatherSchema is not a ZodObject');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const shape = GetCurrentWeatherSchema.shape;
|
|
846
|
+
expect(shape.location instanceof z.ZodString).toBe(true);
|
|
847
|
+
|
|
848
|
+
// Check locations property
|
|
849
|
+
expect(shape.locations).toBeDefined();
|
|
850
|
+
expect(shape.locations instanceof z.ZodOptional).toBe(true);
|
|
851
|
+
|
|
852
|
+
if (!(shape.locations instanceof z.ZodOptional)) {
|
|
853
|
+
throw new Error('locations is not a ZodOptional');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const locationsInnerType = shape.locations._def.innerType;
|
|
857
|
+
expect(locationsInnerType instanceof z.ZodArray).toBe(true);
|
|
858
|
+
|
|
859
|
+
if (!(locationsInnerType instanceof z.ZodArray)) {
|
|
860
|
+
throw new Error('locationsInnerType is not a ZodArray');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const locationsItemSchema = locationsInnerType.element;
|
|
864
|
+
expect(locationsItemSchema instanceof z.ZodObject).toBe(true);
|
|
865
|
+
|
|
866
|
+
if (!(locationsItemSchema instanceof z.ZodObject)) {
|
|
867
|
+
throw new Error('locationsItemSchema is not a ZodObject');
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Validate the structure of locationsItemSchema
|
|
871
|
+
expect(locationsItemSchema.shape.city instanceof z.ZodString).toBe(true);
|
|
872
|
+
expect(locationsItemSchema.shape.state instanceof z.ZodString).toBe(true);
|
|
873
|
+
expect(locationsItemSchema.shape.countryCode instanceof z.ZodString).toBe(true);
|
|
874
|
+
|
|
875
|
+
// Check if time is optional
|
|
876
|
+
const timeSchema = locationsItemSchema.shape.time;
|
|
877
|
+
expect(timeSchema instanceof z.ZodOptional).toBe(true);
|
|
878
|
+
|
|
879
|
+
if (!(timeSchema instanceof z.ZodOptional)) {
|
|
880
|
+
throw new Error('timeSchema is not a ZodOptional');
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
expect(timeSchema._def.innerType instanceof z.ZodString).toBe(true);
|
|
884
|
+
|
|
885
|
+
// Check the description
|
|
886
|
+
expect(shape.locations._def.description).toBe(
|
|
887
|
+
'A list of locations to retrieve the weather for.',
|
|
888
|
+
);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it('validates correct data for GetCurrentWeather', () => {
|
|
892
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
|
|
893
|
+
const validData = {
|
|
894
|
+
location: 'New York',
|
|
895
|
+
locations: [
|
|
896
|
+
{ city: 'New York', state: 'NY', countryCode: 'US', time: '2023-12-04T14:00:00Z' },
|
|
897
|
+
],
|
|
898
|
+
};
|
|
899
|
+
expect(() => GetCurrentWeatherSchema.parse(validData)).not.toThrow();
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it('throws error for invalid data for GetCurrentWeather', () => {
|
|
903
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
|
|
904
|
+
const invalidData = {
|
|
905
|
+
location: 123,
|
|
906
|
+
locations: [{ city: 'New York', state: 'NY', countryCode: 'US', time: 'invalid-time' }],
|
|
907
|
+
};
|
|
908
|
+
expect(() => GetCurrentWeatherSchema.parse(invalidData)).toThrow();
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
describe('whimsicalOpenapiSpec', () => {
|
|
913
|
+
const { zodSchemas } = openapiToFunction(whimsicalOpenapiSpec, true);
|
|
914
|
+
|
|
915
|
+
it('generates correct Zod schema for postRenderFlowchart', () => {
|
|
916
|
+
expect(zodSchemas).toBeDefined();
|
|
917
|
+
expect(zodSchemas?.postRenderFlowchart).toBeDefined();
|
|
918
|
+
|
|
919
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
920
|
+
expect(PostRenderFlowchartSchema).toBeInstanceOf(z.ZodObject);
|
|
921
|
+
|
|
922
|
+
if (!(PostRenderFlowchartSchema instanceof z.ZodObject)) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const shape = PostRenderFlowchartSchema.shape;
|
|
927
|
+
expect(shape.mermaid).toBeInstanceOf(z.ZodString);
|
|
928
|
+
expect(shape.title).toBeInstanceOf(z.ZodOptional);
|
|
929
|
+
expect((shape.title as z.ZodOptional<z.ZodString>)._def.innerType).toBeInstanceOf(
|
|
930
|
+
z.ZodString,
|
|
931
|
+
);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
it('validates correct data for postRenderFlowchart', () => {
|
|
935
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
936
|
+
const validData = {
|
|
937
|
+
mermaid: 'graph TD; A-->B; B-->C; C-->D;',
|
|
938
|
+
title: 'Test Flowchart',
|
|
939
|
+
};
|
|
940
|
+
expect(() => PostRenderFlowchartSchema?.parse(validData)).not.toThrow();
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('throws error for invalid data for postRenderFlowchart', () => {
|
|
944
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
945
|
+
const invalidData = {
|
|
946
|
+
mermaid: 123,
|
|
947
|
+
title: 42,
|
|
948
|
+
};
|
|
949
|
+
expect(() => PostRenderFlowchartSchema?.parse(invalidData)).toThrow();
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
describe('scholarAIOpenapiSpec', () => {
|
|
954
|
+
const result = validateAndParseOpenAPISpec(scholarAIOpenapiSpec);
|
|
955
|
+
const spec = result.spec as OpenAPIV3.Document;
|
|
956
|
+
const { zodSchemas } = openapiToFunction(spec, true);
|
|
957
|
+
|
|
958
|
+
it('generates correct Zod schema for searchAbstracts', () => {
|
|
959
|
+
expect(zodSchemas).toBeDefined();
|
|
960
|
+
expect(zodSchemas?.searchAbstracts).toBeDefined();
|
|
961
|
+
|
|
962
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
963
|
+
expect(SearchAbstractsSchema).toBeInstanceOf(z.ZodObject);
|
|
964
|
+
|
|
965
|
+
if (!(SearchAbstractsSchema instanceof z.ZodObject)) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const shape = SearchAbstractsSchema.shape;
|
|
970
|
+
expect(shape.keywords).toBeInstanceOf(z.ZodString);
|
|
971
|
+
expect(shape.sort).toBeInstanceOf(z.ZodOptional);
|
|
972
|
+
expect(
|
|
973
|
+
(shape.sort as z.ZodOptional<z.ZodEnum<[string, ...string[]]>>)._def.innerType,
|
|
974
|
+
).toBeInstanceOf(z.ZodEnum);
|
|
975
|
+
expect(shape.query).toBeInstanceOf(z.ZodString);
|
|
976
|
+
expect(shape.peer_reviewed_only).toBeInstanceOf(z.ZodOptional);
|
|
977
|
+
expect(shape.start_year).toBeInstanceOf(z.ZodOptional);
|
|
978
|
+
expect(shape.end_year).toBeInstanceOf(z.ZodOptional);
|
|
979
|
+
expect(shape.offset).toBeInstanceOf(z.ZodOptional);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it('validates correct data for searchAbstracts', () => {
|
|
983
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
984
|
+
const validData = {
|
|
985
|
+
keywords: 'machine learning',
|
|
986
|
+
sort: 'cited_by_count',
|
|
987
|
+
query: 'AI applications',
|
|
988
|
+
peer_reviewed_only: 'true',
|
|
989
|
+
start_year: '2020',
|
|
990
|
+
end_year: '2023',
|
|
991
|
+
offset: '0',
|
|
992
|
+
};
|
|
993
|
+
expect(() => SearchAbstractsSchema?.parse(validData)).not.toThrow();
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('throws error for invalid data for searchAbstracts', () => {
|
|
997
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
998
|
+
const invalidData = {
|
|
999
|
+
keywords: 123,
|
|
1000
|
+
sort: 'invalid_sort',
|
|
1001
|
+
query: 42,
|
|
1002
|
+
peer_reviewed_only: 'maybe',
|
|
1003
|
+
start_year: 2020,
|
|
1004
|
+
end_year: 2023,
|
|
1005
|
+
offset: 0,
|
|
1006
|
+
};
|
|
1007
|
+
expect(() => SearchAbstractsSchema?.parse(invalidData)).toThrow();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it('generates correct Zod schema for getFullText', () => {
|
|
1011
|
+
expect(zodSchemas?.getFullText).toBeDefined();
|
|
1012
|
+
|
|
1013
|
+
const GetFullTextSchema = zodSchemas?.getFullText;
|
|
1014
|
+
expect(GetFullTextSchema).toBeInstanceOf(z.ZodObject);
|
|
1015
|
+
|
|
1016
|
+
if (!(GetFullTextSchema instanceof z.ZodObject)) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const shape = GetFullTextSchema.shape;
|
|
1021
|
+
expect(shape.pdf_url).toBeInstanceOf(z.ZodString);
|
|
1022
|
+
expect(shape.chunk).toBeInstanceOf(z.ZodOptional);
|
|
1023
|
+
expect((shape.chunk as z.ZodOptional<z.ZodNumber>)._def.innerType).toBeInstanceOf(
|
|
1024
|
+
z.ZodNumber,
|
|
1025
|
+
);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
it('generates correct Zod schema for saveCitation', () => {
|
|
1029
|
+
expect(zodSchemas?.saveCitation).toBeDefined();
|
|
1030
|
+
|
|
1031
|
+
const SaveCitationSchema = zodSchemas?.saveCitation;
|
|
1032
|
+
expect(SaveCitationSchema).toBeInstanceOf(z.ZodObject);
|
|
1033
|
+
|
|
1034
|
+
if (!(SaveCitationSchema instanceof z.ZodObject)) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const shape = SaveCitationSchema.shape;
|
|
1039
|
+
expect(shape.doi).toBeInstanceOf(z.ZodString);
|
|
1040
|
+
expect(shape.zotero_user_id).toBeInstanceOf(z.ZodString);
|
|
1041
|
+
expect(shape.zotero_api_key).toBeInstanceOf(z.ZodString);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
describe('openapiToFunction zodSchemas for SWAPI', () => {
|
|
1047
|
+
const result = validateAndParseOpenAPISpec(swapidev);
|
|
1048
|
+
const spec = result.spec as OpenAPIV3.Document;
|
|
1049
|
+
const { zodSchemas } = openapiToFunction(spec, true);
|
|
1050
|
+
|
|
1051
|
+
describe('getPeople schema', () => {
|
|
1052
|
+
it('does not generate Zod schema for getPeople (no parameters)', () => {
|
|
1053
|
+
expect(zodSchemas).toBeDefined();
|
|
1054
|
+
expect(zodSchemas?.getPeople).toBeUndefined();
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it('validates correct data for getPeople', () => {
|
|
1058
|
+
const GetPeopleSchema = zodSchemas?.getPeople;
|
|
1059
|
+
expect(GetPeopleSchema).toBeUndefined();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it('does not throw for invalid data for getPeople', () => {
|
|
1063
|
+
const GetPeopleSchema = zodSchemas?.getPeople;
|
|
1064
|
+
expect(GetPeopleSchema).toBeUndefined();
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
describe('getPersonById schema', () => {
|
|
1069
|
+
it('generates correct Zod schema for getPersonById', () => {
|
|
1070
|
+
expect(zodSchemas).toBeDefined();
|
|
1071
|
+
expect(zodSchemas?.getPersonById).toBeDefined();
|
|
1072
|
+
|
|
1073
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1074
|
+
expect(GetPersonByIdSchema).toBeInstanceOf(z.ZodObject);
|
|
1075
|
+
|
|
1076
|
+
if (!(GetPersonByIdSchema instanceof z.ZodObject)) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const shape = GetPersonByIdSchema.shape;
|
|
1081
|
+
expect(shape.id).toBeInstanceOf(z.ZodString);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
it('validates correct data for getPersonById', () => {
|
|
1085
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1086
|
+
const validData = { id: '1' };
|
|
1087
|
+
expect(() => GetPersonByIdSchema?.parse(validData)).not.toThrow();
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it('throws error for invalid data for getPersonById', () => {
|
|
1091
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1092
|
+
const invalidData = { id: 1 }; // should be string
|
|
1093
|
+
expect(() => GetPersonByIdSchema?.parse(invalidData)).toThrow();
|
|
1094
|
+
});
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
551
1097
|
});
|