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.
- 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/types/accessPermissions.d.ts +744 -0
- package/dist/types/actions.d.ts +118 -0
- package/dist/types/api-endpoints.d.ts +150 -0
- package/dist/types/artifacts.d.ts +97 -0
- package/dist/types/azure.d.ts +22 -0
- package/dist/types/bedrock.d.ts +1220 -0
- package/dist/types/config.d.ts +14849 -0
- package/dist/types/config.spec.d.ts +1 -0
- package/dist/types/createPayload.d.ts +5 -0
- package/dist/types/data-service.d.ts +287 -0
- package/dist/types/feedback.d.ts +36 -0
- package/dist/types/file-config.d.ts +263 -0
- package/dist/types/file-config.spec.d.ts +1 -0
- package/dist/types/generate.d.ts +597 -0
- package/dist/types/headers-helpers.d.ts +2 -0
- package/{src/index.ts → dist/types/index.d.ts} +0 -15
- package/dist/types/keys.d.ts +92 -0
- package/dist/types/mcp.d.ts +2760 -0
- package/dist/types/messages.d.ts +10 -0
- package/dist/types/models.d.ts +1547 -0
- package/dist/types/parameterSettings.d.ts +69 -0
- package/dist/types/parsers.d.ts +110 -0
- package/dist/types/permissions.d.ts +522 -0
- package/dist/types/react-query/react-query-service.d.ts +85 -0
- package/dist/types/request.d.ts +25 -0
- package/dist/types/roles.d.ts +554 -0
- package/dist/types/roles.spec.d.ts +1 -0
- package/dist/types/schemas.d.ts +5110 -0
- package/dist/types/schemas.spec.d.ts +1 -0
- package/dist/types/types/agents.d.ts +433 -0
- package/dist/types/types/assistants.d.ts +547 -0
- package/dist/types/types/files.d.ts +172 -0
- package/dist/types/types/graph.d.ts +135 -0
- package/{src/types/mcpServers.ts → dist/types/types/mcpServers.d.ts} +12 -18
- package/dist/types/types/mutations.d.ts +209 -0
- package/dist/types/types/queries.d.ts +169 -0
- package/dist/types/types/runs.d.ts +36 -0
- package/dist/types/types/web.d.ts +520 -0
- package/dist/types/types.d.ts +503 -0
- package/dist/types/utils.d.ts +12 -0
- package/package.json +5 -1
- package/babel.config.js +0 -4
- package/check_updates.sh +0 -52
- package/jest.config.js +0 -19
- package/react-query/package-lock.json +0 -292
- package/react-query/package.json +0 -10
- package/rollup.config.js +0 -74
- package/server-rollup.config.js +0 -40
- package/specs/actions.spec.ts +0 -2533
- package/specs/api-endpoints-subdir.spec.ts +0 -140
- package/specs/api-endpoints.spec.ts +0 -74
- package/specs/azure.spec.ts +0 -844
- package/specs/bedrock.spec.ts +0 -862
- package/specs/filetypes.spec.ts +0 -175
- package/specs/generate.spec.ts +0 -770
- package/specs/headers-helpers.spec.ts +0 -24
- package/specs/mcp.spec.ts +0 -147
- package/specs/openapiSpecs.ts +0 -524
- package/specs/parsers.spec.ts +0 -601
- package/specs/request-interceptor.spec.ts +0 -304
- package/specs/utils.spec.ts +0 -196
- package/src/accessPermissions.ts +0 -346
- package/src/actions.ts +0 -813
- package/src/api-endpoints.ts +0 -440
- package/src/artifacts.ts +0 -3104
- package/src/azure.ts +0 -328
- package/src/bedrock.ts +0 -425
- package/src/config.spec.ts +0 -315
- package/src/config.ts +0 -2006
- package/src/createPayload.ts +0 -46
- package/src/data-service.ts +0 -1087
- package/src/feedback.ts +0 -141
- package/src/file-config.spec.ts +0 -1248
- package/src/file-config.ts +0 -764
- package/src/generate.ts +0 -634
- package/src/headers-helpers.ts +0 -13
- package/src/keys.ts +0 -99
- package/src/mcp.ts +0 -271
- package/src/messages.ts +0 -50
- package/src/models.ts +0 -69
- package/src/parameterSettings.ts +0 -1111
- package/src/parsers.ts +0 -563
- package/src/permissions.ts +0 -188
- package/src/react-query/react-query-service.ts +0 -566
- package/src/request.ts +0 -171
- package/src/roles.spec.ts +0 -132
- package/src/roles.ts +0 -225
- package/src/schemas.spec.ts +0 -355
- package/src/schemas.ts +0 -1234
- package/src/types/agents.ts +0 -470
- package/src/types/assistants.ts +0 -654
- package/src/types/files.ts +0 -191
- package/src/types/graph.ts +0 -145
- package/src/types/mutations.ts +0 -422
- package/src/types/queries.ts +0 -208
- package/src/types/runs.ts +0 -40
- package/src/types/web.ts +0 -588
- package/src/types.ts +0 -676
- package/src/utils.ts +0 -85
- package/tsconfig.json +0 -28
- package/tsconfig.spec.json +0 -10
- /package/{src/react-query/index.ts → dist/types/react-query/index.d.ts} +0 -0
- /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
|
-
}
|