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.
- 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 +2 -2
- package/server-rollup.config.js +1 -1
- package/specs/actions.spec.ts +125 -7
- package/specs/filetypes.spec.ts +1 -7
- package/specs/mcp.spec.ts +52 -0
- package/specs/utils.spec.ts +129 -0
- package/src/actions.ts +100 -44
- package/src/api-endpoints.ts +23 -5
- package/src/azure.ts +2 -1
- package/src/bedrock.ts +84 -4
- package/src/config.ts +169 -69
- package/src/createPayload.ts +3 -1
- package/src/data-service.ts +54 -15
- package/src/file-config.ts +7 -0
- package/src/generate.ts +1 -1
- package/src/index.ts +2 -0
- package/src/keys.ts +4 -0
- package/src/mcp.ts +17 -1
- package/src/models.ts +1 -1
- package/src/ocr.ts +14 -0
- package/src/parsers.ts +43 -43
- package/src/react-query/react-query-service.ts +33 -119
- package/src/request.ts +7 -0
- package/src/roles.ts +33 -1
- package/src/schemas.ts +229 -190
- package/src/types/agents.ts +45 -1
- package/src/types/assistants.ts +20 -4
- package/src/types/files.ts +2 -0
- package/src/types/mutations.ts +41 -3
- package/src/types/queries.ts +24 -13
- package/src/types/runs.ts +1 -0
- package/src/types.ts +78 -77
- package/src/utils.ts +44 -0
- package/src/zod.spec.ts +86 -27
- package/src/zod.ts +22 -2
- package/tsconfig.json +1 -2
- package/specs/parsers.spec.ts +0 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "librechat-data-provider",
|
|
3
|
-
"version": "0.7.
|
|
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.
|
|
42
|
+
"axios": "^1.8.2",
|
|
43
43
|
"js-yaml": "^4.1.0",
|
|
44
44
|
"zod": "^3.22.4"
|
|
45
45
|
},
|
package/server-rollup.config.js
CHANGED
package/specs/actions.spec.ts
CHANGED
|
@@ -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
|
-
|
|
589
|
-
|
|
590
|
+
|
|
591
|
+
const resolvedSchemaObject = resolveRef(
|
|
592
|
+
flowchartRequestRef as OpenAPIV3.ReferenceObject,
|
|
590
593
|
openapiSpec.components,
|
|
591
|
-
);
|
|
594
|
+
) as OpenAPIV3.SchemaObject;
|
|
592
595
|
|
|
593
|
-
expect(
|
|
594
|
-
expect(
|
|
595
|
-
|
|
596
|
-
|
|
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
|
});
|
package/specs/filetypes.spec.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
379
|
+
|
|
380
|
+
return resolveRef(resolved as typeof obj, components) as Exclude<T, OpenAPIV3.ReferenceObject>;
|
|
350
381
|
}
|
|
351
|
-
|
|
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
|
-
/**
|
|
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
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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(
|
|
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),
|
|
431
|
-
operationObj.requestBody ? 'application/json' : '
|
|
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
|
}
|