librechat-data-provider 0.4.2 → 0.4.4

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/azure.ts ADDED
@@ -0,0 +1,211 @@
1
+ import type { ZodError } from 'zod';
2
+ import type {
3
+ TAzureGroups,
4
+ TAzureGroupMap,
5
+ TAzureModelGroupMap,
6
+ TValidatedAzureConfig,
7
+ } from '../src/config';
8
+ import { errorsToString, extractEnvVariable, envVarRegex } from '../src/parsers';
9
+ import { azureGroupConfigsSchema } from '../src/config';
10
+
11
+ export const deprecatedAzureVariables = [
12
+ /* "related to" precedes description text */
13
+ { key: 'AZURE_OPENAI_DEFAULT_MODEL', description: 'setting a default model' },
14
+ { key: 'AZURE_OPENAI_MODELS', description: 'setting models' },
15
+ {
16
+ key: 'AZURE_USE_MODEL_AS_DEPLOYMENT_NAME',
17
+ description: 'using model names as deployment names',
18
+ },
19
+ { key: 'AZURE_API_KEY', description: 'setting a single Azure API key' },
20
+ { key: 'AZURE_OPENAI_API_INSTANCE_NAME', description: 'setting a single Azure instance name' },
21
+ {
22
+ key: 'AZURE_OPENAI_API_DEPLOYMENT_NAME',
23
+ description: 'setting a single Azure deployment name',
24
+ },
25
+ { key: 'AZURE_OPENAI_API_VERSION', description: 'setting a single Azure API version' },
26
+ {
27
+ key: 'AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME',
28
+ description: 'setting a single Azure completions deployment name',
29
+ },
30
+ {
31
+ key: 'AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME',
32
+ description: 'setting a single Azure embeddings deployment name',
33
+ },
34
+ {
35
+ key: 'PLUGINS_USE_AZURE',
36
+ description: 'using Azure for Plugins',
37
+ },
38
+ ];
39
+
40
+ export const conflictingAzureVariables = [
41
+ {
42
+ key: 'INSTANCE_NAME',
43
+ },
44
+ {
45
+ key: 'DEPLOYMENT_NAME',
46
+ },
47
+ ];
48
+
49
+ export function validateAzureGroups(configs: TAzureGroups): TValidatedAzureConfig & {
50
+ isValid: boolean;
51
+ errors: (ZodError | string)[];
52
+ } {
53
+ let isValid = true;
54
+ const modelNames: string[] = [];
55
+ const modelGroupMap: TAzureModelGroupMap = {};
56
+ const groupMap: TAzureGroupMap = {};
57
+ const errors: (ZodError | string)[] = [];
58
+
59
+ const result = azureGroupConfigsSchema.safeParse(configs);
60
+ if (!result.success) {
61
+ isValid = false;
62
+ errors.push(errorsToString(result.error.errors));
63
+ } else {
64
+ for (const group of result.data) {
65
+ const {
66
+ group: groupName,
67
+ apiKey,
68
+ instanceName,
69
+ deploymentName,
70
+ version,
71
+ baseURL,
72
+ additionalHeaders,
73
+ models,
74
+ } = group;
75
+
76
+ if (groupMap[groupName]) {
77
+ errors.push(`Duplicate group name detected: "${groupName}". Group names must be unique.`);
78
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
79
+ }
80
+
81
+ groupMap[groupName] = {
82
+ apiKey,
83
+ instanceName,
84
+ deploymentName,
85
+ version,
86
+ baseURL,
87
+ additionalHeaders,
88
+ models,
89
+ };
90
+
91
+ for (const modelName in group.models) {
92
+ modelNames.push(modelName);
93
+ const model = group.models[modelName];
94
+
95
+ if (modelGroupMap[modelName]) {
96
+ errors.push(
97
+ `Duplicate model name detected: "${modelName}". Model names must be unique across groups.`,
98
+ );
99
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
100
+ }
101
+
102
+ if (typeof model === 'boolean') {
103
+ // For boolean models, check if group-level deploymentName and version are present.
104
+ if (!group.deploymentName || !group.version) {
105
+ errors.push(
106
+ `Model "${modelName}" in group "${groupName}" is missing a deploymentName or version.`,
107
+ );
108
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
109
+ }
110
+
111
+ modelGroupMap[modelName] = {
112
+ group: groupName,
113
+ };
114
+ } else {
115
+ // For object models, check if deploymentName and version are required but missing.
116
+ if (
117
+ (!model.deploymentName && !group.deploymentName) ||
118
+ (!model.version && !group.version)
119
+ ) {
120
+ errors.push(
121
+ `Model "${modelName}" in group "${groupName}" is missing a required deploymentName or version.`,
122
+ );
123
+ return { isValid: false, modelNames, modelGroupMap, groupMap, errors };
124
+ }
125
+
126
+ modelGroupMap[modelName] = {
127
+ group: groupName,
128
+ // deploymentName: model.deploymentName || group.deploymentName,
129
+ // version: model.version || group.version,
130
+ };
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ return { isValid, modelNames, modelGroupMap, groupMap, errors };
137
+ }
138
+
139
+ type AzureOptions = {
140
+ azureOpenAIApiKey: string;
141
+ azureOpenAIApiInstanceName: string;
142
+ azureOpenAIApiDeploymentName: string;
143
+ azureOpenAIApiVersion: string;
144
+ };
145
+
146
+ type MappedAzureConfig = {
147
+ azureOptions: AzureOptions;
148
+ baseURL?: string;
149
+ headers?: Record<string, string>;
150
+ };
151
+
152
+ export function mapModelToAzureConfig({
153
+ modelName,
154
+ modelGroupMap,
155
+ groupMap,
156
+ }: Omit<TValidatedAzureConfig, 'modelNames'> & {
157
+ modelName: string;
158
+ }): MappedAzureConfig {
159
+ const modelConfig = modelGroupMap[modelName];
160
+ if (!modelConfig) {
161
+ throw new Error(`Model named "${modelName}" not found in configuration.`);
162
+ }
163
+
164
+ const groupConfig = groupMap[modelConfig.group];
165
+ if (!groupConfig) {
166
+ throw new Error(
167
+ `Group "${modelConfig.group}" for model "${modelName}" not found in configuration.`,
168
+ );
169
+ }
170
+
171
+ const modelDetails = groupConfig.models[modelName];
172
+ const deploymentName =
173
+ typeof modelDetails === 'object'
174
+ ? modelDetails.deploymentName || groupConfig.deploymentName
175
+ : groupConfig.deploymentName;
176
+ const version =
177
+ typeof modelDetails === 'object'
178
+ ? modelDetails.version || groupConfig.version
179
+ : groupConfig.version;
180
+
181
+ if (!deploymentName || !version) {
182
+ throw new Error(
183
+ `Model "${modelName}" in group "${modelConfig.group}" is missing a deploymentName ("${deploymentName}") or version ("${version}").`,
184
+ );
185
+ }
186
+
187
+ const azureOptions: AzureOptions = {
188
+ azureOpenAIApiKey: extractEnvVariable(groupConfig.apiKey),
189
+ azureOpenAIApiInstanceName: extractEnvVariable(groupConfig.instanceName),
190
+ azureOpenAIApiDeploymentName: extractEnvVariable(deploymentName),
191
+ azureOpenAIApiVersion: extractEnvVariable(version),
192
+ };
193
+
194
+ for (const value of Object.values(azureOptions)) {
195
+ if (typeof value === 'string' && envVarRegex.test(value)) {
196
+ throw new Error(`Azure configuration environment variable "${value}" was not found.`);
197
+ }
198
+ }
199
+
200
+ const result: MappedAzureConfig = { azureOptions };
201
+
202
+ if (groupConfig.baseURL) {
203
+ result.baseURL = extractEnvVariable(groupConfig.baseURL);
204
+ }
205
+
206
+ if (groupConfig.additionalHeaders) {
207
+ result.headers = groupConfig.additionalHeaders;
208
+ }
209
+
210
+ return result;
211
+ }
package/src/config.ts CHANGED
@@ -8,6 +8,55 @@ export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'd
8
8
 
9
9
  export const fileSourceSchema = z.nativeEnum(FileSources);
10
10
 
11
+ export const modelConfigSchema = z
12
+ .object({
13
+ deploymentName: z.string().optional(),
14
+ version: z.string().optional(),
15
+ })
16
+ .or(z.boolean());
17
+
18
+ export type TAzureModelConfig = z.infer<typeof modelConfigSchema>;
19
+
20
+ export const azureBaseSchema = z.object({
21
+ apiKey: z.string(),
22
+ instanceName: z.string(),
23
+ deploymentName: z.string().optional(),
24
+ version: z.string().optional(),
25
+ baseURL: z.string().optional(),
26
+ additionalHeaders: z.record(z.any()).optional(),
27
+ });
28
+
29
+ export type TAzureBaseSchema = z.infer<typeof azureBaseSchema>;
30
+
31
+ export const azureGroupSchema = z
32
+ .object({
33
+ group: z.string(),
34
+ models: z.record(z.string(), modelConfigSchema),
35
+ })
36
+ .required()
37
+ .and(azureBaseSchema);
38
+
39
+ export const azureGroupConfigsSchema = z.array(azureGroupSchema).min(1);
40
+ export type TAzureGroups = z.infer<typeof azureGroupConfigsSchema>;
41
+
42
+ export type TAzureModelMapSchema = {
43
+ // deploymentName?: string;
44
+ // version?: string;
45
+ group: string;
46
+ };
47
+
48
+ export type TAzureModelGroupMap = Record<string, TAzureModelMapSchema>;
49
+ export type TAzureGroupMap = Record<
50
+ string,
51
+ TAzureBaseSchema & { models: Record<string, TAzureModelConfig> }
52
+ >;
53
+
54
+ export type TValidatedAzureConfig = {
55
+ modelNames: string[];
56
+ modelGroupMap: TAzureModelGroupMap;
57
+ groupMap: TAzureGroupMap;
58
+ };
59
+
11
60
  export const assistantEndpointSchema = z.object({
12
61
  /* assistants specific */
13
62
  disableBuilder: z.boolean().optional(),
@@ -56,8 +105,30 @@ export const endpointSchema = z.object({
56
105
  headers: z.record(z.any()).optional(),
57
106
  addParams: z.record(z.any()).optional(),
58
107
  dropParams: z.array(z.string()).optional(),
108
+ customOrder: z.number().optional(),
59
109
  });
60
110
 
111
+ export const azureEndpointSchema = z
112
+ .object({
113
+ groups: azureGroupConfigsSchema,
114
+ plugins: z.boolean().optional(),
115
+ })
116
+ .and(
117
+ endpointSchema
118
+ .pick({
119
+ titleConvo: true,
120
+ titleMethod: true,
121
+ titleModel: true,
122
+ summarize: true,
123
+ summaryModel: true,
124
+ customOrder: true,
125
+ })
126
+ .partial(),
127
+ );
128
+
129
+ export type TAzureConfig = Omit<z.infer<typeof azureEndpointSchema>, 'groups'> &
130
+ TValidatedAzureConfig;
131
+
61
132
  export const rateLimitSchema = z.object({
62
133
  fileUploads: z
63
134
  .object({
@@ -83,6 +154,7 @@ export const configSchema = z.object({
83
154
  fileConfig: fileConfigSchema.optional(),
84
155
  endpoints: z
85
156
  .object({
157
+ [EModelEndpoint.azureOpenAI]: azureEndpointSchema.optional(),
86
158
  [EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
87
159
  custom: z.array(endpointSchema.partial()).optional(),
88
160
  })
@@ -284,10 +356,20 @@ export enum CacheKeys {
284
356
  * Key for the override config cache.
285
357
  */
286
358
  OVERRIDE_CONFIG = 'overrideConfig',
359
+ }
360
+
361
+ /**
362
+ * Enum for violation types, used to identify, log, and cache violations.
363
+ */
364
+ export enum ViolationTypes {
287
365
  /**
288
- * Key for accessing File Upload Violations (exceeding limit).
366
+ * File Upload Violations (exceeding limit).
289
367
  */
290
368
  FILE_UPLOAD_LIMIT = 'file_upload_limit',
369
+ /**
370
+ * Illegal Model Request (not available).
371
+ */
372
+ ILLEGAL_MODEL_REQUEST = 'illegal_model_request',
291
373
  }
292
374
 
293
375
  /**
@@ -361,7 +443,7 @@ export enum Constants {
361
443
  /**
362
444
  * Key for the Custom Config's version (librechat.yaml).
363
445
  */
364
- CONFIG_VERSION = '1.0.3',
446
+ CONFIG_VERSION = '1.0.4',
365
447
  /**
366
448
  * Standard value for the first message's `parentMessageId` value, to indicate no parent exists.
367
449
  */
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /* config */
2
+ export * from './azure';
2
3
  export * from './config';
3
4
  export * from './file-config';
4
5
  /* schema helpers */
package/src/parsers.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import type { ZodIssue } from 'zod';
1
2
  import type { TConversation, TPreset } from './schemas';
2
- import type { TEndpointOption } from './types';
3
+ import type { TConfig, TEndpointOption, TEndpointsConfig } from './types';
3
4
  import {
4
5
  EModelEndpoint,
5
6
  openAISchema,
@@ -42,6 +43,101 @@ const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
42
43
  // [EModelEndpoint.google]: createGoogleSchema,
43
44
  // };
44
45
 
46
+ /** Get the enabled endpoints from the `ENDPOINTS` environment variable */
47
+ export function getEnabledEndpoints() {
48
+ const defaultEndpoints: string[] = [
49
+ EModelEndpoint.openAI,
50
+ EModelEndpoint.assistants,
51
+ EModelEndpoint.azureOpenAI,
52
+ EModelEndpoint.google,
53
+ EModelEndpoint.bingAI,
54
+ EModelEndpoint.chatGPTBrowser,
55
+ EModelEndpoint.gptPlugins,
56
+ EModelEndpoint.anthropic,
57
+ ];
58
+
59
+ const endpointsEnv = process.env.ENDPOINTS || '';
60
+ let enabledEndpoints = defaultEndpoints;
61
+ if (endpointsEnv) {
62
+ enabledEndpoints = endpointsEnv
63
+ .split(',')
64
+ .filter((endpoint) => endpoint?.trim())
65
+ .map((endpoint) => endpoint.trim());
66
+ }
67
+ return enabledEndpoints;
68
+ }
69
+
70
+ /** Orders an existing EndpointsConfig object based on enabled endpoint/custom ordering */
71
+ export function orderEndpointsConfig(endpointsConfig: TEndpointsConfig) {
72
+ if (!endpointsConfig) {
73
+ return {};
74
+ }
75
+ const enabledEndpoints = getEnabledEndpoints();
76
+ const endpointKeys = Object.keys(endpointsConfig);
77
+ const defaultCustomIndex = enabledEndpoints.indexOf(EModelEndpoint.custom);
78
+ return endpointKeys.reduce(
79
+ (accumulatedConfig: Record<string, TConfig | null | undefined>, currentEndpointKey) => {
80
+ const isCustom = !(currentEndpointKey in EModelEndpoint);
81
+ const isEnabled = enabledEndpoints.includes(currentEndpointKey);
82
+ if (!isEnabled && !isCustom) {
83
+ return accumulatedConfig;
84
+ }
85
+
86
+ const index = enabledEndpoints.indexOf(currentEndpointKey);
87
+
88
+ if (isCustom) {
89
+ accumulatedConfig[currentEndpointKey] = {
90
+ order: defaultCustomIndex >= 0 ? defaultCustomIndex : 9999,
91
+ ...(endpointsConfig[currentEndpointKey] as Omit<TConfig, 'order'> & { order?: number }),
92
+ };
93
+ } else if (endpointsConfig[currentEndpointKey]) {
94
+ accumulatedConfig[currentEndpointKey] = {
95
+ ...endpointsConfig[currentEndpointKey],
96
+ order: index,
97
+ };
98
+ }
99
+ return accumulatedConfig;
100
+ },
101
+ {},
102
+ );
103
+ }
104
+
105
+ /** Converts an array of Zod issues into a string. */
106
+ export function errorsToString(errors: ZodIssue[]) {
107
+ return errors
108
+ .map((error) => {
109
+ const field = error.path.join('.');
110
+ const message = error.message;
111
+
112
+ return `${field}: ${message}`;
113
+ })
114
+ .join(' ');
115
+ }
116
+
117
+ export const envVarRegex = /^\${(.+)}$/;
118
+
119
+ /** Extracts the value of an environment variable from a string. */
120
+ export function extractEnvVariable(value: string) {
121
+ const envVarMatch = value.match(envVarRegex);
122
+ if (envVarMatch) {
123
+ return process.env[envVarMatch[1]] || value;
124
+ }
125
+ return value;
126
+ }
127
+
128
+ /** Resolves header values to env variables if detected */
129
+ export function resolveHeaders(headers: Record<string, string> | undefined) {
130
+ const resolvedHeaders = { ...(headers ?? {}) };
131
+
132
+ if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
133
+ Object.keys(headers).forEach((key) => {
134
+ resolvedHeaders[key] = extractEnvVariable(headers[key]);
135
+ });
136
+ }
137
+
138
+ return resolvedHeaders;
139
+ }
140
+
45
141
  export function getFirstDefinedValue(possibleValues: string[]) {
46
142
  let returnValue;
47
143
  for (const value of possibleValues) {
@@ -117,8 +117,8 @@ export const useUpdateUserKeysMutation = (): UseMutationResult<
117
117
  > => {
118
118
  const queryClient = useQueryClient();
119
119
  return useMutation((payload: t.TUpdateUserKeyRequest) => dataService.updateUserKey(payload), {
120
- onSuccess: () => {
121
- queryClient.invalidateQueries([QueryKeys.name]);
120
+ onSuccess: (data, variables) => {
121
+ queryClient.invalidateQueries([QueryKeys.name, variables.name]);
122
122
  },
123
123
  });
124
124
  };
@@ -136,7 +136,7 @@ export const useRevokeUserKeyMutation = (name: string): UseMutationResult<unknow
136
136
  const queryClient = useQueryClient();
137
137
  return useMutation(() => dataService.revokeUserKey(name), {
138
138
  onSuccess: () => {
139
- queryClient.invalidateQueries([QueryKeys.name]);
139
+ queryClient.invalidateQueries([QueryKeys.name, name]);
140
140
  if (name === s.EModelEndpoint.assistants) {
141
141
  queryClient.invalidateQueries([QueryKeys.assistants, defaultOrderQuery]);
142
142
  queryClient.invalidateQueries([QueryKeys.assistantDocs]);
package/src/schemas.ts CHANGED
@@ -506,9 +506,6 @@ export const compactOpenAISchema = tConversationSchema
506
506
  })
507
507
  .transform((obj: Partial<TConversation>) => {
508
508
  const newObj: Partial<TConversation> = { ...obj };
509
- if (newObj.model === 'gpt-3.5-turbo') {
510
- delete newObj.model;
511
- }
512
509
  if (newObj.temperature === 1) {
513
510
  delete newObj.temperature;
514
511
  }
@@ -545,9 +542,6 @@ export const compactGoogleSchema = tConversationSchema
545
542
  })
546
543
  .transform((obj) => {
547
544
  const newObj: Partial<TConversation> = { ...obj };
548
- if (newObj.model === google.model.default) {
549
- delete newObj.model;
550
- }
551
545
  if (newObj.temperature === google.temperature.default) {
552
546
  delete newObj.temperature;
553
547
  }
@@ -577,9 +571,6 @@ export const compactAnthropicSchema = tConversationSchema
577
571
  })
578
572
  .transform((obj) => {
579
573
  const newObj: Partial<TConversation> = { ...obj };
580
- if (newObj.model === 'claude-1') {
581
- delete newObj.model;
582
- }
583
574
  if (newObj.temperature === 1) {
584
575
  delete newObj.temperature;
585
576
  }
@@ -603,11 +594,6 @@ export const compactChatGPTSchema = tConversationSchema
603
594
  })
604
595
  .transform((obj) => {
605
596
  const newObj: Partial<TConversation> = { ...obj };
606
- // model: obj.model ?? 'text-davinci-002-render-sha',
607
- if (newObj.model === 'text-davinci-002-render-sha') {
608
- delete newObj.model;
609
- }
610
-
611
597
  return removeNullishValues(newObj);
612
598
  })
613
599
  .catch(() => ({}));
@@ -626,9 +612,6 @@ export const compactPluginsSchema = tConversationSchema
626
612
  })
627
613
  .transform((obj) => {
628
614
  const newObj: Partial<TConversation> = { ...obj };
629
- if (newObj.model === 'gpt-3.5-turbo') {
630
- delete newObj.model;
631
- }
632
615
  if (newObj.chatGptLabel === null) {
633
616
  delete newObj.chatGptLabel;
634
617
  }