librechat-data-provider 0.7.41 → 0.7.52

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.
@@ -348,3 +348,130 @@ components:
348
348
  message:
349
349
  type: string
350
350
  description: Confirmation of successful save or error message.`;
351
+
352
+ export const swapidev = `
353
+ openapi: 3.0.3
354
+ info:
355
+ title: Star Wars API
356
+ description: This is a simple API that provides information about the Star Wars universe.
357
+ version: 1.0.0
358
+ servers:
359
+ - url: https://swapi.dev
360
+
361
+ paths:
362
+ /api/people:
363
+ get:
364
+ summary: List all people
365
+ operationId: getPeople
366
+ tags:
367
+ - People
368
+ responses:
369
+ '200':
370
+ description: A list of people
371
+ content:
372
+ application/json:
373
+ schema:
374
+ type: object
375
+ properties:
376
+ count:
377
+ type: integer
378
+ example: 82
379
+ next:
380
+ type: string
381
+ nullable: true
382
+ example: https://swapi.dev/api/people/?page=2
383
+ previous:
384
+ type: string
385
+ nullable: true
386
+ example: null
387
+ results:
388
+ type: array
389
+ items:
390
+ $ref: '#/components/schemas/Person'
391
+
392
+ /api/people/{id}:
393
+ get:
394
+ summary: Get a person by ID
395
+ operationId: getPersonById
396
+ tags:
397
+ - People
398
+ parameters:
399
+ - name: id
400
+ in: path
401
+ required: true
402
+ description: The ID of the person to retrieve
403
+ schema:
404
+ type: string
405
+ responses:
406
+ '200':
407
+ description: A single person
408
+ content:
409
+ application/json:
410
+ schema:
411
+ $ref: '#/components/schemas/Person'
412
+ '404':
413
+ description: Person not found
414
+
415
+ components:
416
+ schemas:
417
+ Person:
418
+ type: object
419
+ properties:
420
+ name:
421
+ type: string
422
+ example: Luke Skywalker
423
+ height:
424
+ type: string
425
+ example: "172"
426
+ mass:
427
+ type: string
428
+ example: "77"
429
+ hair_color:
430
+ type: string
431
+ example: blond
432
+ skin_color:
433
+ type: string
434
+ example: fair
435
+ eye_color:
436
+ type: string
437
+ example: blue
438
+ birth_year:
439
+ type: string
440
+ example: "19BBY"
441
+ gender:
442
+ type: string
443
+ example: male
444
+ homeworld:
445
+ type: string
446
+ example: https://swapi.dev/api/planets/1/
447
+ films:
448
+ type: array
449
+ items:
450
+ type: string
451
+ example: https://swapi.dev/api/films/1/
452
+ species:
453
+ type: array
454
+ items:
455
+ type: string
456
+ example: https://swapi.dev/api/species/1/
457
+ vehicles:
458
+ type: array
459
+ items:
460
+ type: string
461
+ example: https://swapi.dev/api/vehicles/14/
462
+ starships:
463
+ type: array
464
+ items:
465
+ type: string
466
+ example: https://swapi.dev/api/starships/12/
467
+ created:
468
+ type: string
469
+ format: date-time
470
+ example: 2014-12-09T13:50:51.644000Z
471
+ edited:
472
+ type: string
473
+ format: date-time
474
+ example: 2014-12-20T21:17:56.891000Z
475
+ url:
476
+ type: string
477
+ example: https://swapi.dev/api/people/1/`;
package/src/actions.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import axios from 'axios';
2
3
  import { URL } from 'url';
3
4
  import crypto from 'crypto';
@@ -12,6 +13,11 @@ export type ParametersSchema = {
12
13
  required: string[];
13
14
  };
14
15
 
16
+ export type OpenAPISchema = OpenAPIV3.SchemaObject &
17
+ ParametersSchema & {
18
+ items?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject;
19
+ };
20
+
15
21
  export type ApiKeyCredentials = {
16
22
  api_key: string;
17
23
  custom_auth_header?: string;
@@ -27,6 +33,16 @@ export type OAuthCredentials = {
27
33
 
28
34
  export type Credentials = ApiKeyCredentials | OAuthCredentials;
29
35
 
36
+ type MediaTypeObject =
37
+ | undefined
38
+ | {
39
+ [media: string]: OpenAPIV3.MediaTypeObject | undefined;
40
+ };
41
+
42
+ type RequestBodyObject = Omit<OpenAPIV3.RequestBodyObject, 'content'> & {
43
+ content: MediaTypeObject;
44
+ };
45
+
30
46
  export function sha1(input: string) {
31
47
  return crypto.createHash('sha1').update(input).digest('hex');
32
48
  }
@@ -38,6 +54,70 @@ export function createURL(domain: string, path: string) {
38
54
  return new URL(fullURL).toString();
39
55
  }
40
56
 
57
+ const schemaTypeHandlers: Record<string, (schema: OpenAPISchema) => z.ZodTypeAny> = {
58
+ string: (schema) => {
59
+ if (schema.enum) {
60
+ return z.enum(schema.enum as [string, ...string[]]);
61
+ }
62
+
63
+ let stringSchema = z.string();
64
+ if (schema.minLength !== undefined) {
65
+ stringSchema = stringSchema.min(schema.minLength);
66
+ }
67
+ if (schema.maxLength !== undefined) {
68
+ stringSchema = stringSchema.max(schema.maxLength);
69
+ }
70
+ return stringSchema;
71
+ },
72
+ number: (schema) => {
73
+ let numberSchema = z.number();
74
+ if (schema.minimum !== undefined) {
75
+ numberSchema = numberSchema.min(schema.minimum);
76
+ }
77
+ if (schema.maximum !== undefined) {
78
+ numberSchema = numberSchema.max(schema.maximum);
79
+ }
80
+ return numberSchema;
81
+ },
82
+ integer: (schema) => (schemaTypeHandlers.number(schema) as z.ZodNumber).int(),
83
+ boolean: () => z.boolean(),
84
+ array: (schema) => {
85
+ if (schema.items) {
86
+ const zodSchema = openAPISchemaToZod(schema.items as OpenAPISchema);
87
+ if (zodSchema) {
88
+ return z.array(zodSchema);
89
+ }
90
+
91
+ return z.array(z.unknown());
92
+ }
93
+ return z.array(z.unknown());
94
+ },
95
+ object: (schema) => {
96
+ const shape: { [key: string]: z.ZodTypeAny } = {};
97
+ if (schema.properties) {
98
+ Object.entries(schema.properties).forEach(([key, value]) => {
99
+ const zodSchema = openAPISchemaToZod(value as OpenAPISchema);
100
+ shape[key] = zodSchema || z.unknown();
101
+ if (schema.required && schema.required.includes(key)) {
102
+ shape[key] = shape[key].describe(value.description || '');
103
+ } else {
104
+ shape[key] = shape[key].optional().describe(value.description || '');
105
+ }
106
+ });
107
+ }
108
+ return z.object(shape);
109
+ },
110
+ };
111
+
112
+ function openAPISchemaToZod(schema: OpenAPISchema): z.ZodTypeAny | undefined {
113
+ if (schema.type === 'object' && Object.keys(schema.properties || {}).length === 0) {
114
+ return undefined;
115
+ }
116
+
117
+ const handler = schemaTypeHandlers[schema.type as string] || (() => z.unknown());
118
+ return handler(schema);
119
+ }
120
+
41
121
  export class FunctionSignature {
42
122
  name: string;
43
123
  description: string;
@@ -46,11 +126,7 @@ export class FunctionSignature {
46
126
  constructor(name: string, description: string, parameters: ParametersSchema) {
47
127
  this.name = name;
48
128
  this.description = description;
49
- if (parameters.properties?.['requestBody']) {
50
- this.parameters = parameters.properties?.['requestBody'] as ParametersSchema;
51
- } else {
52
- this.parameters = parameters;
53
- }
129
+ this.parameters = parameters;
54
130
  }
55
131
 
56
132
  toObjectTool(): FunctionTool {
@@ -64,39 +140,31 @@ export class FunctionSignature {
64
140
  };
65
141
  }
66
142
  }
143
+ class RequestConfig {
144
+ constructor(
145
+ readonly domain: string,
146
+ readonly basePath: string,
147
+ readonly method: string,
148
+ readonly operation: string,
149
+ readonly isConsequential: boolean,
150
+ readonly contentType: string,
151
+ ) {}
152
+ }
67
153
 
68
- export class ActionRequest {
69
- domain: string;
154
+ class RequestExecutor {
70
155
  path: string;
71
- method: string;
72
- operation: string;
73
- operationHash?: string;
74
- isConsequential: boolean;
75
- contentType: string;
76
156
  params?: object;
77
-
78
- constructor(
79
- domain: string,
80
- path: string,
81
- method: string,
82
- operation: string,
83
- isConsequential: boolean,
84
- contentType: string,
85
- ) {
86
- this.domain = domain;
87
- this.path = path;
88
- this.method = method;
89
- this.operation = operation;
90
- this.isConsequential = isConsequential;
91
- this.contentType = contentType;
92
- }
93
-
157
+ private operationHash?: string;
94
158
  private authHeaders: Record<string, string> = {};
95
159
  private authToken?: string;
96
160
 
161
+ constructor(private config: RequestConfig) {
162
+ this.path = config.basePath;
163
+ }
164
+
97
165
  setParams(params: object) {
98
166
  this.operationHash = sha1(JSON.stringify(params));
99
- this.params = params;
167
+ this.params = Object.assign({}, params);
100
168
 
101
169
  for (const [key, value] of Object.entries(params)) {
102
170
  const paramPattern = `{${key}}`;
@@ -105,11 +173,12 @@ export class ActionRequest {
105
173
  delete (this.params as Record<string, unknown>)[key];
106
174
  }
107
175
  }
176
+ return this;
108
177
  }
109
178
 
110
179
  async setAuth(metadata: ActionMetadata) {
111
180
  if (!metadata.auth) {
112
- return;
181
+ return this;
113
182
  }
114
183
 
115
184
  const {
@@ -154,7 +223,6 @@ export class ActionRequest {
154
223
  ) {
155
224
  this.authHeaders[custom_auth_header] = api_key;
156
225
  } else if (isOAuth) {
157
- // TODO: WIP - OAuth support
158
226
  if (!this.authToken) {
159
227
  const tokenResponse = await axios.post(
160
228
  client_url,
@@ -172,16 +240,17 @@ export class ActionRequest {
172
240
  }
173
241
  this.authHeaders['Authorization'] = `Bearer ${this.authToken}`;
174
242
  }
243
+ return this;
175
244
  }
176
245
 
177
246
  async execute() {
178
- const url = createURL(this.domain, this.path);
247
+ const url = createURL(this.config.domain, this.path);
179
248
  const headers = {
180
249
  ...this.authHeaders,
181
- 'Content-Type': this.contentType,
250
+ 'Content-Type': this.config.contentType,
182
251
  };
183
252
 
184
- const method = this.method.toLowerCase();
253
+ const method = this.config.method.toLowerCase();
185
254
 
186
255
  if (method === 'get') {
187
256
  return axios.get(url, { headers, params: this.params });
@@ -194,13 +263,73 @@ export class ActionRequest {
194
263
  } else if (method === 'patch') {
195
264
  return axios.patch(url, this.params, { headers });
196
265
  } else {
197
- throw new Error(`Unsupported HTTP method: ${this.method}`);
266
+ throw new Error(`Unsupported HTTP method: ${method}`);
198
267
  }
199
268
  }
269
+
270
+ getConfig() {
271
+ return this.config;
272
+ }
273
+ }
274
+
275
+ export class ActionRequest {
276
+ private config: RequestConfig;
277
+
278
+ constructor(
279
+ domain: string,
280
+ path: string,
281
+ method: string,
282
+ operation: string,
283
+ isConsequential: boolean,
284
+ contentType: string,
285
+ ) {
286
+ this.config = new RequestConfig(domain, path, method, operation, isConsequential, contentType);
287
+ }
288
+
289
+ // Add getters to maintain backward compatibility
290
+ get domain() {
291
+ return this.config.domain;
292
+ }
293
+ get path() {
294
+ return this.config.basePath;
295
+ }
296
+ get method() {
297
+ return this.config.method;
298
+ }
299
+ get operation() {
300
+ return this.config.operation;
301
+ }
302
+ get isConsequential() {
303
+ return this.config.isConsequential;
304
+ }
305
+ get contentType() {
306
+ return this.config.contentType;
307
+ }
308
+
309
+ createExecutor() {
310
+ return new RequestExecutor(this.config);
311
+ }
312
+
313
+ // Maintain backward compatibility by delegating to a new executor
314
+ setParams(params: object) {
315
+ const executor = this.createExecutor();
316
+ executor.setParams(params);
317
+ return executor;
318
+ }
319
+
320
+ async setAuth(metadata: ActionMetadata) {
321
+ const executor = this.createExecutor();
322
+ return executor.setAuth(metadata);
323
+ }
324
+
325
+ async execute() {
326
+ const executor = this.createExecutor();
327
+ return executor.execute();
328
+ }
200
329
  }
201
330
 
202
331
  export function resolveRef(
203
- schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject,
332
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | RequestBodyObject,
204
333
  components?: OpenAPIV3.ComponentsObject,
205
334
  ): OpenAPIV3.SchemaObject {
206
335
  if ('$ref' in schema && components) {
@@ -219,14 +348,17 @@ function sanitizeOperationId(input: string) {
219
348
  }
220
349
 
221
350
  /** Function to convert OpenAPI spec to function signatures and request builders */
222
- export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
351
+ export function openapiToFunction(
352
+ openapiSpec: OpenAPIV3.Document,
353
+ generateZodSchemas = false,
354
+ ): {
223
355
  functionSignatures: FunctionSignature[];
224
356
  requestBuilders: Record<string, ActionRequest>;
357
+ zodSchemas?: Record<string, z.ZodTypeAny>;
225
358
  } {
226
359
  const functionSignatures: FunctionSignature[] = [];
227
360
  const requestBuilders: Record<string, ActionRequest> = {};
228
-
229
- // Base URL from OpenAPI spec servers
361
+ const zodSchemas: Record<string, z.ZodTypeAny> = {};
230
362
  const baseUrl = openapiSpec.servers?.[0]?.url ?? '';
231
363
 
232
364
  // Iterate over each path and method in the OpenAPI spec
@@ -241,19 +373,11 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
241
373
  const operationId = operationObj.operationId || sanitizeOperationId(defaultOperationId);
242
374
  const description = operationObj.summary || operationObj.description || '';
243
375
 
244
- const parametersSchema: ParametersSchema = { type: 'object', properties: {}, required: [] };
245
-
246
- if (operationObj.requestBody) {
247
- const requestBody = operationObj.requestBody as OpenAPIV3.RequestBodyObject;
248
- const content = requestBody.content;
249
- const contentType = Object.keys(content)[0];
250
- const schema = content[contentType]?.schema;
251
- const resolvedSchema = resolveRef(
252
- schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
253
- openapiSpec.components,
254
- );
255
- parametersSchema.properties['requestBody'] = resolvedSchema;
256
- }
376
+ const parametersSchema: OpenAPISchema = {
377
+ type: 'object',
378
+ properties: {},
379
+ required: [],
380
+ };
257
381
 
258
382
  if (operationObj.parameters) {
259
383
  for (const param of operationObj.parameters) {
@@ -263,12 +387,27 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
263
387
  openapiSpec.components,
264
388
  );
265
389
  parametersSchema.properties[paramObj.name] = resolvedSchema;
266
- if (paramObj.required) {
390
+ if (paramObj.required === true) {
267
391
  parametersSchema.required.push(paramObj.name);
268
392
  }
269
- if (paramObj.description && !('$$ref' in parametersSchema.properties[paramObj.name])) {
270
- parametersSchema.properties[paramObj.name].description = paramObj.description;
271
- }
393
+ }
394
+ }
395
+
396
+ if (operationObj.requestBody) {
397
+ const requestBody = operationObj.requestBody as RequestBodyObject;
398
+ const content = requestBody.content;
399
+ const contentType = Object.keys(content ?? {})[0];
400
+ const schema = content?.[contentType]?.schema;
401
+ const resolvedSchema = resolveRef(
402
+ schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
403
+ openapiSpec.components,
404
+ );
405
+ parametersSchema.properties = {
406
+ ...parametersSchema.properties,
407
+ ...resolvedSchema.properties,
408
+ };
409
+ if (resolvedSchema.required) {
410
+ parametersSchema.required.push(...resolvedSchema.required);
272
411
  }
273
412
  }
274
413
 
@@ -280,15 +419,22 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
280
419
  path,
281
420
  method,
282
421
  operationId,
283
- !!operationObj['x-openai-isConsequential'], // Custom extension for consequential actions
422
+ !!(operationObj['x-openai-isConsequential'] ?? false), // Custom extension for consequential actions
284
423
  operationObj.requestBody ? 'application/json' : 'application/x-www-form-urlencoded',
285
424
  );
286
425
 
287
426
  requestBuilders[operationId] = actionRequest;
427
+
428
+ if (generateZodSchemas && Object.keys(parametersSchema.properties).length > 0) {
429
+ const schema = openAPISchemaToZod(parametersSchema);
430
+ if (schema) {
431
+ zodSchemas[operationId] = schema;
432
+ }
433
+ }
288
434
  }
289
435
  }
290
436
 
291
- return { functionSignatures, requestBuilders };
437
+ return { functionSignatures, requestBuilders, zodSchemas };
292
438
  }
293
439
 
294
440
  export type ValidationResult = {
@@ -331,10 +477,10 @@ export function validateAndParseOpenAPISpec(specString: string): ValidationResul
331
477
  for (const [path, methods] of Object.entries(paths)) {
332
478
  for (const [httpMethod, operation] of Object.entries(methods as OpenAPIV3.PathItemObject)) {
333
479
  // Ensure operation is a valid operation object
334
- const { responses } = operation as OpenAPIV3.OperationObject;
480
+ const { responses } = operation as OpenAPIV3.OperationObject | { responses: undefined };
335
481
  if (typeof operation === 'object' && responses) {
336
482
  for (const [statusCode, response] of Object.entries(responses)) {
337
- const content = (response as OpenAPIV3.ResponseObject).content;
483
+ const content = (response as OpenAPIV3.ResponseObject).content as MediaTypeObject;
338
484
  if (content && content['application/json'] && content['application/json'].schema) {
339
485
  const schema = content['application/json'].schema;
340
486
  if ('$ref' in schema && typeof schema.$ref === 'string') {
@@ -125,6 +125,21 @@ export const assistants = ({
125
125
  return url;
126
126
  };
127
127
 
128
+ export const agents = ({ path = '', options }: { path?: string; options?: object }) => {
129
+ let url = '/api/agents';
130
+
131
+ if (path && path !== '') {
132
+ url += `/${path}`;
133
+ }
134
+
135
+ if (options && Object.keys(options).length > 0) {
136
+ const queryParams = new URLSearchParams(options as Record<string, string>).toString();
137
+ url += `?${queryParams}`;
138
+ }
139
+
140
+ return url;
141
+ };
142
+
128
143
  export const files = () => '/api/files';
129
144
 
130
145
  export const images = () => `${files()}/images`;
@@ -193,7 +208,8 @@ export const updatePromptPermissions = (roleName: string) =>
193
208
  `${roles()}/${roleName.toLowerCase()}/prompts`;
194
209
 
195
210
  /* Conversation Tags */
196
- export const conversationTags = (tag?: string) => `/api/tags${tag ? `/${tag}` : ''}`;
211
+ export const conversationTags = (tag?: string) =>
212
+ `/api/tags${tag != null && tag ? `/${encodeURIComponent(tag)}` : ''}`;
197
213
 
198
214
  export const conversationTagsList = (pageNumber: string, sort?: string, order?: string) =>
199
215
  `${conversationTags()}/list?pageNumber=${pageNumber}${sort ? `&sort=${sort}` : ''}${
@@ -201,4 +217,8 @@ export const conversationTagsList = (pageNumber: string, sort?: string, order?:
201
217
  }`;
202
218
 
203
219
  export const addTagToConversation = (conversationId: string) =>
204
- `${conversationsRoot}/tags/${conversationId}`;
220
+ `${conversationTags()}/convo/${conversationId}`;
221
+
222
+ export const userTerms = () => '/api/user/terms';
223
+ export const acceptUserTerms = () => '/api/user/terms/accept';
224
+ export const banner = () => '/api/banner';