librechat-data-provider 0.3.9 → 0.4.1
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 +2 -1
- package/package.json +4 -1
- package/specs/actions.spec.ts +475 -0
- package/specs/filetypes.spec.ts +181 -0
- package/specs/openapiSpecs.ts +350 -0
- package/src/actions.ts +347 -0
- package/src/config.ts +130 -13
- package/src/createPayload.ts +1 -5
- package/src/data-service.ts +39 -2
- package/src/file-config.ts +264 -0
- package/src/index.ts +2 -0
- package/src/keys.ts +8 -1
- package/src/parsers.ts +10 -10
- package/src/react-query/index.ts +0 -1
- package/src/react-query/react-query-service.ts +16 -1
- package/src/schemas.ts +31 -14
- package/src/types/assistants.ts +255 -3
- package/src/types/files.ts +46 -13
- package/src/types/mutations.ts +90 -1
- package/src/types.ts +7 -0
- package/tsconfig.spec.json +10 -0
- package/src/react-query/assistants.ts +0 -138
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
|
|
3
|
+
export type FlowchartSchema = {
|
|
4
|
+
mermaid: {
|
|
5
|
+
type: 'string';
|
|
6
|
+
description: 'Flowchart to be rendered, in Mermaid syntax';
|
|
7
|
+
};
|
|
8
|
+
title: {
|
|
9
|
+
type: 'string';
|
|
10
|
+
description: 'Title of the flowchart';
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getWeatherOpenapiSpec: OpenAPIV3.Document = {
|
|
15
|
+
openapi: '3.1.0',
|
|
16
|
+
info: {
|
|
17
|
+
title: 'Get weather data',
|
|
18
|
+
description: 'Retrieves current weather data for a location.',
|
|
19
|
+
version: 'v1.0.0',
|
|
20
|
+
},
|
|
21
|
+
servers: [
|
|
22
|
+
{
|
|
23
|
+
url: 'https://weather.example.com',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
paths: {
|
|
27
|
+
'/location': {
|
|
28
|
+
get: {
|
|
29
|
+
description: 'Get temperature for a specific location',
|
|
30
|
+
operationId: 'GetCurrentWeather',
|
|
31
|
+
parameters: [
|
|
32
|
+
{
|
|
33
|
+
name: 'location',
|
|
34
|
+
in: 'query',
|
|
35
|
+
description: 'The city and state to retrieve the weather for',
|
|
36
|
+
required: true,
|
|
37
|
+
schema: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
requestBody: {
|
|
43
|
+
required: true,
|
|
44
|
+
content: {
|
|
45
|
+
'application/json': {
|
|
46
|
+
schema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
locations: {
|
|
50
|
+
type: 'array',
|
|
51
|
+
items: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
city: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
example: 'San Francisco',
|
|
57
|
+
},
|
|
58
|
+
state: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
example: 'CA',
|
|
61
|
+
},
|
|
62
|
+
countryCode: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'ISO 3166-1 alpha-2 country code',
|
|
65
|
+
example: 'US',
|
|
66
|
+
},
|
|
67
|
+
time: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description:
|
|
70
|
+
'Optional time for which the weather is requested, in ISO 8601 format.',
|
|
71
|
+
example: '2023-12-04T14:00:00Z',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ['city', 'state', 'countryCode'],
|
|
75
|
+
description:
|
|
76
|
+
'Details of the location for which the weather data is requested.',
|
|
77
|
+
},
|
|
78
|
+
description: 'A list of locations to retrieve the weather for.',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
deprecated: false,
|
|
86
|
+
responses: {},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
components: {
|
|
91
|
+
schemas: {},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const whimsicalOpenapiSpec: OpenAPIV3.Document = {
|
|
96
|
+
openapi: '3.0.0',
|
|
97
|
+
info: {
|
|
98
|
+
version: '1.0.0',
|
|
99
|
+
title: 'Diagram to Image API',
|
|
100
|
+
description: 'A simple API to generate flowchart, mindmap, or sequence diagram images.',
|
|
101
|
+
},
|
|
102
|
+
servers: [{ url: 'https://whimsical.com/api' }],
|
|
103
|
+
paths: {
|
|
104
|
+
'/ai.chatgpt.render-flowchart': {
|
|
105
|
+
post: {
|
|
106
|
+
operationId: 'postRenderFlowchart',
|
|
107
|
+
// 'x-openai-isConsequential': false,
|
|
108
|
+
summary: 'Renders a flowchart',
|
|
109
|
+
description:
|
|
110
|
+
'Accepts a string describing a flowchart and returns a URL to a rendered image',
|
|
111
|
+
requestBody: {
|
|
112
|
+
content: {
|
|
113
|
+
'application/json': {
|
|
114
|
+
schema: {
|
|
115
|
+
$ref: '#/components/schemas/FlowchartRequest',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: true,
|
|
120
|
+
},
|
|
121
|
+
responses: {
|
|
122
|
+
'200': {
|
|
123
|
+
description: 'URL to the rendered image',
|
|
124
|
+
content: {
|
|
125
|
+
'application/json': {
|
|
126
|
+
schema: {
|
|
127
|
+
$ref: '#/components/schemas/FlowchartResponse',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
components: {
|
|
137
|
+
schemas: {
|
|
138
|
+
FlowchartRequest: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
mermaid: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: 'Flowchart to be rendered, in Mermaid syntax',
|
|
144
|
+
},
|
|
145
|
+
title: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'Title of the flowchart',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
required: ['mermaid'],
|
|
151
|
+
},
|
|
152
|
+
FlowchartResponse: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
imageURL: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'URL of the rendered image',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const scholarAIOpenapiSpec = `
|
|
166
|
+
openapi: 3.0.1
|
|
167
|
+
info:
|
|
168
|
+
title: ScholarAI
|
|
169
|
+
description: Allows the user to search facts and findings from scientific articles
|
|
170
|
+
version: 'v1'
|
|
171
|
+
servers:
|
|
172
|
+
- url: https://scholar-ai.net
|
|
173
|
+
paths:
|
|
174
|
+
/api/abstracts:
|
|
175
|
+
get:
|
|
176
|
+
operationId: searchAbstracts
|
|
177
|
+
summary: Get relevant paper abstracts by keywords search
|
|
178
|
+
parameters:
|
|
179
|
+
- name: keywords
|
|
180
|
+
in: query
|
|
181
|
+
description: Keywords of inquiry which should appear in article. Must be in English.
|
|
182
|
+
required: true
|
|
183
|
+
schema:
|
|
184
|
+
type: string
|
|
185
|
+
- name: sort
|
|
186
|
+
in: query
|
|
187
|
+
description: The sort order for results. Valid values are cited_by_count or publication_date. Excluding this value does a relevance based search.
|
|
188
|
+
required: false
|
|
189
|
+
schema:
|
|
190
|
+
type: string
|
|
191
|
+
enum:
|
|
192
|
+
- cited_by_count
|
|
193
|
+
- publication_date
|
|
194
|
+
- name: query
|
|
195
|
+
in: query
|
|
196
|
+
description: The user query
|
|
197
|
+
required: true
|
|
198
|
+
schema:
|
|
199
|
+
type: string
|
|
200
|
+
- name: peer_reviewed_only
|
|
201
|
+
in: query
|
|
202
|
+
description: Whether to only return peer reviewed articles. Defaults to true, ChatGPT should cautiously suggest this value can be set to false
|
|
203
|
+
required: false
|
|
204
|
+
schema:
|
|
205
|
+
type: string
|
|
206
|
+
- name: start_year
|
|
207
|
+
in: query
|
|
208
|
+
description: The first year, inclusive, to include in the search range. Excluding this value will include all years.
|
|
209
|
+
required: false
|
|
210
|
+
schema:
|
|
211
|
+
type: string
|
|
212
|
+
- name: end_year
|
|
213
|
+
in: query
|
|
214
|
+
description: The last year, inclusive, to include in the search range. Excluding this value will include all years.
|
|
215
|
+
required: false
|
|
216
|
+
schema:
|
|
217
|
+
type: string
|
|
218
|
+
- name: offset
|
|
219
|
+
in: query
|
|
220
|
+
description: The offset of the first result to return. Defaults to 0.
|
|
221
|
+
required: false
|
|
222
|
+
schema:
|
|
223
|
+
type: string
|
|
224
|
+
responses:
|
|
225
|
+
"200":
|
|
226
|
+
description: OK
|
|
227
|
+
content:
|
|
228
|
+
application/json:
|
|
229
|
+
schema:
|
|
230
|
+
$ref: '#/components/schemas/searchAbstractsResponse'
|
|
231
|
+
/api/fulltext:
|
|
232
|
+
get:
|
|
233
|
+
operationId: getFullText
|
|
234
|
+
summary: Get full text of a paper by URL for PDF
|
|
235
|
+
parameters:
|
|
236
|
+
- name: pdf_url
|
|
237
|
+
in: query
|
|
238
|
+
description: URL for PDF
|
|
239
|
+
required: true
|
|
240
|
+
schema:
|
|
241
|
+
type: string
|
|
242
|
+
- name: chunk
|
|
243
|
+
in: query
|
|
244
|
+
description: chunk number to retrieve, defaults to 1
|
|
245
|
+
required: false
|
|
246
|
+
schema:
|
|
247
|
+
type: number
|
|
248
|
+
responses:
|
|
249
|
+
"200":
|
|
250
|
+
description: OK
|
|
251
|
+
content:
|
|
252
|
+
application/json:
|
|
253
|
+
schema:
|
|
254
|
+
$ref: '#/components/schemas/getFullTextResponse'
|
|
255
|
+
/api/save-citation:
|
|
256
|
+
get:
|
|
257
|
+
operationId: saveCitation
|
|
258
|
+
summary: Save citation to reference manager
|
|
259
|
+
parameters:
|
|
260
|
+
- name: doi
|
|
261
|
+
in: query
|
|
262
|
+
description: Digital Object Identifier (DOI) of article
|
|
263
|
+
required: true
|
|
264
|
+
schema:
|
|
265
|
+
type: string
|
|
266
|
+
- name: zotero_user_id
|
|
267
|
+
in: query
|
|
268
|
+
description: Zotero User ID
|
|
269
|
+
required: true
|
|
270
|
+
schema:
|
|
271
|
+
type: string
|
|
272
|
+
- name: zotero_api_key
|
|
273
|
+
in: query
|
|
274
|
+
description: Zotero API Key
|
|
275
|
+
required: true
|
|
276
|
+
schema:
|
|
277
|
+
type: string
|
|
278
|
+
responses:
|
|
279
|
+
"200":
|
|
280
|
+
description: OK
|
|
281
|
+
content:
|
|
282
|
+
application/json:
|
|
283
|
+
schema:
|
|
284
|
+
$ref: '#/components/schemas/saveCitationResponse'
|
|
285
|
+
components:
|
|
286
|
+
schemas:
|
|
287
|
+
searchAbstractsResponse:
|
|
288
|
+
type: object
|
|
289
|
+
properties:
|
|
290
|
+
next_offset:
|
|
291
|
+
type: number
|
|
292
|
+
description: The offset of the next page of results.
|
|
293
|
+
total_num_results:
|
|
294
|
+
type: number
|
|
295
|
+
description: The total number of results.
|
|
296
|
+
abstracts:
|
|
297
|
+
type: array
|
|
298
|
+
items:
|
|
299
|
+
type: object
|
|
300
|
+
properties:
|
|
301
|
+
title:
|
|
302
|
+
type: string
|
|
303
|
+
abstract:
|
|
304
|
+
type: string
|
|
305
|
+
description: Summary of the context, methods, results, and conclusions of the paper.
|
|
306
|
+
doi:
|
|
307
|
+
type: string
|
|
308
|
+
description: The DOI of the paper.
|
|
309
|
+
landing_page_url:
|
|
310
|
+
type: string
|
|
311
|
+
description: Link to the paper on its open-access host.
|
|
312
|
+
pdf_url:
|
|
313
|
+
type: string
|
|
314
|
+
description: Link to the paper PDF.
|
|
315
|
+
publicationDate:
|
|
316
|
+
type: string
|
|
317
|
+
description: The date the paper was published in YYYY-MM-DD format.
|
|
318
|
+
relevance:
|
|
319
|
+
type: number
|
|
320
|
+
description: The relevance of the paper to the search query. 1 is the most relevant.
|
|
321
|
+
creators:
|
|
322
|
+
type: array
|
|
323
|
+
items:
|
|
324
|
+
type: string
|
|
325
|
+
description: The name of the creator.
|
|
326
|
+
cited_by_count:
|
|
327
|
+
type: number
|
|
328
|
+
description: The number of citations of the article.
|
|
329
|
+
description: The list of relevant abstracts.
|
|
330
|
+
getFullTextResponse:
|
|
331
|
+
type: object
|
|
332
|
+
properties:
|
|
333
|
+
full_text:
|
|
334
|
+
type: string
|
|
335
|
+
description: The full text of the paper.
|
|
336
|
+
pdf_url:
|
|
337
|
+
type: string
|
|
338
|
+
description: The PDF URL of the paper.
|
|
339
|
+
chunk:
|
|
340
|
+
type: number
|
|
341
|
+
description: The chunk of the paper.
|
|
342
|
+
total_chunk_num:
|
|
343
|
+
type: number
|
|
344
|
+
description: The total chunks of the paper.
|
|
345
|
+
saveCitationResponse:
|
|
346
|
+
type: object
|
|
347
|
+
properties:
|
|
348
|
+
message:
|
|
349
|
+
type: string
|
|
350
|
+
description: Confirmation of successful save or error message.`;
|
package/src/actions.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { load } from 'js-yaml';
|
|
5
|
+
import type { FunctionTool, Schema, Reference, ActionMetadata } from './types/assistants';
|
|
6
|
+
import type { OpenAPIV3 } from 'openapi-types';
|
|
7
|
+
import { Tools, AuthTypeEnum, AuthorizationTypeEnum } from './types/assistants';
|
|
8
|
+
|
|
9
|
+
export type ParametersSchema = {
|
|
10
|
+
type: string;
|
|
11
|
+
properties: Record<string, Reference | Schema>;
|
|
12
|
+
required: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ApiKeyCredentials = {
|
|
16
|
+
api_key: string;
|
|
17
|
+
custom_auth_header?: string;
|
|
18
|
+
authorization_type?: AuthorizationTypeEnum;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type OAuthCredentials = {
|
|
22
|
+
tokenUrl: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
clientSecret: string;
|
|
25
|
+
scope: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type Credentials = ApiKeyCredentials | OAuthCredentials;
|
|
29
|
+
|
|
30
|
+
export function sha1(input: string) {
|
|
31
|
+
return crypto.createHash('sha1').update(input).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createURL(domain: string, path: string) {
|
|
35
|
+
const myURL = new URL(path, domain);
|
|
36
|
+
return myURL.toString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class FunctionSignature {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
parameters: ParametersSchema;
|
|
43
|
+
|
|
44
|
+
constructor(name: string, description: string, parameters: ParametersSchema) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.description = description;
|
|
47
|
+
if (parameters.properties?.['requestBody']) {
|
|
48
|
+
this.parameters = parameters.properties?.['requestBody'] as ParametersSchema;
|
|
49
|
+
} else {
|
|
50
|
+
this.parameters = parameters;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toObjectTool(): FunctionTool {
|
|
55
|
+
return {
|
|
56
|
+
type: Tools.function,
|
|
57
|
+
function: {
|
|
58
|
+
name: this.name,
|
|
59
|
+
description: this.description,
|
|
60
|
+
parameters: this.parameters,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class ActionRequest {
|
|
67
|
+
domain: string;
|
|
68
|
+
path: string;
|
|
69
|
+
method: string;
|
|
70
|
+
operation: string;
|
|
71
|
+
operationHash?: string;
|
|
72
|
+
isConsequential: boolean;
|
|
73
|
+
contentType: string;
|
|
74
|
+
params?: object;
|
|
75
|
+
|
|
76
|
+
constructor(
|
|
77
|
+
domain: string,
|
|
78
|
+
path: string,
|
|
79
|
+
method: string,
|
|
80
|
+
operation: string,
|
|
81
|
+
isConsequential: boolean,
|
|
82
|
+
contentType: string,
|
|
83
|
+
) {
|
|
84
|
+
this.domain = domain;
|
|
85
|
+
this.path = path;
|
|
86
|
+
this.method = method;
|
|
87
|
+
this.operation = operation;
|
|
88
|
+
this.isConsequential = isConsequential;
|
|
89
|
+
this.contentType = contentType;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private authHeaders: Record<string, string> = {};
|
|
93
|
+
private authToken?: string;
|
|
94
|
+
|
|
95
|
+
async setParams(params: object) {
|
|
96
|
+
this.operationHash = sha1(JSON.stringify(params));
|
|
97
|
+
this.params = params;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async setAuth(metadata: ActionMetadata) {
|
|
101
|
+
if (!metadata.auth) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const {
|
|
106
|
+
type,
|
|
107
|
+
/* API Key */
|
|
108
|
+
authorization_type,
|
|
109
|
+
custom_auth_header,
|
|
110
|
+
/* OAuth */
|
|
111
|
+
authorization_url,
|
|
112
|
+
client_url,
|
|
113
|
+
scope,
|
|
114
|
+
token_exchange_method,
|
|
115
|
+
} = metadata.auth;
|
|
116
|
+
|
|
117
|
+
const {
|
|
118
|
+
/* API Key */
|
|
119
|
+
api_key,
|
|
120
|
+
/* OAuth */
|
|
121
|
+
oauth_client_id,
|
|
122
|
+
oauth_client_secret,
|
|
123
|
+
} = metadata;
|
|
124
|
+
|
|
125
|
+
const isApiKey = api_key && type === AuthTypeEnum.ServiceHttp;
|
|
126
|
+
const isOAuth =
|
|
127
|
+
oauth_client_id &&
|
|
128
|
+
oauth_client_secret &&
|
|
129
|
+
type === AuthTypeEnum.OAuth &&
|
|
130
|
+
authorization_url &&
|
|
131
|
+
client_url &&
|
|
132
|
+
scope &&
|
|
133
|
+
token_exchange_method;
|
|
134
|
+
|
|
135
|
+
if (isApiKey && authorization_type === AuthorizationTypeEnum.Basic) {
|
|
136
|
+
const basicToken = Buffer.from(api_key).toString('base64');
|
|
137
|
+
this.authHeaders['Authorization'] = `Basic ${basicToken}`;
|
|
138
|
+
} else if (isApiKey && authorization_type === AuthorizationTypeEnum.Bearer) {
|
|
139
|
+
this.authHeaders['Authorization'] = `Bearer ${api_key}`;
|
|
140
|
+
} else if (
|
|
141
|
+
isApiKey &&
|
|
142
|
+
authorization_type === AuthorizationTypeEnum.Custom &&
|
|
143
|
+
custom_auth_header
|
|
144
|
+
) {
|
|
145
|
+
this.authHeaders[custom_auth_header] = api_key;
|
|
146
|
+
} else if (isOAuth) {
|
|
147
|
+
// TODO: WIP - OAuth support
|
|
148
|
+
if (!this.authToken) {
|
|
149
|
+
const tokenResponse = await axios.post(
|
|
150
|
+
client_url,
|
|
151
|
+
{
|
|
152
|
+
client_id: oauth_client_id,
|
|
153
|
+
client_secret: oauth_client_secret,
|
|
154
|
+
scope: scope,
|
|
155
|
+
grant_type: 'client_credentials',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
this.authToken = tokenResponse.data.access_token;
|
|
162
|
+
}
|
|
163
|
+
this.authHeaders['Authorization'] = `Bearer ${this.authToken}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async execute() {
|
|
168
|
+
const url = createURL(this.domain, this.path);
|
|
169
|
+
const headers = {
|
|
170
|
+
...this.authHeaders,
|
|
171
|
+
'Content-Type': this.contentType,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const method = this.method.toLowerCase();
|
|
175
|
+
|
|
176
|
+
if (method === 'get') {
|
|
177
|
+
return axios.get(url, { headers, params: this.params });
|
|
178
|
+
} else if (method === 'post') {
|
|
179
|
+
return axios.post(url, this.params, { headers });
|
|
180
|
+
} else if (method === 'put') {
|
|
181
|
+
return axios.put(url, this.params, { headers });
|
|
182
|
+
} else if (method === 'delete') {
|
|
183
|
+
return axios.delete(url, { headers, data: this.params });
|
|
184
|
+
} else if (method === 'patch') {
|
|
185
|
+
return axios.patch(url, this.params, { headers });
|
|
186
|
+
} else {
|
|
187
|
+
throw new Error(`Unsupported HTTP method: ${this.method}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function resolveRef(
|
|
193
|
+
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject,
|
|
194
|
+
components?: OpenAPIV3.ComponentsObject,
|
|
195
|
+
): OpenAPIV3.SchemaObject {
|
|
196
|
+
if ('$ref' in schema && components) {
|
|
197
|
+
const refPath = schema.$ref.replace(/^#\/components\/schemas\//, '');
|
|
198
|
+
const resolvedSchema = components.schemas?.[refPath];
|
|
199
|
+
if (!resolvedSchema) {
|
|
200
|
+
throw new Error(`Reference ${schema.$ref} not found`);
|
|
201
|
+
}
|
|
202
|
+
return resolveRef(resolvedSchema, components);
|
|
203
|
+
}
|
|
204
|
+
return schema as OpenAPIV3.SchemaObject;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Function to convert OpenAPI spec to function signatures and request builders */
|
|
208
|
+
export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
|
|
209
|
+
functionSignatures: FunctionSignature[];
|
|
210
|
+
requestBuilders: Record<string, ActionRequest>;
|
|
211
|
+
} {
|
|
212
|
+
const functionSignatures: FunctionSignature[] = [];
|
|
213
|
+
const requestBuilders: Record<string, ActionRequest> = {};
|
|
214
|
+
|
|
215
|
+
// Base URL from OpenAPI spec servers
|
|
216
|
+
const baseUrl = openapiSpec.servers?.[0]?.url ?? '';
|
|
217
|
+
|
|
218
|
+
// Iterate over each path and method in the OpenAPI spec
|
|
219
|
+
for (const [path, methods] of Object.entries(openapiSpec.paths)) {
|
|
220
|
+
for (const [method, operation] of Object.entries(methods as OpenAPIV3.PathsObject)) {
|
|
221
|
+
const operationObj = operation as OpenAPIV3.OperationObject & {
|
|
222
|
+
'x-openai-isConsequential'?: boolean;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Operation ID is used as the function name
|
|
226
|
+
const operationId = operationObj.operationId || `${method}_${path}`;
|
|
227
|
+
const description = operationObj.summary || operationObj.description || '';
|
|
228
|
+
|
|
229
|
+
const parametersSchema: ParametersSchema = { type: 'object', properties: {}, required: [] };
|
|
230
|
+
|
|
231
|
+
if (operationObj.requestBody) {
|
|
232
|
+
const requestBody = operationObj.requestBody as OpenAPIV3.RequestBodyObject;
|
|
233
|
+
const content = requestBody.content;
|
|
234
|
+
const contentType = Object.keys(content)[0];
|
|
235
|
+
const schema = content[contentType]?.schema;
|
|
236
|
+
const resolvedSchema = resolveRef(
|
|
237
|
+
schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
|
|
238
|
+
openapiSpec.components,
|
|
239
|
+
);
|
|
240
|
+
parametersSchema.properties['requestBody'] = resolvedSchema;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (operationObj.parameters) {
|
|
244
|
+
for (const param of operationObj.parameters) {
|
|
245
|
+
const paramObj = param as OpenAPIV3.ParameterObject;
|
|
246
|
+
const resolvedSchema = resolveRef(
|
|
247
|
+
{ ...paramObj.schema } as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
|
|
248
|
+
openapiSpec.components,
|
|
249
|
+
);
|
|
250
|
+
parametersSchema.properties[paramObj.name] = resolvedSchema;
|
|
251
|
+
if (paramObj.required) {
|
|
252
|
+
parametersSchema.required.push(paramObj.name);
|
|
253
|
+
}
|
|
254
|
+
if (paramObj.description && !('$$ref' in parametersSchema.properties[paramObj.name])) {
|
|
255
|
+
parametersSchema.properties[paramObj.name].description = paramObj.description;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const functionSignature = new FunctionSignature(operationId, description, parametersSchema);
|
|
261
|
+
functionSignatures.push(functionSignature);
|
|
262
|
+
|
|
263
|
+
const actionRequest = new ActionRequest(
|
|
264
|
+
baseUrl,
|
|
265
|
+
path,
|
|
266
|
+
method,
|
|
267
|
+
operationId,
|
|
268
|
+
!!operationObj['x-openai-isConsequential'], // Custom extension for consequential actions
|
|
269
|
+
operationObj.requestBody ? 'application/json' : 'application/x-www-form-urlencoded',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
requestBuilders[operationId] = actionRequest;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { functionSignatures, requestBuilders };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export type ValidationResult = {
|
|
280
|
+
status: boolean;
|
|
281
|
+
message: string;
|
|
282
|
+
spec?: OpenAPIV3.Document;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export function validateAndParseOpenAPISpec(specString: string): ValidationResult {
|
|
286
|
+
try {
|
|
287
|
+
let parsedSpec;
|
|
288
|
+
try {
|
|
289
|
+
parsedSpec = JSON.parse(specString);
|
|
290
|
+
} catch {
|
|
291
|
+
parsedSpec = load(specString);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check for servers
|
|
295
|
+
if (
|
|
296
|
+
!parsedSpec.servers ||
|
|
297
|
+
!Array.isArray(parsedSpec.servers) ||
|
|
298
|
+
parsedSpec.servers.length === 0
|
|
299
|
+
) {
|
|
300
|
+
return { status: false, message: 'Could not find a valid URL in `servers`' };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!parsedSpec.servers[0].url) {
|
|
304
|
+
return { status: false, message: 'Could not find a valid URL in `servers`' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Check for paths
|
|
308
|
+
const paths = parsedSpec.paths;
|
|
309
|
+
if (!paths || typeof paths !== 'object' || Object.keys(paths).length === 0) {
|
|
310
|
+
return { status: false, message: 'No paths found in the OpenAPI spec.' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const components = parsedSpec.components?.schemas || {};
|
|
314
|
+
const messages = [];
|
|
315
|
+
|
|
316
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
317
|
+
for (const [httpMethod, operation] of Object.entries(methods as OpenAPIV3.PathItemObject)) {
|
|
318
|
+
// Ensure operation is a valid operation object
|
|
319
|
+
const { responses } = operation as OpenAPIV3.OperationObject;
|
|
320
|
+
if (typeof operation === 'object' && responses) {
|
|
321
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
322
|
+
const content = (response as OpenAPIV3.ResponseObject).content;
|
|
323
|
+
if (content && content['application/json'] && content['application/json'].schema) {
|
|
324
|
+
const schema = content['application/json'].schema;
|
|
325
|
+
if ('$ref' in schema && typeof schema.$ref === 'string') {
|
|
326
|
+
const refName = schema.$ref.split('/').pop();
|
|
327
|
+
if (refName && !components[refName]) {
|
|
328
|
+
messages.push(
|
|
329
|
+
`In context=('paths', '${path}', '${httpMethod}', '${statusCode}', 'response', 'content', 'application/json', 'schema'), reference to unknown component ${refName}; using empty schema`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
status: true,
|
|
341
|
+
message: messages.join('\n') || 'OpenAPI spec is valid.',
|
|
342
|
+
spec: parsedSpec,
|
|
343
|
+
};
|
|
344
|
+
} catch (error) {
|
|
345
|
+
return { status: false, message: 'Error parsing OpenAPI spec.' };
|
|
346
|
+
}
|
|
347
|
+
}
|