librechat-data-provider 0.7.430 → 0.7.692

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/src/actions.ts CHANGED
@@ -33,6 +33,16 @@ export type OAuthCredentials = {
33
33
 
34
34
  export type Credentials = ApiKeyCredentials | OAuthCredentials;
35
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
+
36
46
  export function sha1(input: string) {
37
47
  return crypto.createHash('sha1').update(input).digest('hex');
38
48
  }
@@ -130,39 +140,31 @@ export class FunctionSignature {
130
140
  };
131
141
  }
132
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
+ }
133
153
 
134
- export class ActionRequest {
135
- domain: string;
154
+ class RequestExecutor {
136
155
  path: string;
137
- method: string;
138
- operation: string;
139
- operationHash?: string;
140
- isConsequential: boolean;
141
- contentType: string;
142
156
  params?: object;
143
-
144
- constructor(
145
- domain: string,
146
- path: string,
147
- method: string,
148
- operation: string,
149
- isConsequential: boolean,
150
- contentType: string,
151
- ) {
152
- this.domain = domain;
153
- this.path = path;
154
- this.method = method;
155
- this.operation = operation;
156
- this.isConsequential = isConsequential;
157
- this.contentType = contentType;
158
- }
159
-
157
+ private operationHash?: string;
160
158
  private authHeaders: Record<string, string> = {};
161
159
  private authToken?: string;
162
160
 
161
+ constructor(private config: RequestConfig) {
162
+ this.path = config.basePath;
163
+ }
164
+
163
165
  setParams(params: object) {
164
166
  this.operationHash = sha1(JSON.stringify(params));
165
- this.params = params;
167
+ this.params = Object.assign({}, params);
166
168
 
167
169
  for (const [key, value] of Object.entries(params)) {
168
170
  const paramPattern = `{${key}}`;
@@ -171,11 +173,12 @@ export class ActionRequest {
171
173
  delete (this.params as Record<string, unknown>)[key];
172
174
  }
173
175
  }
176
+ return this;
174
177
  }
175
178
 
176
179
  async setAuth(metadata: ActionMetadata) {
177
180
  if (!metadata.auth) {
178
- return;
181
+ return this;
179
182
  }
180
183
 
181
184
  const {
@@ -198,15 +201,21 @@ export class ActionRequest {
198
201
  oauth_client_secret,
199
202
  } = metadata;
200
203
 
201
- const isApiKey = api_key && type === AuthTypeEnum.ServiceHttp;
202
- const isOAuth =
204
+ const isApiKey = api_key != null && api_key.length > 0 && type === AuthTypeEnum.ServiceHttp;
205
+ const isOAuth = !!(
206
+ oauth_client_id != null &&
203
207
  oauth_client_id &&
208
+ oauth_client_secret != null &&
204
209
  oauth_client_secret &&
205
210
  type === AuthTypeEnum.OAuth &&
211
+ authorization_url != null &&
206
212
  authorization_url &&
213
+ client_url != null &&
207
214
  client_url &&
215
+ scope != null &&
208
216
  scope &&
209
- token_exchange_method;
217
+ token_exchange_method
218
+ );
210
219
 
211
220
  if (isApiKey && authorization_type === AuthorizationTypeEnum.Basic) {
212
221
  const basicToken = Buffer.from(api_key).toString('base64');
@@ -216,12 +225,13 @@ export class ActionRequest {
216
225
  } else if (
217
226
  isApiKey &&
218
227
  authorization_type === AuthorizationTypeEnum.Custom &&
228
+ custom_auth_header != null &&
219
229
  custom_auth_header
220
230
  ) {
221
231
  this.authHeaders[custom_auth_header] = api_key;
222
232
  } else if (isOAuth) {
223
- // TODO: WIP - OAuth support
224
- if (!this.authToken) {
233
+ const authToken = this.authToken ?? '';
234
+ if (!authToken) {
225
235
  const tokenResponse = await axios.post(
226
236
  client_url,
227
237
  {
@@ -238,16 +248,17 @@ export class ActionRequest {
238
248
  }
239
249
  this.authHeaders['Authorization'] = `Bearer ${this.authToken}`;
240
250
  }
251
+ return this;
241
252
  }
242
253
 
243
254
  async execute() {
244
- const url = createURL(this.domain, this.path);
255
+ const url = createURL(this.config.domain, this.path);
245
256
  const headers = {
246
257
  ...this.authHeaders,
247
- 'Content-Type': this.contentType,
258
+ 'Content-Type': this.config.contentType,
248
259
  };
249
260
 
250
- const method = this.method.toLowerCase();
261
+ const method = this.config.method.toLowerCase();
251
262
 
252
263
  if (method === 'get') {
253
264
  return axios.get(url, { headers, params: this.params });
@@ -260,13 +271,73 @@ export class ActionRequest {
260
271
  } else if (method === 'patch') {
261
272
  return axios.patch(url, this.params, { headers });
262
273
  } else {
263
- throw new Error(`Unsupported HTTP method: ${this.method}`);
274
+ throw new Error(`Unsupported HTTP method: ${method}`);
264
275
  }
265
276
  }
277
+
278
+ getConfig() {
279
+ return this.config;
280
+ }
281
+ }
282
+
283
+ export class ActionRequest {
284
+ private config: RequestConfig;
285
+
286
+ constructor(
287
+ domain: string,
288
+ path: string,
289
+ method: string,
290
+ operation: string,
291
+ isConsequential: boolean,
292
+ contentType: string,
293
+ ) {
294
+ this.config = new RequestConfig(domain, path, method, operation, isConsequential, contentType);
295
+ }
296
+
297
+ // Add getters to maintain backward compatibility
298
+ get domain() {
299
+ return this.config.domain;
300
+ }
301
+ get path() {
302
+ return this.config.basePath;
303
+ }
304
+ get method() {
305
+ return this.config.method;
306
+ }
307
+ get operation() {
308
+ return this.config.operation;
309
+ }
310
+ get isConsequential() {
311
+ return this.config.isConsequential;
312
+ }
313
+ get contentType() {
314
+ return this.config.contentType;
315
+ }
316
+
317
+ createExecutor() {
318
+ return new RequestExecutor(this.config);
319
+ }
320
+
321
+ // Maintain backward compatibility by delegating to a new executor
322
+ setParams(params: object) {
323
+ const executor = this.createExecutor();
324
+ executor.setParams(params);
325
+ return executor;
326
+ }
327
+
328
+ async setAuth(metadata: ActionMetadata) {
329
+ const executor = this.createExecutor();
330
+ return executor.setAuth(metadata);
331
+ }
332
+
333
+ async execute() {
334
+ const executor = this.createExecutor();
335
+ return executor.execute();
336
+ }
266
337
  }
267
338
 
268
339
  export function resolveRef(
269
- schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject,
340
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | RequestBodyObject,
270
341
  components?: OpenAPIV3.ComponentsObject,
271
342
  ): OpenAPIV3.SchemaObject {
272
343
  if ('$ref' in schema && components) {
@@ -324,17 +395,17 @@ export function openapiToFunction(
324
395
  openapiSpec.components,
325
396
  );
326
397
  parametersSchema.properties[paramObj.name] = resolvedSchema;
327
- if (paramObj.required) {
398
+ if (paramObj.required === true) {
328
399
  parametersSchema.required.push(paramObj.name);
329
400
  }
330
401
  }
331
402
  }
332
403
 
333
404
  if (operationObj.requestBody) {
334
- const requestBody = operationObj.requestBody as OpenAPIV3.RequestBodyObject;
405
+ const requestBody = operationObj.requestBody as RequestBodyObject;
335
406
  const content = requestBody.content;
336
- const contentType = Object.keys(content)[0];
337
- const schema = content[contentType]?.schema;
407
+ const contentType = Object.keys(content ?? {})[0];
408
+ const schema = content?.[contentType]?.schema;
338
409
  const resolvedSchema = resolveRef(
339
410
  schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
340
411
  openapiSpec.components,
@@ -356,7 +427,7 @@ export function openapiToFunction(
356
427
  path,
357
428
  method,
358
429
  operationId,
359
- !!operationObj['x-openai-isConsequential'], // Custom extension for consequential actions
430
+ !!(operationObj['x-openai-isConsequential'] ?? false), // Custom extension for consequential actions
360
431
  operationObj.requestBody ? 'application/json' : 'application/x-www-form-urlencoded',
361
432
  );
362
433
 
@@ -414,10 +485,10 @@ export function validateAndParseOpenAPISpec(specString: string): ValidationResul
414
485
  for (const [path, methods] of Object.entries(paths)) {
415
486
  for (const [httpMethod, operation] of Object.entries(methods as OpenAPIV3.PathItemObject)) {
416
487
  // Ensure operation is a valid operation object
417
- const { responses } = operation as OpenAPIV3.OperationObject;
488
+ const { responses } = operation as OpenAPIV3.OperationObject | { responses: undefined };
418
489
  if (typeof operation === 'object' && responses) {
419
490
  for (const [statusCode, response] of Object.entries(responses)) {
420
- const content = (response as OpenAPIV3.ResponseObject).content;
491
+ const content = (response as OpenAPIV3.ResponseObject).content as MediaTypeObject;
421
492
  if (content && content['application/json'] && content['application/json'].schema) {
422
493
  const schema = content['application/json'].schema;
423
494
  if ('$ref' in schema && typeof schema.$ref === 'string') {
@@ -34,9 +34,9 @@ export const abortRequest = (endpoint: string) => `/api/ask/${endpoint}/abort`;
34
34
  export const conversationsRoot = '/api/convos';
35
35
 
36
36
  export const conversations = (pageNumber: string, isArchived?: boolean, tags?: string[]) =>
37
- `${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}${tags
38
- ?.map((tag) => `&tags=${tag}`)
39
- .join('')}`;
37
+ `${conversationsRoot}?pageNumber=${pageNumber}${
38
+ isArchived === true ? '&isArchived=true' : ''
39
+ }${tags?.map((tag) => `&tags=${tag}`).join('')}`;
40
40
 
41
41
  export const conversationById = (id: string) => `${conversationsRoot}/${id}`;
42
42
 
@@ -50,6 +50,8 @@ export const importConversation = () => `${conversationsRoot}/import`;
50
50
 
51
51
  export const forkConversation = () => `${conversationsRoot}/fork`;
52
52
 
53
+ export const duplicateConversation = () => `${conversationsRoot}/duplicate`;
54
+
53
55
  export const search = (q: string, pageNumber: string) =>
54
56
  `/api/search?q=${q}&pageNumber=${pageNumber}`;
55
57
 
@@ -77,7 +79,8 @@ export const loginFacebook = () => '/api/auth/facebook';
77
79
 
78
80
  export const loginGoogle = () => '/api/auth/google';
79
81
 
80
- export const refreshToken = (retry?: boolean) => `/api/auth/refresh${retry ? '?retry=true' : ''}`;
82
+ export const refreshToken = (retry?: boolean) =>
83
+ `/api/auth/refresh${retry === true ? '?retry=true' : ''}`;
81
84
 
82
85
  export const requestPasswordReset = () => '/api/auth/requestPasswordReset';
83
86
 
@@ -94,19 +97,21 @@ export const config = () => '/api/config';
94
97
  export const prompts = () => '/api/prompts';
95
98
 
96
99
  export const assistants = ({
97
- path,
100
+ path = '',
98
101
  options,
99
102
  version,
100
103
  endpoint,
104
+ isAvatar,
101
105
  }: {
102
106
  path?: string;
103
107
  options?: object;
104
108
  endpoint?: AssistantsEndpoint;
105
109
  version: number | string;
110
+ isAvatar?: boolean;
106
111
  }) => {
107
- let url = `/api/assistants/v${version}`;
112
+ let url = isAvatar === true ? `${images()}/assistants` : `/api/assistants/v${version}`;
108
113
 
109
- if (path) {
114
+ if (path && path !== '') {
110
115
  url += `/${path}`;
111
116
  }
112
117
 
@@ -125,10 +130,10 @@ export const assistants = ({
125
130
  return url;
126
131
  };
127
132
 
128
- export const agents = ({ path, options }: { path?: string; options?: object }) => {
133
+ export const agents = ({ path = '', options }: { path?: string; options?: object }) => {
129
134
  let url = '/api/agents';
130
135
 
131
- if (path) {
136
+ if (path && path !== '') {
132
137
  url += `/${path}`;
133
138
  }
134
139
 
@@ -204,8 +209,8 @@ export const getAllPromptGroups = () => `${prompts()}/all`;
204
209
  /* Roles */
205
210
  export const roles = () => '/api/roles';
206
211
  export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
207
- export const updatePromptPermissions = (roleName: string) =>
208
- `${roles()}/${roleName.toLowerCase()}/prompts`;
212
+ export const updatePromptPermissions = (roleName: string) => `${getRole(roleName)}/prompts`;
213
+ export const updateAgentPermissions = (roleName: string) => `${getRole(roleName)}/agents`;
209
214
 
210
215
  /* Conversation Tags */
211
216
  export const conversationTags = (tag?: string) =>
package/src/azure.ts CHANGED
@@ -63,13 +63,13 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati
63
63
  const {
64
64
  group: groupName,
65
65
  apiKey,
66
- instanceName,
67
- deploymentName,
68
- version,
69
- baseURL,
66
+ instanceName = '',
67
+ deploymentName = '',
68
+ version = '',
69
+ baseURL = '',
70
70
  additionalHeaders,
71
71
  models,
72
- serverless,
72
+ serverless = false,
73
73
  ...rest
74
74
  } = group;
75
75
 
@@ -120,9 +120,11 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati
120
120
  continue;
121
121
  }
122
122
 
123
+ const groupDeploymentName = group.deploymentName ?? '';
124
+ const groupVersion = group.version ?? '';
123
125
  if (typeof model === 'boolean') {
124
126
  // For boolean models, check if group-level deploymentName and version are present.
125
- if (!group.deploymentName || !group.version) {
127
+ if (!groupDeploymentName || !groupVersion) {
126
128
  errors.push(
127
129
  `Model "${modelName}" in group "${groupName}" is missing a deploymentName or version.`,
128
130
  );
@@ -133,11 +135,10 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati
133
135
  group: groupName,
134
136
  };
135
137
  } else {
138
+ const modelDeploymentName = model.deploymentName ?? '';
139
+ const modelVersion = model.version ?? '';
136
140
  // For object models, check if deploymentName and version are required but missing.
137
- if (
138
- (!model.deploymentName && !group.deploymentName) ||
139
- (!model.version && !group.version)
140
- ) {
141
+ if ((!modelDeploymentName && !groupDeploymentName) || (!modelVersion && !groupVersion)) {
141
142
  errors.push(
142
143
  `Model "${modelName}" in group "${groupName}" is missing a required deploymentName or version.`,
143
144
  );
@@ -146,8 +147,8 @@ export function validateAzureGroups(configs: TAzureGroups): TAzureConfigValidati
146
147
 
147
148
  modelGroupMap[modelName] = {
148
149
  group: groupName,
149
- // deploymentName: model.deploymentName || group.deploymentName,
150
- // version: model.version || group.version,
150
+ // deploymentName: modelDeploymentName || groupDeploymentName,
151
+ // version: modelVersion || groupVersion,
151
152
  };
152
153
  }
153
154
  }
@@ -190,26 +191,28 @@ export function mapModelToAzureConfig({
190
191
  );
191
192
  }
192
193
 
193
- const instanceName = groupConfig.instanceName;
194
+ const instanceName = groupConfig.instanceName ?? '';
194
195
 
195
- if (!instanceName && !groupConfig.serverless) {
196
+ if (!instanceName && groupConfig.serverless !== true) {
196
197
  throw new Error(
197
198
  `Group "${modelConfig.group}" is missing an instanceName for non-serverless configuration.`,
198
199
  );
199
200
  }
200
201
 
201
- if (groupConfig.serverless && !groupConfig.baseURL) {
202
+ const baseURL = groupConfig.baseURL ?? '';
203
+ if (groupConfig.serverless === true && !baseURL) {
202
204
  throw new Error(
203
205
  `Group "${modelConfig.group}" is missing the required base URL for serverless configuration.`,
204
206
  );
205
207
  }
206
208
 
207
- if (groupConfig.serverless) {
209
+ if (groupConfig.serverless === true) {
208
210
  const result: MappedAzureConfig = {
209
211
  azureOptions: {
212
+ azureOpenAIApiVersion: extractEnvVariable(groupConfig.version ?? ''),
210
213
  azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey),
211
214
  },
212
- baseURL: extractEnvVariable(groupConfig.baseURL as string),
215
+ baseURL: extractEnvVariable(baseURL),
213
216
  serverless: true,
214
217
  };
215
218
 
@@ -232,11 +235,11 @@ export function mapModelToAzureConfig({
232
235
  }
233
236
 
234
237
  const modelDetails = groupConfig.models[modelName];
235
- const { deploymentName, version } =
238
+ const { deploymentName = '', version = '' } =
236
239
  typeof modelDetails === 'object'
237
240
  ? {
238
- deploymentName: modelDetails.deploymentName || groupConfig.deploymentName,
239
- version: modelDetails.version || groupConfig.version,
241
+ deploymentName: modelDetails.deploymentName ?? groupConfig.deploymentName,
242
+ version: modelDetails.version ?? groupConfig.version,
240
243
  }
241
244
  : {
242
245
  deploymentName: groupConfig.deploymentName,
@@ -264,8 +267,8 @@ export function mapModelToAzureConfig({
264
267
 
265
268
  const result: MappedAzureConfig = { azureOptions };
266
269
 
267
- if (groupConfig.baseURL) {
268
- result.baseURL = extractEnvVariable(groupConfig.baseURL);
270
+ if (baseURL) {
271
+ result.baseURL = extractEnvVariable(baseURL);
269
272
  }
270
273
 
271
274
  if (groupConfig.additionalHeaders) {
@@ -287,15 +290,17 @@ export function mapGroupToAzureConfig({
287
290
  throw new Error(`Group named "${groupName}" not found in configuration.`);
288
291
  }
289
292
 
290
- const instanceName = groupConfig.instanceName as string;
293
+ const instanceName = groupConfig.instanceName ?? '';
294
+ const serverless = groupConfig.serverless ?? false;
295
+ const baseURL = groupConfig.baseURL ?? '';
291
296
 
292
- if (!instanceName && !groupConfig.serverless) {
297
+ if (!instanceName && !serverless) {
293
298
  throw new Error(
294
299
  `Group "${groupName}" is missing an instanceName for non-serverless configuration.`,
295
300
  );
296
301
  }
297
302
 
298
- if (groupConfig.serverless && !groupConfig.baseURL) {
303
+ if (serverless && !baseURL) {
299
304
  throw new Error(
300
305
  `Group "${groupName}" is missing the required base URL for serverless configuration.`,
301
306
  );
@@ -311,25 +316,26 @@ export function mapGroupToAzureConfig({
311
316
  const modelDetails = groupConfig.models[firstModelName];
312
317
 
313
318
  const azureOptions: AzureOptions = {
319
+ azureOpenAIApiVersion: extractEnvVariable(groupConfig.version ?? ''),
314
320
  azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey),
315
321
  azureOpenAIApiInstanceName: extractEnvVariable(instanceName),
316
322
  // DeploymentName and Version set below
317
323
  };
318
324
 
319
- if (groupConfig.serverless) {
325
+ if (serverless) {
320
326
  return {
321
327
  azureOptions,
322
- baseURL: extractEnvVariable(groupConfig.baseURL ?? ''),
328
+ baseURL: extractEnvVariable(baseURL),
323
329
  serverless: true,
324
330
  ...(groupConfig.additionalHeaders && { headers: groupConfig.additionalHeaders }),
325
331
  };
326
332
  }
327
333
 
328
- const { deploymentName, version } =
334
+ const { deploymentName = '', version = '' } =
329
335
  typeof modelDetails === 'object'
330
336
  ? {
331
- deploymentName: modelDetails.deploymentName || groupConfig.deploymentName,
332
- version: modelDetails.version || groupConfig.version,
337
+ deploymentName: modelDetails.deploymentName ?? groupConfig.deploymentName,
338
+ version: modelDetails.version ?? groupConfig.version,
333
339
  }
334
340
  : {
335
341
  deploymentName: groupConfig.deploymentName,
@@ -347,8 +353,8 @@ export function mapGroupToAzureConfig({
347
353
 
348
354
  const result: MappedAzureConfig = { azureOptions };
349
355
 
350
- if (groupConfig.baseURL) {
351
- result.baseURL = extractEnvVariable(groupConfig.baseURL);
356
+ if (baseURL) {
357
+ result.baseURL = extractEnvVariable(baseURL);
352
358
  }
353
359
 
354
360
  if (groupConfig.additionalHeaders) {