librechat-data-provider 0.7.68 → 0.7.71

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.
@@ -5,7 +5,7 @@
5
5
  "module": "./index.es.js",
6
6
  "types": "../types/react-query/index.d.ts",
7
7
  "dependencies": {
8
- "axios": "^1.7.7",
8
+ "axios": "^1.8.2",
9
9
  "zod": "^3.22.4"
10
10
  }
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librechat-data-provider",
3
- "version": "0.7.68",
3
+ "version": "0.7.71",
4
4
  "description": "data services for librechat apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "homepage": "https://librechat.ai",
41
41
  "dependencies": {
42
- "axios": "^1.7.7",
42
+ "axios": "^1.8.2",
43
43
  "js-yaml": "^4.1.0",
44
44
  "zod": "^3.22.4"
45
45
  },
@@ -19,7 +19,7 @@ export default {
19
19
  input: entryPath,
20
20
  output: {
21
21
  dir: 'test_bundle',
22
- format: 'cjs',
22
+ format: 'es',
23
23
  },
24
24
  plugins: [
25
25
  alias(customAliases),
@@ -21,6 +21,7 @@ import type { ParametersSchema } from '../src/actions';
21
21
 
22
22
  jest.mock('axios');
23
23
  const mockedAxios = axios as jest.Mocked<typeof axios>;
24
+ mockedAxios.create.mockReturnValue(mockedAxios);
24
25
 
25
26
  describe('FunctionSignature', () => {
26
27
  it('creates a function signature and converts to JSON tool', () => {
@@ -584,21 +585,99 @@ describe('resolveRef', () => {
584
585
  openapiSpec.paths['/ai.chatgpt.render-flowchart']?.post
585
586
  ?.requestBody as OpenAPIV3.RequestBodyObject
586
587
  ).content['application/json'].schema;
588
+
587
589
  expect(flowchartRequestRef).toBeDefined();
588
- const resolvedFlowchartRequest = resolveRef(
589
- flowchartRequestRef as OpenAPIV3.RequestBodyObject,
590
+
591
+ const resolvedSchemaObject = resolveRef(
592
+ flowchartRequestRef as OpenAPIV3.ReferenceObject,
590
593
  openapiSpec.components,
591
- );
594
+ ) as OpenAPIV3.SchemaObject;
592
595
 
593
- expect(resolvedFlowchartRequest).toBeDefined();
594
- expect(resolvedFlowchartRequest.type).toBe('object');
595
- const properties = resolvedFlowchartRequest.properties as FlowchartSchema;
596
- expect(properties).toBeDefined();
596
+ expect(resolvedSchemaObject).toBeDefined();
597
+ expect(resolvedSchemaObject.type).toBe('object');
598
+ expect(resolvedSchemaObject.properties).toBeDefined();
599
+
600
+ const properties = resolvedSchemaObject.properties as FlowchartSchema;
597
601
  expect(properties.mermaid).toBeDefined();
598
602
  expect(properties.mermaid.type).toBe('string');
599
603
  });
600
604
  });
601
605
 
606
+ describe('resolveRef general cases', () => {
607
+ const spec = {
608
+ openapi: '3.0.0',
609
+ info: { title: 'TestSpec', version: '1.0.0' },
610
+ paths: {},
611
+ components: {
612
+ schemas: {
613
+ TestSchema: { type: 'string' },
614
+ },
615
+ parameters: {
616
+ TestParam: {
617
+ name: 'myParam',
618
+ in: 'query',
619
+ required: false,
620
+ schema: { $ref: '#/components/schemas/TestSchema' },
621
+ },
622
+ },
623
+ requestBodies: {
624
+ TestRequestBody: {
625
+ content: {
626
+ 'application/json': {
627
+ schema: { $ref: '#/components/schemas/TestSchema' },
628
+ },
629
+ },
630
+ },
631
+ },
632
+ },
633
+ } satisfies OpenAPIV3.Document;
634
+
635
+ it('resolves schema refs correctly', () => {
636
+ const schemaRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/schemas/TestSchema' };
637
+ const resolvedSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
638
+ schemaRef,
639
+ spec.components,
640
+ );
641
+ expect(resolvedSchema.type).toEqual('string');
642
+ });
643
+
644
+ it('resolves parameter refs correctly, then schema within parameter', () => {
645
+ const paramRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/parameters/TestParam' };
646
+ const resolvedParam = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>(
647
+ paramRef,
648
+ spec.components,
649
+ );
650
+ expect(resolvedParam.name).toEqual('myParam');
651
+ expect(resolvedParam.in).toEqual('query');
652
+ expect(resolvedParam.required).toBe(false);
653
+
654
+ const paramSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
655
+ resolvedParam.schema as OpenAPIV3.ReferenceObject,
656
+ spec.components,
657
+ );
658
+ expect(paramSchema.type).toEqual('string');
659
+ });
660
+
661
+ it('resolves requestBody refs correctly, then schema within requestBody', () => {
662
+ const requestBodyRef: OpenAPIV3.ReferenceObject = {
663
+ $ref: '#/components/requestBodies/TestRequestBody',
664
+ };
665
+ const resolvedRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject>(
666
+ requestBodyRef,
667
+ spec.components,
668
+ );
669
+
670
+ expect(resolvedRequestBody.content['application/json']).toBeDefined();
671
+
672
+ const schemaInRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
673
+ resolvedRequestBody.content['application/json'].schema as OpenAPIV3.ReferenceObject,
674
+ spec.components,
675
+ );
676
+
677
+ expect(schemaInRequestBody.type).toEqual('string');
678
+ });
679
+ });
680
+
602
681
  describe('openapiToFunction', () => {
603
682
  it('converts OpenAPI spec to function signatures and request builders', () => {
604
683
  const { functionSignatures, requestBuilders } = openapiToFunction(getWeatherOpenapiSpec);
@@ -1094,4 +1173,43 @@ describe('createURL', () => {
1094
1173
  });
1095
1174
  });
1096
1175
  });
1176
+
1177
+ describe('openapiToFunction parameter refs resolution', () => {
1178
+ const weatherSpec = {
1179
+ openapi: '3.0.0',
1180
+ info: { title: 'Weather', version: '1.0.0' },
1181
+ servers: [{ url: 'https://api.weather.gov' }],
1182
+ paths: {
1183
+ '/points/{point}': {
1184
+ get: {
1185
+ operationId: 'getPoint',
1186
+ parameters: [{ $ref: '#/components/parameters/PathPoint' }],
1187
+ responses: { '200': { description: 'ok' } },
1188
+ },
1189
+ },
1190
+ },
1191
+ components: {
1192
+ parameters: {
1193
+ PathPoint: {
1194
+ name: 'point',
1195
+ in: 'path',
1196
+ required: true,
1197
+ schema: { type: 'string', pattern: '^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$' },
1198
+ },
1199
+ },
1200
+ },
1201
+ } satisfies OpenAPIV3.Document;
1202
+
1203
+ it('correctly resolves $ref for parameters', () => {
1204
+ const { functionSignatures } = openapiToFunction(weatherSpec, true);
1205
+ const func = functionSignatures.find((sig) => sig.name === 'getPoint');
1206
+ expect(func).toBeDefined();
1207
+ expect(func?.parameters.properties).toHaveProperty('point');
1208
+ expect(func?.parameters.required).toContain('point');
1209
+
1210
+ const paramSchema = func?.parameters.properties['point'] as OpenAPIV3.SchemaObject;
1211
+ expect(paramSchema.type).toEqual('string');
1212
+ expect(paramSchema.pattern).toEqual('^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$');
1213
+ });
1214
+ });
1097
1215
  });
@@ -14,13 +14,7 @@ import {
14
14
  } from '../src/file-config';
15
15
 
16
16
  describe('MIME Type Regex Patterns', () => {
17
- const unsupportedMimeTypes = [
18
- 'text/x-unknown',
19
- 'application/unknown',
20
- 'image/bmp',
21
- 'image/svg',
22
- 'audio/mp3',
23
- ];
17
+ const unsupportedMimeTypes = ['text/x-unknown', 'application/unknown', 'image/bmp', 'audio/mp3'];
24
18
 
25
19
  // Testing general supported MIME types
26
20
  fullMimeTypesList.forEach((mimeType) => {
@@ -0,0 +1,52 @@
1
+ import { StdioOptionsSchema } from '../src/mcp';
2
+
3
+ describe('Environment Variable Extraction (MCP)', () => {
4
+ const originalEnv = process.env;
5
+
6
+ beforeEach(() => {
7
+ process.env = {
8
+ ...originalEnv,
9
+ TEST_API_KEY: 'test-api-key-value',
10
+ ANOTHER_SECRET: 'another-secret-value',
11
+ };
12
+ });
13
+
14
+ afterEach(() => {
15
+ process.env = originalEnv;
16
+ });
17
+
18
+ describe('StdioOptionsSchema', () => {
19
+ it('should transform environment variables in the env field', () => {
20
+ const options = {
21
+ command: 'node',
22
+ args: ['server.js'],
23
+ env: {
24
+ API_KEY: '${TEST_API_KEY}',
25
+ ANOTHER_KEY: '${ANOTHER_SECRET}',
26
+ PLAIN_VALUE: 'plain-value',
27
+ NON_EXISTENT: '${NON_EXISTENT_VAR}',
28
+ },
29
+ };
30
+
31
+ const result = StdioOptionsSchema.parse(options);
32
+
33
+ expect(result.env).toEqual({
34
+ API_KEY: 'test-api-key-value',
35
+ ANOTHER_KEY: 'another-secret-value',
36
+ PLAIN_VALUE: 'plain-value',
37
+ NON_EXISTENT: '${NON_EXISTENT_VAR}',
38
+ });
39
+ });
40
+
41
+ it('should handle undefined env field', () => {
42
+ const options = {
43
+ command: 'node',
44
+ args: ['server.js'],
45
+ };
46
+
47
+ const result = StdioOptionsSchema.parse(options);
48
+
49
+ expect(result.env).toBeUndefined();
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,129 @@
1
+ import { extractEnvVariable } from '../src/utils';
2
+
3
+ describe('Environment Variable Extraction', () => {
4
+ const originalEnv = process.env;
5
+
6
+ beforeEach(() => {
7
+ process.env = {
8
+ ...originalEnv,
9
+ TEST_API_KEY: 'test-api-key-value',
10
+ ANOTHER_SECRET: 'another-secret-value',
11
+ };
12
+ });
13
+
14
+ afterEach(() => {
15
+ process.env = originalEnv;
16
+ });
17
+
18
+ describe('extractEnvVariable (original tests)', () => {
19
+ test('should return the value of the environment variable', () => {
20
+ process.env.TEST_VAR = 'test_value';
21
+ expect(extractEnvVariable('${TEST_VAR}')).toBe('test_value');
22
+ });
23
+
24
+ test('should return the original string if the envrionment variable is not defined correctly', () => {
25
+ process.env.TEST_VAR = 'test_value';
26
+ expect(extractEnvVariable('${ TEST_VAR }')).toBe('${ TEST_VAR }');
27
+ });
28
+
29
+ test('should return the original string if environment variable is not set', () => {
30
+ expect(extractEnvVariable('${NON_EXISTENT_VAR}')).toBe('${NON_EXISTENT_VAR}');
31
+ });
32
+
33
+ test('should return the original string if it does not contain an environment variable', () => {
34
+ expect(extractEnvVariable('some_string')).toBe('some_string');
35
+ });
36
+
37
+ test('should handle empty strings', () => {
38
+ expect(extractEnvVariable('')).toBe('');
39
+ });
40
+
41
+ test('should handle strings without variable format', () => {
42
+ expect(extractEnvVariable('no_var_here')).toBe('no_var_here');
43
+ });
44
+
45
+ /** No longer the expected behavior; keeping for reference */
46
+ test.skip('should not process multiple variable formats', () => {
47
+ process.env.FIRST_VAR = 'first';
48
+ process.env.SECOND_VAR = 'second';
49
+ expect(extractEnvVariable('${FIRST_VAR} and ${SECOND_VAR}')).toBe(
50
+ '${FIRST_VAR} and ${SECOND_VAR}',
51
+ );
52
+ });
53
+ });
54
+
55
+ describe('extractEnvVariable function', () => {
56
+ it('should extract environment variables from exact matches', () => {
57
+ expect(extractEnvVariable('${TEST_API_KEY}')).toBe('test-api-key-value');
58
+ expect(extractEnvVariable('${ANOTHER_SECRET}')).toBe('another-secret-value');
59
+ });
60
+
61
+ it('should extract environment variables from strings with prefixes', () => {
62
+ expect(extractEnvVariable('prefix-${TEST_API_KEY}')).toBe('prefix-test-api-key-value');
63
+ });
64
+
65
+ it('should extract environment variables from strings with suffixes', () => {
66
+ expect(extractEnvVariable('${TEST_API_KEY}-suffix')).toBe('test-api-key-value-suffix');
67
+ });
68
+
69
+ it('should extract environment variables from strings with both prefixes and suffixes', () => {
70
+ expect(extractEnvVariable('prefix-${TEST_API_KEY}-suffix')).toBe(
71
+ 'prefix-test-api-key-value-suffix',
72
+ );
73
+ });
74
+
75
+ it('should not match invalid patterns', () => {
76
+ expect(extractEnvVariable('$TEST_API_KEY')).toBe('$TEST_API_KEY');
77
+ expect(extractEnvVariable('{TEST_API_KEY}')).toBe('{TEST_API_KEY}');
78
+ expect(extractEnvVariable('TEST_API_KEY')).toBe('TEST_API_KEY');
79
+ });
80
+ });
81
+
82
+ describe('extractEnvVariable', () => {
83
+ it('should extract environment variable values', () => {
84
+ expect(extractEnvVariable('${TEST_API_KEY}')).toBe('test-api-key-value');
85
+ expect(extractEnvVariable('${ANOTHER_SECRET}')).toBe('another-secret-value');
86
+ });
87
+
88
+ it('should return the original string if environment variable is not found', () => {
89
+ expect(extractEnvVariable('${NON_EXISTENT_VAR}')).toBe('${NON_EXISTENT_VAR}');
90
+ });
91
+
92
+ it('should return the original string if no environment variable pattern is found', () => {
93
+ expect(extractEnvVariable('plain-string')).toBe('plain-string');
94
+ });
95
+ });
96
+
97
+ describe('extractEnvVariable space trimming', () => {
98
+ beforeEach(() => {
99
+ process.env.HELLO = 'world';
100
+ process.env.USER = 'testuser';
101
+ });
102
+
103
+ it('should extract the value when string contains only an environment variable with surrounding whitespace', () => {
104
+ expect(extractEnvVariable(' ${HELLO} ')).toBe('world');
105
+ expect(extractEnvVariable(' ${HELLO} ')).toBe('world');
106
+ expect(extractEnvVariable('\t${HELLO}\n')).toBe('world');
107
+ });
108
+
109
+ it('should preserve content when variable is part of a larger string', () => {
110
+ expect(extractEnvVariable('Hello ${USER}!')).toBe('Hello testuser!');
111
+ expect(extractEnvVariable(' Hello ${USER}! ')).toBe('Hello testuser!');
112
+ });
113
+
114
+ it('should not handle multiple variables', () => {
115
+ expect(extractEnvVariable('${HELLO} ${USER}')).toBe('${HELLO} ${USER}');
116
+ expect(extractEnvVariable(' ${HELLO} ${USER} ')).toBe('${HELLO} ${USER}');
117
+ });
118
+
119
+ it('should handle undefined variables', () => {
120
+ expect(extractEnvVariable(' ${UNDEFINED_VAR} ')).toBe('${UNDEFINED_VAR}');
121
+ });
122
+
123
+ it('should handle mixed content correctly', () => {
124
+ expect(extractEnvVariable('Welcome, ${USER}!\nYour message: ${HELLO}')).toBe(
125
+ 'Welcome, testuser!\nYour message: world',
126
+ );
127
+ });
128
+ });
129
+ });
package/src/actions.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  import { z } from 'zod';
2
- import axios from 'axios';
2
+ import _axios from 'axios';
3
3
  import { URL } from 'url';
4
4
  import crypto from 'crypto';
5
5
  import { load } from 'js-yaml';
6
- import type { FunctionTool, Schema, Reference, ActionMetadata } from './types/assistants';
6
+ import type {
7
+ FunctionTool,
8
+ Schema,
9
+ Reference,
10
+ ActionMetadata,
11
+ ActionMetadataRuntime,
12
+ } from './types/assistants';
7
13
  import type { OpenAPIV3 } from 'openapi-types';
8
14
  import { Tools, AuthTypeEnum, AuthorizationTypeEnum } from './types/assistants';
9
15
 
@@ -11,6 +17,7 @@ export type ParametersSchema = {
11
17
  type: string;
12
18
  properties: Record<string, Reference | Schema>;
13
19
  required: string[];
20
+ additionalProperties?: boolean;
14
21
  };
15
22
 
16
23
  export type OpenAPISchema = OpenAPIV3.SchemaObject &
@@ -118,28 +125,40 @@ function openAPISchemaToZod(schema: OpenAPISchema): z.ZodTypeAny | undefined {
118
125
  return handler(schema);
119
126
  }
120
127
 
128
+ /**
129
+ * Class representing a function signature.
130
+ */
121
131
  export class FunctionSignature {
122
132
  name: string;
123
133
  description: string;
124
134
  parameters: ParametersSchema;
135
+ strict: boolean;
125
136
 
126
- constructor(name: string, description: string, parameters: ParametersSchema) {
137
+ constructor(name: string, description: string, parameters: ParametersSchema, strict?: boolean) {
127
138
  this.name = name;
128
139
  this.description = description;
129
140
  this.parameters = parameters;
141
+ this.strict = strict ?? false;
130
142
  }
131
143
 
132
144
  toObjectTool(): FunctionTool {
145
+ const parameters = {
146
+ ...this.parameters,
147
+ additionalProperties: this.strict ? false : undefined,
148
+ };
149
+
133
150
  return {
134
151
  type: Tools.function,
135
152
  function: {
136
153
  name: this.name,
137
154
  description: this.description,
138
- parameters: this.parameters,
155
+ parameters,
156
+ ...(this.strict ? { strict: this.strict } : {}),
139
157
  },
140
158
  };
141
159
  }
142
160
  }
161
+
143
162
  class RequestConfig {
144
163
  constructor(
145
164
  readonly domain: string,
@@ -176,7 +195,7 @@ class RequestExecutor {
176
195
  return this;
177
196
  }
178
197
 
179
- async setAuth(metadata: ActionMetadata) {
198
+ async setAuth(metadata: ActionMetadataRuntime) {
180
199
  if (!metadata.auth) {
181
200
  return this;
182
201
  }
@@ -199,6 +218,8 @@ class RequestExecutor {
199
218
  /* OAuth */
200
219
  oauth_client_id,
201
220
  oauth_client_secret,
221
+ oauth_token_expires_at,
222
+ oauth_access_token = '',
202
223
  } = metadata;
203
224
 
204
225
  const isApiKey = api_key != null && api_key.length > 0 && type === AuthTypeEnum.ServiceHttp;
@@ -230,22 +251,23 @@ class RequestExecutor {
230
251
  ) {
231
252
  this.authHeaders[custom_auth_header] = api_key;
232
253
  } else if (isOAuth) {
233
- const authToken = this.authToken ?? '';
234
- if (!authToken) {
235
- const tokenResponse = await axios.post(
236
- client_url,
237
- {
238
- client_id: oauth_client_id,
239
- client_secret: oauth_client_secret,
240
- scope: scope,
241
- grant_type: 'client_credentials',
242
- },
243
- {
244
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
245
- },
246
- );
247
- this.authToken = tokenResponse.data.access_token;
254
+ // TODO: maybe doing it in a different way later on. but we want that the user needs to folllow the oauth flow.
255
+ // If we do not have a valid token, bail or ask user to sign in
256
+ const now = new Date();
257
+
258
+ // 1. Check if token is set
259
+ if (!oauth_access_token) {
260
+ throw new Error('No access token found. Please log in first.');
261
+ }
262
+
263
+ // 2. Check if token is expired
264
+ if (oauth_token_expires_at && now >= new Date(oauth_token_expires_at)) {
265
+ // Optionally check refresh_token logic, or just prompt user to re-login
266
+ throw new Error('Access token is expired. Please re-login.');
248
267
  }
268
+
269
+ // If valid, use it
270
+ this.authToken = oauth_access_token;
249
271
  this.authHeaders['Authorization'] = `Bearer ${this.authToken}`;
250
272
  }
251
273
  return this;
@@ -259,7 +281,7 @@ class RequestExecutor {
259
281
  };
260
282
 
261
283
  const method = this.config.method.toLowerCase();
262
-
284
+ const axios = _axios.create();
263
285
  if (method === 'get') {
264
286
  return axios.get(url, { headers, params: this.params });
265
287
  } else if (method === 'post') {
@@ -336,26 +358,38 @@ export class ActionRequest {
336
358
  }
337
359
  }
338
360
 
339
- export function resolveRef(
340
- schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | RequestBodyObject,
341
- components?: OpenAPIV3.ComponentsObject,
342
- ): OpenAPIV3.SchemaObject {
343
- if ('$ref' in schema && components) {
344
- const refPath = schema.$ref.replace(/^#\/components\/schemas\//, '');
345
- const resolvedSchema = components.schemas?.[refPath];
346
- if (!resolvedSchema) {
347
- throw new Error(`Reference ${schema.$ref} not found`);
361
+ export function resolveRef<
362
+ T extends
363
+ | OpenAPIV3.ReferenceObject
364
+ | OpenAPIV3.SchemaObject
365
+ | OpenAPIV3.ParameterObject
366
+ | OpenAPIV3.RequestBodyObject,
367
+ >(obj: T, components?: OpenAPIV3.ComponentsObject): Exclude<T, OpenAPIV3.ReferenceObject> {
368
+ if ('$ref' in obj && components) {
369
+ const refPath = obj.$ref.replace(/^#\/components\//, '').split('/');
370
+
371
+ let resolved: unknown = components as Record<string, unknown>;
372
+ for (const segment of refPath) {
373
+ if (typeof resolved === 'object' && resolved !== null && segment in resolved) {
374
+ resolved = (resolved as Record<string, unknown>)[segment];
375
+ } else {
376
+ throw new Error(`Could not resolve reference: ${obj.$ref}`);
377
+ }
348
378
  }
349
- return resolveRef(resolvedSchema, components);
379
+
380
+ return resolveRef(resolved as typeof obj, components) as Exclude<T, OpenAPIV3.ReferenceObject>;
350
381
  }
351
- return schema as OpenAPIV3.SchemaObject;
382
+
383
+ return obj as Exclude<T, OpenAPIV3.ReferenceObject>;
352
384
  }
353
385
 
354
386
  function sanitizeOperationId(input: string) {
355
387
  return input.replace(/[^a-zA-Z0-9_-]/g, '');
356
388
  }
357
389
 
358
- /** Function to convert OpenAPI spec to function signatures and request builders */
390
+ /**
391
+ * Converts an OpenAPI spec to function signatures and request builders.
392
+ */
359
393
  export function openapiToFunction(
360
394
  openapiSpec: OpenAPIV3.Document,
361
395
  generateZodSchemas = false,
@@ -374,12 +408,15 @@ export function openapiToFunction(
374
408
  for (const [method, operation] of Object.entries(methods as OpenAPIV3.PathsObject)) {
375
409
  const operationObj = operation as OpenAPIV3.OperationObject & {
376
410
  'x-openai-isConsequential'?: boolean;
411
+ } & {
412
+ 'x-strict'?: boolean;
377
413
  };
378
414
 
379
415
  // Operation ID is used as the function name
380
416
  const defaultOperationId = `${method}_${path}`;
381
417
  const operationId = operationObj.operationId || sanitizeOperationId(defaultOperationId);
382
418
  const description = operationObj.summary || operationObj.description || '';
419
+ const isStrict = operationObj['x-strict'] ?? false;
383
420
 
384
421
  const parametersSchema: OpenAPISchema = {
385
422
  type: 'object',
@@ -388,15 +425,25 @@ export function openapiToFunction(
388
425
  };
389
426
 
390
427
  if (operationObj.parameters) {
391
- for (const param of operationObj.parameters) {
392
- const paramObj = param as OpenAPIV3.ParameterObject;
393
- const resolvedSchema = resolveRef(
394
- { ...paramObj.schema } as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
428
+ for (const param of operationObj.parameters ?? []) {
429
+ const resolvedParam = resolveRef(
430
+ param,
431
+ openapiSpec.components,
432
+ ) as OpenAPIV3.ParameterObject;
433
+
434
+ const paramName = resolvedParam.name;
435
+ if (!paramName || !resolvedParam.schema) {
436
+ continue;
437
+ }
438
+
439
+ const paramSchema = resolveRef(
440
+ resolvedParam.schema,
395
441
  openapiSpec.components,
396
- );
397
- parametersSchema.properties[paramObj.name] = resolvedSchema;
398
- if (paramObj.required === true) {
399
- parametersSchema.required.push(paramObj.name);
442
+ ) as OpenAPIV3.SchemaObject;
443
+
444
+ parametersSchema.properties[paramName] = paramSchema;
445
+ if (resolvedParam.required) {
446
+ parametersSchema.required.push(paramName);
400
447
  }
401
448
  }
402
449
  }
@@ -419,7 +466,12 @@ export function openapiToFunction(
419
466
  }
420
467
  }
421
468
 
422
- const functionSignature = new FunctionSignature(operationId, description, parametersSchema);
469
+ const functionSignature = new FunctionSignature(
470
+ operationId,
471
+ description,
472
+ parametersSchema,
473
+ isStrict,
474
+ );
423
475
  functionSignatures.push(functionSignature);
424
476
 
425
477
  const actionRequest = new ActionRequest(
@@ -427,8 +479,8 @@ export function openapiToFunction(
427
479
  path,
428
480
  method,
429
481
  operationId,
430
- !!(operationObj['x-openai-isConsequential'] ?? false), // Custom extension for consequential actions
431
- operationObj.requestBody ? 'application/json' : 'application/x-www-form-urlencoded',
482
+ !!(operationObj['x-openai-isConsequential'] ?? false),
483
+ operationObj.requestBody ? 'application/json' : '',
432
484
  );
433
485
 
434
486
  requestBuilders[operationId] = actionRequest;
@@ -451,6 +503,9 @@ export type ValidationResult = {
451
503
  spec?: OpenAPIV3.Document;
452
504
  };
453
505
 
506
+ /**
507
+ * Validates and parses an OpenAPI spec.
508
+ */
454
509
  export function validateAndParseOpenAPISpec(specString: string): ValidationResult {
455
510
  try {
456
511
  let parsedSpec;
@@ -511,6 +566,7 @@ export function validateAndParseOpenAPISpec(specString: string): ValidationResul
511
566
  spec: parsedSpec,
512
567
  };
513
568
  } catch (error) {
569
+ console.error(error);
514
570
  return { status: false, message: 'Error parsing OpenAPI spec.' };
515
571
  }
516
572
  }