librechat-data-provider 0.8.402 → 0.8.404

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.
Files changed (109) hide show
  1. package/dist/index.es.js +1 -1
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/react-query/index.es.js +1 -1
  6. package/dist/react-query/index.es.js.map +1 -1
  7. package/dist/types/accessPermissions.d.ts +744 -0
  8. package/dist/types/actions.d.ts +118 -0
  9. package/dist/types/api-endpoints.d.ts +150 -0
  10. package/dist/types/artifacts.d.ts +97 -0
  11. package/dist/types/azure.d.ts +22 -0
  12. package/dist/types/bedrock.d.ts +1220 -0
  13. package/dist/types/config.d.ts +14849 -0
  14. package/dist/types/config.spec.d.ts +1 -0
  15. package/dist/types/createPayload.d.ts +5 -0
  16. package/dist/types/data-service.d.ts +287 -0
  17. package/dist/types/feedback.d.ts +36 -0
  18. package/dist/types/file-config.d.ts +263 -0
  19. package/dist/types/file-config.spec.d.ts +1 -0
  20. package/dist/types/generate.d.ts +597 -0
  21. package/dist/types/headers-helpers.d.ts +2 -0
  22. package/{src/index.ts → dist/types/index.d.ts} +0 -15
  23. package/dist/types/keys.d.ts +92 -0
  24. package/dist/types/mcp.d.ts +2760 -0
  25. package/dist/types/messages.d.ts +10 -0
  26. package/dist/types/models.d.ts +1547 -0
  27. package/dist/types/parameterSettings.d.ts +69 -0
  28. package/dist/types/parsers.d.ts +110 -0
  29. package/dist/types/permissions.d.ts +522 -0
  30. package/dist/types/react-query/react-query-service.d.ts +85 -0
  31. package/dist/types/request.d.ts +25 -0
  32. package/dist/types/roles.d.ts +554 -0
  33. package/dist/types/roles.spec.d.ts +1 -0
  34. package/dist/types/schemas.d.ts +5110 -0
  35. package/dist/types/schemas.spec.d.ts +1 -0
  36. package/dist/types/types/agents.d.ts +433 -0
  37. package/dist/types/types/assistants.d.ts +547 -0
  38. package/dist/types/types/files.d.ts +172 -0
  39. package/dist/types/types/graph.d.ts +135 -0
  40. package/{src/types/mcpServers.ts → dist/types/types/mcpServers.d.ts} +12 -18
  41. package/dist/types/types/mutations.d.ts +209 -0
  42. package/dist/types/types/queries.d.ts +169 -0
  43. package/dist/types/types/runs.d.ts +36 -0
  44. package/dist/types/types/web.d.ts +520 -0
  45. package/dist/types/types.d.ts +503 -0
  46. package/dist/types/utils.d.ts +12 -0
  47. package/package.json +5 -1
  48. package/babel.config.js +0 -4
  49. package/check_updates.sh +0 -52
  50. package/jest.config.js +0 -19
  51. package/react-query/package-lock.json +0 -292
  52. package/react-query/package.json +0 -10
  53. package/rollup.config.js +0 -74
  54. package/server-rollup.config.js +0 -40
  55. package/specs/actions.spec.ts +0 -2533
  56. package/specs/api-endpoints-subdir.spec.ts +0 -140
  57. package/specs/api-endpoints.spec.ts +0 -74
  58. package/specs/azure.spec.ts +0 -844
  59. package/specs/bedrock.spec.ts +0 -862
  60. package/specs/filetypes.spec.ts +0 -175
  61. package/specs/generate.spec.ts +0 -770
  62. package/specs/headers-helpers.spec.ts +0 -24
  63. package/specs/mcp.spec.ts +0 -147
  64. package/specs/openapiSpecs.ts +0 -524
  65. package/specs/parsers.spec.ts +0 -601
  66. package/specs/request-interceptor.spec.ts +0 -304
  67. package/specs/utils.spec.ts +0 -196
  68. package/src/accessPermissions.ts +0 -346
  69. package/src/actions.ts +0 -813
  70. package/src/api-endpoints.ts +0 -440
  71. package/src/artifacts.ts +0 -3104
  72. package/src/azure.ts +0 -328
  73. package/src/bedrock.ts +0 -425
  74. package/src/config.spec.ts +0 -315
  75. package/src/config.ts +0 -2006
  76. package/src/createPayload.ts +0 -46
  77. package/src/data-service.ts +0 -1087
  78. package/src/feedback.ts +0 -141
  79. package/src/file-config.spec.ts +0 -1248
  80. package/src/file-config.ts +0 -764
  81. package/src/generate.ts +0 -634
  82. package/src/headers-helpers.ts +0 -13
  83. package/src/keys.ts +0 -99
  84. package/src/mcp.ts +0 -271
  85. package/src/messages.ts +0 -50
  86. package/src/models.ts +0 -69
  87. package/src/parameterSettings.ts +0 -1111
  88. package/src/parsers.ts +0 -563
  89. package/src/permissions.ts +0 -188
  90. package/src/react-query/react-query-service.ts +0 -566
  91. package/src/request.ts +0 -171
  92. package/src/roles.spec.ts +0 -132
  93. package/src/roles.ts +0 -225
  94. package/src/schemas.spec.ts +0 -355
  95. package/src/schemas.ts +0 -1234
  96. package/src/types/agents.ts +0 -470
  97. package/src/types/assistants.ts +0 -654
  98. package/src/types/files.ts +0 -191
  99. package/src/types/graph.ts +0 -145
  100. package/src/types/mutations.ts +0 -422
  101. package/src/types/queries.ts +0 -208
  102. package/src/types/runs.ts +0 -40
  103. package/src/types/web.ts +0 -588
  104. package/src/types.ts +0 -676
  105. package/src/utils.ts +0 -85
  106. package/tsconfig.json +0 -28
  107. package/tsconfig.spec.json +0 -10
  108. /package/{src/react-query/index.ts → dist/types/react-query/index.d.ts} +0 -0
  109. /package/{src/types/index.ts → dist/types/types/index.d.ts} +0 -0
package/src/actions.ts DELETED
@@ -1,813 +0,0 @@
1
- import { z } from 'zod';
2
- import { URL } from 'url';
3
- import _axios from 'axios';
4
- import crypto from 'crypto';
5
- import { load } from 'js-yaml';
6
- import type { ActionMetadata, ActionMetadataRuntime } from './types/agents';
7
- import type { FunctionTool, Schema, Reference } from './types/assistants';
8
- import { AuthTypeEnum, AuthorizationTypeEnum } from './types/agents';
9
- import type { OpenAPIV3 } from 'openapi-types';
10
- import { Tools } from './types/assistants';
11
-
12
- export type ParametersSchema = {
13
- type: string;
14
- properties: Record<string, Reference | Schema>;
15
- required: string[];
16
- additionalProperties?: boolean;
17
- };
18
-
19
- export type OpenAPISchema = OpenAPIV3.SchemaObject &
20
- ParametersSchema & {
21
- items?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject;
22
- };
23
-
24
- export type ApiKeyCredentials = {
25
- api_key: string;
26
- custom_auth_header?: string;
27
- authorization_type?: AuthorizationTypeEnum;
28
- };
29
-
30
- export type OAuthCredentials = {
31
- tokenUrl: string;
32
- clientId: string;
33
- clientSecret: string;
34
- scope: string;
35
- };
36
-
37
- export type Credentials = ApiKeyCredentials | OAuthCredentials;
38
-
39
- type MediaTypeObject =
40
- | undefined
41
- | {
42
- [media: string]: OpenAPIV3.MediaTypeObject | undefined;
43
- };
44
-
45
- type RequestBodyObject = Omit<OpenAPIV3.RequestBodyObject, 'content'> & {
46
- content: MediaTypeObject;
47
- };
48
-
49
- export function sha1(input: string) {
50
- return crypto.createHash('sha1').update(input).digest('hex');
51
- }
52
-
53
- export function createURL(domain: string, path: string) {
54
- const cleanDomain = domain.replace(/\/$/, '');
55
- const cleanPath = path.replace(/^\//, '');
56
- const fullURL = `${cleanDomain}/${cleanPath}`;
57
- return new URL(fullURL).toString();
58
- }
59
-
60
- const schemaTypeHandlers: Record<string, (schema: OpenAPISchema) => z.ZodTypeAny> = {
61
- string: (schema) => {
62
- if (schema.enum) {
63
- return z.enum(schema.enum as [string, ...string[]]);
64
- }
65
-
66
- let stringSchema = z.string();
67
- if (schema.minLength !== undefined) {
68
- stringSchema = stringSchema.min(schema.minLength);
69
- }
70
- if (schema.maxLength !== undefined) {
71
- stringSchema = stringSchema.max(schema.maxLength);
72
- }
73
- return stringSchema;
74
- },
75
- number: (schema) => {
76
- let numberSchema = z.number();
77
- if (schema.minimum !== undefined) {
78
- numberSchema = numberSchema.min(schema.minimum);
79
- }
80
- if (schema.maximum !== undefined) {
81
- numberSchema = numberSchema.max(schema.maximum);
82
- }
83
- return numberSchema;
84
- },
85
- integer: (schema) => (schemaTypeHandlers.number(schema) as z.ZodNumber).int(),
86
- boolean: () => z.boolean(),
87
- array: (schema) => {
88
- if (schema.items) {
89
- const zodSchema = openAPISchemaToZod(schema.items as OpenAPISchema);
90
- if (zodSchema) {
91
- return z.array(zodSchema);
92
- }
93
-
94
- return z.array(z.unknown());
95
- }
96
- return z.array(z.unknown());
97
- },
98
- object: (schema) => {
99
- const shape: { [key: string]: z.ZodTypeAny } = {};
100
- if (schema.properties) {
101
- Object.entries(schema.properties).forEach(([key, value]) => {
102
- const zodSchema = openAPISchemaToZod(value as OpenAPISchema);
103
- shape[key] = zodSchema || z.unknown();
104
- if (schema.required && schema.required.includes(key)) {
105
- shape[key] = shape[key].describe(value.description || '');
106
- } else {
107
- shape[key] = shape[key].optional().describe(value.description || '');
108
- }
109
- });
110
- }
111
- return z.object(shape);
112
- },
113
- };
114
-
115
- function openAPISchemaToZod(schema: OpenAPISchema): z.ZodTypeAny | undefined {
116
- if (schema.type === 'object' && Object.keys(schema.properties || {}).length === 0) {
117
- return undefined;
118
- }
119
-
120
- const handler = schemaTypeHandlers[schema.type as string] || (() => z.unknown());
121
- return handler(schema);
122
- }
123
-
124
- /**
125
- * Class representing a function signature.
126
- */
127
- export class FunctionSignature {
128
- name: string;
129
- description: string;
130
- parameters: ParametersSchema;
131
- strict: boolean;
132
-
133
- constructor(name: string, description: string, parameters: ParametersSchema, strict?: boolean) {
134
- this.name = name;
135
- this.description = description;
136
- this.parameters = parameters;
137
- this.strict = strict ?? false;
138
- }
139
-
140
- toObjectTool(): FunctionTool {
141
- const parameters = {
142
- ...this.parameters,
143
- additionalProperties: this.strict ? false : undefined,
144
- };
145
-
146
- return {
147
- type: Tools.function,
148
- function: {
149
- name: this.name,
150
- description: this.description,
151
- parameters,
152
- ...(this.strict ? { strict: this.strict } : {}),
153
- },
154
- };
155
- }
156
- }
157
-
158
- class RequestConfig {
159
- constructor(
160
- readonly domain: string,
161
- readonly basePath: string,
162
- readonly method: string,
163
- readonly operation: string,
164
- readonly isConsequential: boolean,
165
- readonly contentType: string,
166
- readonly parameterLocations?: Record<string, 'query' | 'path' | 'header' | 'body'>,
167
- ) {}
168
- }
169
-
170
- class RequestExecutor {
171
- path: string;
172
- params?: Record<string, unknown>;
173
- private operationHash?: string;
174
- private authHeaders: Record<string, string> = {};
175
- private authToken?: string;
176
-
177
- constructor(private config: RequestConfig) {
178
- this.path = config.basePath;
179
- }
180
-
181
- setParams(params: Record<string, unknown>) {
182
- this.operationHash = sha1(JSON.stringify(params));
183
- this.params = { ...params } as Record<string, unknown>;
184
- if (this.config.parameterLocations) {
185
- //Substituting “Path” Parameters:
186
- for (const [key, value] of Object.entries(params)) {
187
- if (this.config.parameterLocations[key] === 'path') {
188
- const paramPattern = `{${key}}`;
189
- if (this.path.includes(paramPattern)) {
190
- this.path = this.path.replace(paramPattern, encodeURIComponent(String(value)));
191
- delete this.params[key];
192
- }
193
- }
194
- }
195
- } else {
196
- // Fallback: if no locations are defined, perform path substitution for all keys.
197
- for (const [key, value] of Object.entries(params)) {
198
- const paramPattern = `{${key}}`;
199
- if (this.path.includes(paramPattern)) {
200
- this.path = this.path.replace(paramPattern, encodeURIComponent(String(value)));
201
- delete this.params[key];
202
- }
203
- }
204
- }
205
- return this;
206
- }
207
-
208
- async setAuth(metadata: ActionMetadataRuntime) {
209
- if (!metadata.auth) {
210
- return this;
211
- }
212
-
213
- const {
214
- type,
215
- /* API Key */
216
- authorization_type,
217
- custom_auth_header,
218
- /* OAuth */
219
- authorization_url,
220
- client_url,
221
- scope,
222
- token_exchange_method,
223
- } = metadata.auth;
224
-
225
- const {
226
- /* API Key */
227
- api_key,
228
- /* OAuth */
229
- oauth_client_id,
230
- oauth_client_secret,
231
- oauth_token_expires_at,
232
- oauth_access_token = '',
233
- } = metadata;
234
-
235
- const isApiKey = api_key != null && api_key.length > 0 && type === AuthTypeEnum.ServiceHttp;
236
- const isOAuth = !!(
237
- oauth_client_id != null &&
238
- oauth_client_id &&
239
- oauth_client_secret != null &&
240
- oauth_client_secret &&
241
- type === AuthTypeEnum.OAuth &&
242
- authorization_url != null &&
243
- authorization_url &&
244
- client_url != null &&
245
- client_url &&
246
- scope != null &&
247
- scope &&
248
- token_exchange_method
249
- );
250
-
251
- if (isApiKey && authorization_type === AuthorizationTypeEnum.Basic) {
252
- const basicToken = Buffer.from(api_key).toString('base64');
253
- this.authHeaders['Authorization'] = `Basic ${basicToken}`;
254
- } else if (isApiKey && authorization_type === AuthorizationTypeEnum.Bearer) {
255
- this.authHeaders['Authorization'] = `Bearer ${api_key}`;
256
- } else if (
257
- isApiKey &&
258
- authorization_type === AuthorizationTypeEnum.Custom &&
259
- custom_auth_header != null &&
260
- custom_auth_header
261
- ) {
262
- this.authHeaders[custom_auth_header] = api_key;
263
- } else if (isOAuth) {
264
- // TODO: maybe doing it in a different way later on. but we want that the user needs to folllow the oauth flow.
265
- // If we do not have a valid token, bail or ask user to sign in
266
- const now = new Date();
267
-
268
- // 1. Check if token is set
269
- if (!oauth_access_token) {
270
- throw new Error('No access token found. Please log in first.');
271
- }
272
-
273
- // 2. Check if token is expired
274
- if (oauth_token_expires_at && now >= new Date(oauth_token_expires_at)) {
275
- // Optionally check refresh_token logic, or just prompt user to re-login
276
- throw new Error('Access token is expired. Please re-login.');
277
- }
278
-
279
- // If valid, use it
280
- this.authToken = oauth_access_token;
281
- this.authHeaders['Authorization'] = `Bearer ${this.authToken}`;
282
- }
283
- return this;
284
- }
285
-
286
- async execute(options?: { httpAgent?: unknown; httpsAgent?: unknown }) {
287
- const url = createURL(this.config.domain, this.path);
288
- const headers: Record<string, string> = {
289
- ...this.authHeaders,
290
- ...(this.config.contentType ? { 'Content-Type': this.config.contentType } : {}),
291
- };
292
- const method = this.config.method.toLowerCase();
293
-
294
- /**
295
- * SECURITY: Disable automatic redirects to prevent SSRF bypass.
296
- * Attackers could use redirects to access internal services:
297
- * 1. Set action URL to allowed external domain
298
- * 2. External domain redirects to internal service (e.g., 127.0.0.1, rag_api)
299
- * 3. Without this protection, axios would follow the redirect
300
- *
301
- * By setting maxRedirects: 0, we prevent this attack vector.
302
- * The action will receive the redirect response (3xx) instead of following it.
303
- *
304
- * SECURITY: When httpAgent/httpsAgent are provided (SSRF-safe agents), they validate
305
- * the DNS-resolved IP at TCP connect time, preventing TOCTOU DNS rebinding attacks.
306
- */
307
- const axios = _axios.create({
308
- maxRedirects: 0,
309
- validateStatus: (status) => status >= 200 && status < 400,
310
- ...(options?.httpAgent != null ? { httpAgent: options.httpAgent } : {}),
311
- ...(options?.httpsAgent != null ? { httpsAgent: options.httpsAgent } : {}),
312
- });
313
-
314
- // Initialize separate containers for query and body parameters.
315
- const queryParams: Record<string, unknown> = {};
316
- const bodyParams: Record<string, unknown> = {};
317
-
318
- if (this.config.parameterLocations && this.params) {
319
- for (const key of Object.keys(this.params)) {
320
- // Determine parameter placement; default to "query" for GET and "body" for others.
321
- const loc: 'query' | 'path' | 'header' | 'body' =
322
- this.config.parameterLocations[key] || (method === 'get' ? 'query' : 'body');
323
-
324
- const val = this.params[key];
325
- if (loc === 'query') {
326
- queryParams[key] = val;
327
- } else if (loc === 'header') {
328
- headers[key] = String(val);
329
- } else if (loc === 'body') {
330
- bodyParams[key] = val;
331
- }
332
- }
333
- } else if (this.params) {
334
- Object.assign(queryParams, this.params);
335
- Object.assign(bodyParams, this.params);
336
- }
337
-
338
- if (method === 'get') {
339
- return axios.get(url, { headers, params: queryParams });
340
- } else if (method === 'post') {
341
- return axios.post(url, bodyParams, { headers, params: queryParams });
342
- } else if (method === 'put') {
343
- return axios.put(url, bodyParams, { headers, params: queryParams });
344
- } else if (method === 'delete') {
345
- return axios.delete(url, { headers, data: bodyParams, params: queryParams });
346
- } else if (method === 'patch') {
347
- return axios.patch(url, bodyParams, { headers, params: queryParams });
348
- } else {
349
- throw new Error(`Unsupported HTTP method: ${method}`);
350
- }
351
- }
352
-
353
- getConfig() {
354
- return this.config;
355
- }
356
- }
357
-
358
- export class ActionRequest {
359
- private config: RequestConfig;
360
-
361
- constructor(
362
- domain: string,
363
- path: string,
364
- method: string,
365
- operation: string,
366
- isConsequential: boolean,
367
- contentType: string,
368
- parameterLocations?: Record<string, 'query' | 'path' | 'header' | 'body'>,
369
- ) {
370
- this.config = new RequestConfig(
371
- domain,
372
- path,
373
- method,
374
- operation,
375
- isConsequential,
376
- contentType,
377
- parameterLocations,
378
- );
379
- }
380
-
381
- // Add getters to maintain backward compatibility
382
- get domain() {
383
- return this.config.domain;
384
- }
385
- get path() {
386
- return this.config.basePath;
387
- }
388
- get method() {
389
- return this.config.method;
390
- }
391
- get operation() {
392
- return this.config.operation;
393
- }
394
- get isConsequential() {
395
- return this.config.isConsequential;
396
- }
397
- get contentType() {
398
- return this.config.contentType;
399
- }
400
-
401
- createExecutor() {
402
- return new RequestExecutor(this.config);
403
- }
404
-
405
- // Maintain backward compatibility by delegating to a new executor
406
- setParams(params: Record<string, unknown>) {
407
- const executor = this.createExecutor();
408
- executor.setParams(params);
409
- return executor;
410
- }
411
-
412
- async setAuth(metadata: ActionMetadata) {
413
- const executor = this.createExecutor();
414
- return executor.setAuth(metadata);
415
- }
416
-
417
- async execute() {
418
- const executor = this.createExecutor();
419
- return executor.execute();
420
- }
421
- }
422
-
423
- export function resolveRef<
424
- T extends
425
- | OpenAPIV3.ReferenceObject
426
- | OpenAPIV3.SchemaObject
427
- | OpenAPIV3.ParameterObject
428
- | OpenAPIV3.RequestBodyObject,
429
- >(obj: T, components?: OpenAPIV3.ComponentsObject): Exclude<T, OpenAPIV3.ReferenceObject> {
430
- if ('$ref' in obj && components) {
431
- const refPath = obj.$ref.replace(/^#\/components\//, '').split('/');
432
-
433
- let resolved: unknown = components as Record<string, unknown>;
434
- for (const segment of refPath) {
435
- if (typeof resolved === 'object' && resolved !== null && segment in resolved) {
436
- resolved = (resolved as Record<string, unknown>)[segment];
437
- } else {
438
- throw new Error(`Could not resolve reference: ${obj.$ref}`);
439
- }
440
- }
441
-
442
- return resolveRef(resolved as typeof obj, components) as Exclude<T, OpenAPIV3.ReferenceObject>;
443
- }
444
-
445
- return obj as Exclude<T, OpenAPIV3.ReferenceObject>;
446
- }
447
-
448
- function sanitizeOperationId(input: string) {
449
- return input.replace(/[^a-zA-Z0-9_-]/g, '');
450
- }
451
-
452
- /**
453
- * Converts an OpenAPI spec to function signatures and request builders.
454
- */
455
- export function openapiToFunction(
456
- openapiSpec: OpenAPIV3.Document,
457
- generateZodSchemas = false,
458
- ): {
459
- functionSignatures: FunctionSignature[];
460
- requestBuilders: Record<string, ActionRequest>;
461
- zodSchemas?: Record<string, z.ZodTypeAny>;
462
- } {
463
- const functionSignatures: FunctionSignature[] = [];
464
- const requestBuilders: Record<string, ActionRequest> = {};
465
- const zodSchemas: Record<string, z.ZodTypeAny> = {};
466
- const baseUrl = openapiSpec.servers?.[0]?.url ?? '';
467
-
468
- // Iterate over each path and method in the OpenAPI spec
469
- for (const [path, methods] of Object.entries(openapiSpec.paths)) {
470
- for (const [method, operation] of Object.entries(methods as OpenAPIV3.PathsObject)) {
471
- const paramLocations: Record<string, 'query' | 'path' | 'header' | 'body'> = {};
472
- const operationObj = operation as OpenAPIV3.OperationObject & {
473
- 'x-openai-isConsequential'?: boolean;
474
- } & {
475
- 'x-strict'?: boolean;
476
- };
477
-
478
- // Operation ID is used as the function name
479
- const defaultOperationId = `${method}_${path}`;
480
- const operationId = operationObj.operationId || sanitizeOperationId(defaultOperationId);
481
- const description = operationObj.summary || operationObj.description || '';
482
- const isStrict = operationObj['x-strict'] ?? false;
483
-
484
- const parametersSchema: OpenAPISchema = {
485
- type: 'object',
486
- properties: {},
487
- required: [],
488
- };
489
-
490
- if (operationObj.parameters) {
491
- for (const param of operationObj.parameters ?? []) {
492
- const resolvedParam = resolveRef(
493
- param,
494
- openapiSpec.components,
495
- ) as OpenAPIV3.ParameterObject;
496
-
497
- const paramName = resolvedParam.name;
498
- if (!paramName || !resolvedParam.schema) {
499
- continue;
500
- }
501
-
502
- const paramSchema = resolveRef(
503
- resolvedParam.schema,
504
- openapiSpec.components,
505
- ) as OpenAPIV3.SchemaObject;
506
-
507
- parametersSchema.properties[paramName] = paramSchema;
508
- if (resolvedParam.required) {
509
- parametersSchema.required.push(paramName);
510
- }
511
- // Record the parameter location from the OpenAPI "in" field.
512
- paramLocations[paramName] =
513
- resolvedParam.in === 'query' ||
514
- resolvedParam.in === 'path' ||
515
- resolvedParam.in === 'header' ||
516
- resolvedParam.in === 'body'
517
- ? resolvedParam.in
518
- : 'query';
519
- }
520
- }
521
-
522
- let contentType = '';
523
- if (operationObj.requestBody) {
524
- const requestBody = operationObj.requestBody as RequestBodyObject;
525
- const content = requestBody.content;
526
- contentType = Object.keys(content ?? {})[0];
527
- const schema = content?.[contentType]?.schema;
528
- const resolvedSchema = resolveRef(
529
- schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
530
- openapiSpec.components,
531
- );
532
- parametersSchema.properties = {
533
- ...parametersSchema.properties,
534
- ...resolvedSchema.properties,
535
- };
536
- if (resolvedSchema.required) {
537
- parametersSchema.required.push(...resolvedSchema.required);
538
- }
539
- // Mark requestBody properties as belonging to the "body"
540
- if (resolvedSchema.properties) {
541
- for (const key in resolvedSchema.properties) {
542
- paramLocations[key] = 'body';
543
- }
544
- }
545
-
546
- contentType = contentType ?? 'application/json';
547
- }
548
-
549
- const functionSignature = new FunctionSignature(
550
- operationId,
551
- description,
552
- parametersSchema,
553
- isStrict,
554
- );
555
- functionSignatures.push(functionSignature);
556
-
557
- const actionRequest = new ActionRequest(
558
- baseUrl,
559
- path,
560
- method,
561
- operationId,
562
- !!(operationObj['x-openai-isConsequential'] ?? false),
563
- contentType,
564
- paramLocations,
565
- );
566
-
567
- requestBuilders[operationId] = actionRequest;
568
-
569
- if (generateZodSchemas && Object.keys(parametersSchema.properties).length > 0) {
570
- const schema = openAPISchemaToZod(parametersSchema);
571
- if (schema) {
572
- zodSchemas[operationId] = schema;
573
- }
574
- }
575
- }
576
- }
577
-
578
- return { functionSignatures, requestBuilders, zodSchemas };
579
- }
580
-
581
- export type ValidationResult = {
582
- status: boolean;
583
- message: string;
584
- spec?: OpenAPIV3.Document;
585
- serverUrl?: string;
586
- };
587
-
588
- /**
589
- * Cross-platform IP validation (works in Node.js and browser).
590
- * @param input - String to check if it's an IP address
591
- * @returns 0 if not IP, 4 for IPv4, 6 for IPv6
592
- */
593
- function isIP(input: string): number {
594
- // IPv4 regex - matches 0.0.0.0 to 255.255.255.255
595
- const ipv4Regex =
596
- /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
597
-
598
- if (ipv4Regex.test(input)) {
599
- return 4;
600
- }
601
-
602
- // IPv6 regex - simplified but covers most cases
603
- // Handles compressed (::), full, and mixed notations
604
- const ipv6Regex =
605
- /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
606
-
607
- if (ipv6Regex.test(input)) {
608
- return 6;
609
- }
610
-
611
- return 0;
612
- }
613
-
614
- /**
615
- * Extracts domain from URL (protocol + hostname).
616
- * @param url - URL to extract from
617
- * @returns Protocol and hostname (e.g., "https://example.com")
618
- */
619
- export function extractDomainFromUrl(url: string): string {
620
- try {
621
- /** Parsed URL object */
622
- const parsedUrl = new URL(url);
623
- // Preserve brackets for IPv6 addresses using isIP
624
- const ipVersion = isIP(parsedUrl.hostname);
625
- const hostname = ipVersion === 6 ? `[${parsedUrl.hostname}]` : parsedUrl.hostname;
626
- return `${parsedUrl.protocol}//${hostname}`;
627
- } catch {
628
- throw new Error(`Invalid URL format: ${url}`);
629
- }
630
- }
631
-
632
- export type DomainValidationResult = {
633
- isValid: boolean;
634
- message?: string;
635
- normalizedSpecDomain?: string;
636
- normalizedClientDomain?: string;
637
- };
638
-
639
- /**
640
- * Validates client domain matches OpenAPI spec server URL domain (SSRF prevention).
641
- * @param clientProvidedDomain - Domain from client (with/without protocol)
642
- * @param specServerUrl - Server URL from OpenAPI spec
643
- * @returns Validation result with normalized domains
644
- */
645
- export function validateActionDomain(
646
- clientProvidedDomain: string,
647
- specServerUrl: string,
648
- ): DomainValidationResult {
649
- try {
650
- /** Parsed spec URL */
651
- const specUrl = new URL(specServerUrl);
652
-
653
- if (specUrl.protocol !== 'http:' && specUrl.protocol !== 'https:') {
654
- return {
655
- isValid: false,
656
- message: `Invalid protocol: Only HTTP and HTTPS are allowed, got ${specUrl.protocol}`,
657
- };
658
- }
659
-
660
- /** Spec hostname only */
661
- const specHostname = specUrl.hostname;
662
- /** Spec domain with protocol (handle IPv6 brackets) */
663
- const specIpVersion = isIP(specHostname);
664
- const normalizedSpecDomain =
665
- specIpVersion === 6
666
- ? `${specUrl.protocol}//[${specHostname}]`
667
- : `${specUrl.protocol}//${specHostname}`;
668
-
669
- /** Extract hostname from client domain if it's a full URL */
670
- let clientHostname = clientProvidedDomain;
671
- let clientHasProtocol = false;
672
-
673
- // Check for any protocol in the client domain
674
- if (clientProvidedDomain.includes('://')) {
675
- if (
676
- !clientProvidedDomain.startsWith('http://') &&
677
- !clientProvidedDomain.startsWith('https://')
678
- ) {
679
- return {
680
- isValid: false,
681
- message: `Invalid protocol: Only HTTP and HTTPS are allowed in client domain`,
682
- };
683
- }
684
- try {
685
- const clientUrl = new URL(clientProvidedDomain);
686
- clientHostname = clientUrl.hostname;
687
- clientHasProtocol = true;
688
- } catch {
689
- // If parsing fails, treat as hostname
690
- clientHasProtocol = false;
691
- }
692
- }
693
-
694
- /** Normalize IPv6 addresses by removing brackets for comparison */
695
- const normalizedClientHostname = clientHostname.replace(/^\[(.+)\]$/, '$1');
696
- const normalizedSpecHostname = specHostname.replace(/^\[(.+)\]$/, '$1');
697
-
698
- /** Check if hostname is valid IP using cross-platform isIP */
699
- const isIPAddress = isIP(normalizedClientHostname) !== 0;
700
-
701
- /** Normalized client domain */
702
- let normalizedClientDomain: string;
703
- if (clientHasProtocol) {
704
- normalizedClientDomain = extractDomainFromUrl(clientProvidedDomain);
705
- } else {
706
- // No protocol specified by client
707
- if (isIPAddress) {
708
- // IPs inherit protocol from spec (for legitimate internal services)
709
- const ipVersion = isIP(normalizedClientHostname);
710
- const hostname =
711
- ipVersion === 6 && !clientHostname.startsWith('[')
712
- ? `[${normalizedClientHostname}]`
713
- : clientHostname;
714
- normalizedClientDomain = `${specUrl.protocol}//${hostname}`;
715
- } else {
716
- // Domain names default to HTTPS for security (forces explicit protocol)
717
- normalizedClientDomain = `https://${clientHostname}`;
718
- }
719
- }
720
-
721
- if (
722
- normalizedSpecDomain === normalizedClientDomain ||
723
- (!clientHasProtocol && isIPAddress && normalizedClientHostname === normalizedSpecHostname)
724
- ) {
725
- return {
726
- isValid: true,
727
- normalizedSpecDomain,
728
- normalizedClientDomain,
729
- };
730
- }
731
-
732
- return {
733
- isValid: false,
734
- message: `Domain mismatch: Client provided '${clientProvidedDomain}', but spec uses '${specHostname}'`,
735
- normalizedSpecDomain,
736
- normalizedClientDomain,
737
- };
738
- } catch (error) {
739
- return {
740
- isValid: false,
741
- message: `Failed to validate domain: ${error instanceof Error ? error.message : 'Unknown error'}`,
742
- };
743
- }
744
- }
745
-
746
- /**
747
- * Validates and parses an OpenAPI spec.
748
- */
749
- export function validateAndParseOpenAPISpec(specString: string): ValidationResult {
750
- try {
751
- let parsedSpec;
752
- try {
753
- parsedSpec = JSON.parse(specString);
754
- } catch {
755
- parsedSpec = load(specString);
756
- }
757
-
758
- // Check for servers
759
- if (
760
- !parsedSpec.servers ||
761
- !Array.isArray(parsedSpec.servers) ||
762
- parsedSpec.servers.length === 0
763
- ) {
764
- return { status: false, message: 'Could not find a valid URL in `servers`' };
765
- }
766
-
767
- if (!parsedSpec.servers[0].url) {
768
- return { status: false, message: 'Could not find a valid URL in `servers`' };
769
- }
770
-
771
- // Check for paths
772
- const paths = parsedSpec.paths;
773
- if (!paths || typeof paths !== 'object' || Object.keys(paths).length === 0) {
774
- return { status: false, message: 'No paths found in the OpenAPI spec.' };
775
- }
776
-
777
- const components = parsedSpec.components?.schemas || {};
778
- const messages = [];
779
-
780
- for (const [path, methods] of Object.entries(paths)) {
781
- for (const [httpMethod, operation] of Object.entries(methods as OpenAPIV3.PathItemObject)) {
782
- // Ensure operation is a valid operation object
783
- const { responses } = operation as OpenAPIV3.OperationObject | { responses: undefined };
784
- if (typeof operation === 'object' && responses) {
785
- for (const [statusCode, response] of Object.entries(responses)) {
786
- const content = (response as OpenAPIV3.ResponseObject).content as MediaTypeObject;
787
- if (content && content['application/json'] && content['application/json'].schema) {
788
- const schema = content['application/json'].schema;
789
- if ('$ref' in schema && typeof schema.$ref === 'string') {
790
- const refName = schema.$ref.split('/').pop();
791
- if (refName && !components[refName]) {
792
- messages.push(
793
- `In context=('paths', '${path}', '${httpMethod}', '${statusCode}', 'response', 'content', 'application/json', 'schema'), reference to unknown component ${refName}; using empty schema`,
794
- );
795
- }
796
- }
797
- }
798
- }
799
- }
800
- }
801
- }
802
-
803
- return {
804
- status: true,
805
- message: messages.join('\n') || 'OpenAPI spec is valid.',
806
- spec: parsedSpec,
807
- serverUrl: parsedSpec.servers[0].url,
808
- };
809
- } catch (error) {
810
- console.error(error);
811
- return { status: false, message: 'Error parsing OpenAPI spec.' };
812
- }
813
- }