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.
Files changed (109) hide show
  1. package/dist/index.es.js +1 -1
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/react-query/index.es.js +1 -1
  6. package/dist/react-query/index.es.js.map +1 -1
  7. package/dist/types/accessPermissions.d.ts +744 -0
  8. package/dist/types/actions.d.ts +118 -0
  9. package/dist/types/api-endpoints.d.ts +150 -0
  10. package/dist/types/artifacts.d.ts +97 -0
  11. package/dist/types/azure.d.ts +22 -0
  12. package/dist/types/bedrock.d.ts +1220 -0
  13. package/dist/types/config.d.ts +14849 -0
  14. package/dist/types/config.spec.d.ts +1 -0
  15. package/dist/types/createPayload.d.ts +5 -0
  16. package/dist/types/data-service.d.ts +287 -0
  17. package/dist/types/feedback.d.ts +36 -0
  18. package/dist/types/file-config.d.ts +263 -0
  19. package/dist/types/file-config.spec.d.ts +1 -0
  20. package/dist/types/generate.d.ts +597 -0
  21. package/dist/types/headers-helpers.d.ts +2 -0
  22. package/{src/index.ts → dist/types/index.d.ts} +0 -15
  23. package/dist/types/keys.d.ts +92 -0
  24. package/dist/types/mcp.d.ts +2760 -0
  25. package/dist/types/messages.d.ts +10 -0
  26. package/dist/types/models.d.ts +1547 -0
  27. package/dist/types/parameterSettings.d.ts +69 -0
  28. package/dist/types/parsers.d.ts +110 -0
  29. package/dist/types/permissions.d.ts +522 -0
  30. package/dist/types/react-query/react-query-service.d.ts +85 -0
  31. package/dist/types/request.d.ts +25 -0
  32. package/dist/types/roles.d.ts +554 -0
  33. package/dist/types/roles.spec.d.ts +1 -0
  34. package/dist/types/schemas.d.ts +5110 -0
  35. package/dist/types/schemas.spec.d.ts +1 -0
  36. package/dist/types/types/agents.d.ts +433 -0
  37. package/dist/types/types/assistants.d.ts +547 -0
  38. package/dist/types/types/files.d.ts +172 -0
  39. package/dist/types/types/graph.d.ts +135 -0
  40. package/{src/types/mcpServers.ts → dist/types/types/mcpServers.d.ts} +12 -18
  41. package/dist/types/types/mutations.d.ts +209 -0
  42. package/dist/types/types/queries.d.ts +169 -0
  43. package/dist/types/types/runs.d.ts +36 -0
  44. package/dist/types/types/web.d.ts +520 -0
  45. package/dist/types/types.d.ts +503 -0
  46. package/dist/types/utils.d.ts +12 -0
  47. package/package.json +5 -1
  48. package/babel.config.js +0 -4
  49. package/check_updates.sh +0 -52
  50. package/jest.config.js +0 -19
  51. package/react-query/package-lock.json +0 -292
  52. package/react-query/package.json +0 -10
  53. package/rollup.config.js +0 -74
  54. package/server-rollup.config.js +0 -40
  55. package/specs/actions.spec.ts +0 -2533
  56. package/specs/api-endpoints-subdir.spec.ts +0 -140
  57. package/specs/api-endpoints.spec.ts +0 -74
  58. package/specs/azure.spec.ts +0 -844
  59. package/specs/bedrock.spec.ts +0 -862
  60. package/specs/filetypes.spec.ts +0 -175
  61. package/specs/generate.spec.ts +0 -770
  62. package/specs/headers-helpers.spec.ts +0 -24
  63. package/specs/mcp.spec.ts +0 -147
  64. package/specs/openapiSpecs.ts +0 -524
  65. package/specs/parsers.spec.ts +0 -601
  66. package/specs/request-interceptor.spec.ts +0 -304
  67. package/specs/utils.spec.ts +0 -196
  68. package/src/accessPermissions.ts +0 -346
  69. package/src/actions.ts +0 -813
  70. package/src/api-endpoints.ts +0 -440
  71. package/src/artifacts.ts +0 -3104
  72. package/src/azure.ts +0 -328
  73. package/src/bedrock.ts +0 -425
  74. package/src/config.spec.ts +0 -315
  75. package/src/config.ts +0 -2006
  76. package/src/createPayload.ts +0 -46
  77. package/src/data-service.ts +0 -1087
  78. package/src/feedback.ts +0 -141
  79. package/src/file-config.spec.ts +0 -1248
  80. package/src/file-config.ts +0 -764
  81. package/src/generate.ts +0 -634
  82. package/src/headers-helpers.ts +0 -13
  83. package/src/keys.ts +0 -99
  84. package/src/mcp.ts +0 -271
  85. package/src/messages.ts +0 -50
  86. package/src/models.ts +0 -69
  87. package/src/parameterSettings.ts +0 -1111
  88. package/src/parsers.ts +0 -563
  89. package/src/permissions.ts +0 -188
  90. package/src/react-query/react-query-service.ts +0 -566
  91. package/src/request.ts +0 -171
  92. package/src/roles.spec.ts +0 -132
  93. package/src/roles.ts +0 -225
  94. package/src/schemas.spec.ts +0 -355
  95. package/src/schemas.ts +0 -1234
  96. package/src/types/agents.ts +0 -470
  97. package/src/types/assistants.ts +0 -654
  98. package/src/types/files.ts +0 -191
  99. package/src/types/graph.ts +0 -145
  100. package/src/types/mutations.ts +0 -422
  101. package/src/types/queries.ts +0 -208
  102. package/src/types/runs.ts +0 -40
  103. package/src/types/web.ts +0 -588
  104. package/src/types.ts +0 -676
  105. package/src/utils.ts +0 -85
  106. package/tsconfig.json +0 -28
  107. package/tsconfig.spec.json +0 -10
  108. /package/{src/react-query/index.ts → dist/types/react-query/index.d.ts} +0 -0
  109. /package/{src/types/index.ts → dist/types/types/index.d.ts} +0 -0
@@ -1,1248 +0,0 @@
1
- import type { FileConfig } from './types/files';
2
- import {
3
- fileConfig as baseFileConfig,
4
- documentParserMimeTypes,
5
- getEndpointFileConfig,
6
- applicationMimeTypes,
7
- defaultOCRMimeTypes,
8
- supportedMimeTypes,
9
- mergeFileConfig,
10
- inferMimeType,
11
- textMimeTypes,
12
- } from './file-config';
13
- import { EModelEndpoint } from './schemas';
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
- expect(inferMimeType('Main.java', '')).toBe('text/x-java');
35
- });
36
-
37
- it('should return empty string for unknown extension with no browser type', () => {
38
- expect(inferMimeType('file.xyz', '')).toBe('');
39
- });
40
-
41
- it('should produce a type accepted by checkType after normalizing text/x-python-script', () => {
42
- const normalized = inferMimeType('test.py', 'text/x-python-script');
43
- expect(baseFileConfig.checkType(normalized)).toBe(true);
44
- });
45
-
46
- it('should reject raw text/x-python-script without normalization', () => {
47
- expect(baseFileConfig.checkType('text/x-python-script')).toBe(false);
48
- });
49
- });
50
-
51
- describe('applicationMimeTypes', () => {
52
- const odfTypes = [
53
- 'application/vnd.oasis.opendocument.text',
54
- 'application/vnd.oasis.opendocument.spreadsheet',
55
- 'application/vnd.oasis.opendocument.presentation',
56
- 'application/vnd.oasis.opendocument.graphics',
57
- ];
58
-
59
- it.each(odfTypes)('matches ODF type: %s', (mimeType) => {
60
- expect(applicationMimeTypes.test(mimeType)).toBe(true);
61
- });
62
-
63
- const existingTypes = [
64
- 'application/pdf',
65
- 'application/json',
66
- 'application/csv',
67
- 'application/msword',
68
- 'application/xml',
69
- 'application/zip',
70
- 'application/epub+zip',
71
- 'application/x-tar',
72
- 'application/x-sh',
73
- 'application/typescript',
74
- 'application/sql',
75
- 'application/yaml',
76
- 'application/x-parquet',
77
- 'application/vnd.apache.parquet',
78
- 'application/vnd.coffeescript',
79
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
80
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
81
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
82
- ];
83
-
84
- it.each(existingTypes)('matches existing type: %s', (mimeType) => {
85
- expect(applicationMimeTypes.test(mimeType)).toBe(true);
86
- });
87
-
88
- const invalidTypes = [
89
- 'application/vnd.oasis.opendocument.text-template',
90
- 'application/vnd.oasis.opendocument.texts',
91
- 'application/vnd.oasis.opendocument.chart',
92
- 'application/vnd.oasis.opendocument.formula',
93
- 'application/vnd.oasis.opendocument.image',
94
- 'application/vnd.oasis.opendocument.text-master',
95
- 'text/plain',
96
- 'image/png',
97
- ];
98
-
99
- it.each(invalidTypes)('does not match invalid type: %s', (mimeType) => {
100
- expect(applicationMimeTypes.test(mimeType)).toBe(false);
101
- });
102
- });
103
-
104
- describe('defaultOCRMimeTypes', () => {
105
- const checkOCRType = (mimeType: string): boolean =>
106
- defaultOCRMimeTypes.some((regex) => regex.test(mimeType));
107
-
108
- it.each([
109
- 'application/vnd.oasis.opendocument.text',
110
- 'application/vnd.oasis.opendocument.spreadsheet',
111
- 'application/vnd.oasis.opendocument.presentation',
112
- 'application/vnd.oasis.opendocument.graphics',
113
- ])('matches ODF type for OCR: %s', (mimeType) => {
114
- expect(checkOCRType(mimeType)).toBe(true);
115
- });
116
- });
117
-
118
- describe('supportedMimeTypes', () => {
119
- const checkSupported = (mimeType: string): boolean =>
120
- supportedMimeTypes.some((regex) => regex.test(mimeType));
121
-
122
- it.each([
123
- 'application/vnd.oasis.opendocument.text',
124
- 'application/vnd.oasis.opendocument.spreadsheet',
125
- 'application/vnd.oasis.opendocument.presentation',
126
- 'application/vnd.oasis.opendocument.graphics',
127
- ])('ODF type flows through supportedMimeTypes: %s', (mimeType) => {
128
- expect(checkSupported(mimeType)).toBe(true);
129
- });
130
- });
131
-
132
- describe('documentParserMimeTypes', () => {
133
- const check = (mimeType: string): boolean =>
134
- documentParserMimeTypes.some((regex) => regex.test(mimeType));
135
-
136
- it.each([
137
- 'application/pdf',
138
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
139
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
140
- 'application/vnd.ms-excel',
141
- 'application/msexcel',
142
- 'application/x-msexcel',
143
- 'application/x-ms-excel',
144
- 'application/vnd.oasis.opendocument.spreadsheet',
145
- 'application/vnd.oasis.opendocument.text',
146
- ])('matches natively parseable type: %s', (mimeType) => {
147
- expect(check(mimeType)).toBe(true);
148
- });
149
-
150
- it.each([
151
- 'application/vnd.oasis.opendocument.presentation',
152
- 'application/vnd.oasis.opendocument.graphics',
153
- 'text/plain',
154
- 'image/png',
155
- ])('does not match OCR-only or unsupported type: %s', (mimeType) => {
156
- expect(check(mimeType)).toBe(false);
157
- });
158
- });
159
-
160
- describe('getEndpointFileConfig', () => {
161
- describe('custom endpoint lookup', () => {
162
- it('should find custom endpoint by direct lookup', () => {
163
- const fileConfig: FileConfig = {
164
- ...baseFileConfig,
165
- endpoints: {
166
- ...baseFileConfig.endpoints,
167
- ollama: {
168
- disabled: true,
169
- fileLimit: 5,
170
- },
171
- },
172
- };
173
-
174
- const result = getEndpointFileConfig({
175
- fileConfig,
176
- endpoint: 'ollama',
177
- endpointType: EModelEndpoint.custom,
178
- });
179
-
180
- expect(result.disabled).toBe(true);
181
- expect(result.fileLimit).toBe(5);
182
- });
183
-
184
- it('should find custom endpoint by normalized lookup', () => {
185
- const fileConfig: FileConfig = {
186
- ...baseFileConfig,
187
- endpoints: {
188
- ...baseFileConfig.endpoints,
189
- ollama: {
190
- disabled: true,
191
- fileLimit: 7,
192
- },
193
- },
194
- };
195
-
196
- /** Test with non-normalized name */
197
- const result = getEndpointFileConfig({
198
- fileConfig,
199
- endpoint: 'Ollama',
200
- endpointType: EModelEndpoint.custom,
201
- });
202
-
203
- expect(result.disabled).toBe(true);
204
- expect(result.fileLimit).toBe(7);
205
- });
206
-
207
- it('should fallback to generic custom config when specific endpoint not found', () => {
208
- const fileConfig: FileConfig = {
209
- ...baseFileConfig,
210
- endpoints: {
211
- ...baseFileConfig.endpoints,
212
- [EModelEndpoint.custom]: {
213
- disabled: false,
214
- fileLimit: 3,
215
- },
216
- },
217
- };
218
-
219
- const result = getEndpointFileConfig({
220
- fileConfig,
221
- endpoint: 'unknownCustomEndpoint',
222
- endpointType: EModelEndpoint.custom,
223
- });
224
-
225
- expect(result.disabled).toBe(false);
226
- expect(result.fileLimit).toBe(3);
227
- });
228
-
229
- it('should fallback to agents config when custom and specific endpoint not found', () => {
230
- const fileConfig: FileConfig = {
231
- ...baseFileConfig,
232
- endpoints: {
233
- ...baseFileConfig.endpoints,
234
- [EModelEndpoint.agents]: {
235
- disabled: false,
236
- fileLimit: 8,
237
- },
238
- },
239
- };
240
-
241
- const result = getEndpointFileConfig({
242
- fileConfig,
243
- endpoint: 'unknownCustomEndpoint',
244
- endpointType: EModelEndpoint.custom,
245
- });
246
-
247
- expect(result.disabled).toBe(false);
248
- expect(result.fileLimit).toBe(8);
249
- });
250
-
251
- it('should use base agents config when only default is dynamically configured', () => {
252
- const dynamicConfig = {
253
- endpoints: {
254
- default: {
255
- disabled: false,
256
- fileLimit: 12,
257
- },
258
- },
259
- };
260
-
261
- const merged = mergeFileConfig(dynamicConfig);
262
- const result = getEndpointFileConfig({
263
- fileConfig: merged,
264
- endpoint: 'unknownCustomEndpoint',
265
- endpointType: EModelEndpoint.custom,
266
- });
267
-
268
- /**
269
- * Should use base agents config (fileLimit: 10) since it exists in baseFileConfig
270
- * and custom endpoints fall back to agents
271
- */
272
- expect(result.disabled).toBe(false);
273
- expect(result.fileLimit).toBe(10); /** From baseFileConfig.endpoints.agents */
274
- });
275
-
276
- it('should prioritize specific custom endpoint over generic custom config', () => {
277
- const fileConfig: FileConfig = {
278
- ...baseFileConfig,
279
- endpoints: {
280
- ...baseFileConfig.endpoints,
281
- [EModelEndpoint.custom]: {
282
- disabled: false,
283
- fileLimit: 20,
284
- },
285
- ollama: {
286
- disabled: true,
287
- fileLimit: 3,
288
- },
289
- },
290
- };
291
-
292
- const result = getEndpointFileConfig({
293
- fileConfig,
294
- endpoint: 'ollama',
295
- endpointType: EModelEndpoint.custom,
296
- });
297
-
298
- /** Should use ollama config, not generic custom */
299
- expect(result.disabled).toBe(true);
300
- expect(result.fileLimit).toBe(3);
301
- });
302
-
303
- it('should skip standard endpoint keys in normalized lookup', () => {
304
- const fileConfig: FileConfig = {
305
- ...baseFileConfig,
306
- endpoints: {
307
- ...baseFileConfig.endpoints,
308
- default: {
309
- disabled: false,
310
- fileLimit: 99,
311
- },
312
- },
313
- };
314
-
315
- /** "default" should not match via normalized lookup for custom endpoints */
316
- const result = getEndpointFileConfig({
317
- fileConfig,
318
- endpoint: 'default',
319
- endpointType: EModelEndpoint.custom,
320
- });
321
-
322
- /** Should not use direct lookup, should fall back to default */
323
- expect(result.fileLimit).toBe(99);
324
- });
325
-
326
- it('should handle complete fallback chain: specific -> custom -> agents -> default', () => {
327
- const customConfig: FileConfig = {
328
- ...baseFileConfig,
329
- endpoints: {
330
- ...baseFileConfig.endpoints,
331
- myOllama: {
332
- disabled: true,
333
- fileLimit: 1,
334
- },
335
- [EModelEndpoint.custom]: {
336
- disabled: false,
337
- fileLimit: 2,
338
- },
339
- [EModelEndpoint.agents]: {
340
- disabled: false,
341
- fileLimit: 3,
342
- },
343
- default: {
344
- disabled: false,
345
- fileLimit: 4,
346
- },
347
- },
348
- };
349
-
350
- /** 1. Should find specific config */
351
- const specific = getEndpointFileConfig({
352
- fileConfig: customConfig,
353
- endpoint: 'myOllama',
354
- endpointType: EModelEndpoint.custom,
355
- });
356
- expect(specific.fileLimit).toBe(1);
357
-
358
- /** 2. Should fallback to custom when specific not found */
359
- const customOnlyConfig: FileConfig = {
360
- ...baseFileConfig,
361
- endpoints: {
362
- ...baseFileConfig.endpoints,
363
- [EModelEndpoint.custom]: {
364
- disabled: false,
365
- fileLimit: 2,
366
- },
367
- [EModelEndpoint.agents]: {
368
- disabled: false,
369
- fileLimit: 3,
370
- },
371
- default: {
372
- disabled: false,
373
- fileLimit: 4,
374
- },
375
- },
376
- };
377
- const customFallback = getEndpointFileConfig({
378
- fileConfig: customOnlyConfig,
379
- endpoint: 'unknownCustom',
380
- endpointType: EModelEndpoint.custom,
381
- });
382
- expect(customFallback.fileLimit).toBe(2);
383
-
384
- /** 3. Should fallback to agents */
385
- const agentsFallback = getEndpointFileConfig({
386
- fileConfig: {
387
- ...baseFileConfig,
388
- endpoints: {
389
- ...baseFileConfig.endpoints,
390
- [EModelEndpoint.agents]: {
391
- disabled: false,
392
- fileLimit: 3,
393
- },
394
- default: {
395
- disabled: false,
396
- fileLimit: 4,
397
- },
398
- },
399
- },
400
- endpoint: 'unknownCustom',
401
- endpointType: EModelEndpoint.custom,
402
- });
403
- expect(agentsFallback.fileLimit).toBe(3);
404
-
405
- /**
406
- * 4. Should use agents even if disabled (caller decides based on disabled flag)
407
- * getEndpointFileConfig returns the config, doesn't filter based on disabled
408
- */
409
- const agentsDisabledConfig = mergeFileConfig({
410
- endpoints: {
411
- [EModelEndpoint.agents]: {
412
- disabled: true,
413
- },
414
- default: {
415
- disabled: false,
416
- fileLimit: 4,
417
- },
418
- },
419
- });
420
- const agentsDisabled = getEndpointFileConfig({
421
- fileConfig: agentsDisabledConfig,
422
- endpoint: 'unknownCustom',
423
- endpointType: EModelEndpoint.custom,
424
- });
425
- /** Should return agents config (disabled: true), not skip to default */
426
- expect(agentsDisabled.disabled).toBe(true);
427
- expect(agentsDisabled.fileLimit).toBe(0); /** disabled: true sets fileLimit to 0 */
428
- });
429
- });
430
-
431
- describe('standard endpoint lookup', () => {
432
- it('should find endpoint by endpointType', () => {
433
- const fileConfig: FileConfig = {
434
- ...baseFileConfig,
435
- endpoints: {
436
- ...baseFileConfig.endpoints,
437
- [EModelEndpoint.openAI]: {
438
- disabled: true,
439
- fileLimit: 15,
440
- },
441
- },
442
- };
443
-
444
- const result = getEndpointFileConfig({
445
- fileConfig,
446
- endpoint: 'someOtherName',
447
- endpointType: EModelEndpoint.openAI,
448
- });
449
-
450
- expect(result.disabled).toBe(true);
451
- expect(result.fileLimit).toBe(15);
452
- });
453
-
454
- it('should find endpoint by direct endpoint name', () => {
455
- const fileConfig: FileConfig = {
456
- ...baseFileConfig,
457
- endpoints: {
458
- ...baseFileConfig.endpoints,
459
- [EModelEndpoint.anthropic]: {
460
- disabled: false,
461
- fileLimit: 25,
462
- },
463
- },
464
- };
465
-
466
- const result = getEndpointFileConfig({
467
- fileConfig,
468
- endpoint: EModelEndpoint.anthropic,
469
- });
470
-
471
- expect(result.disabled).toBe(false);
472
- expect(result.fileLimit).toBe(25);
473
- });
474
-
475
- it('should find endpoint by normalized name', () => {
476
- const fileConfig: FileConfig = {
477
- ...baseFileConfig,
478
- endpoints: {
479
- ...baseFileConfig.endpoints,
480
- ollama: {
481
- disabled: true,
482
- fileLimit: 6,
483
- },
484
- },
485
- };
486
-
487
- /** Test normalization (Ollama -> ollama) */
488
- const result = getEndpointFileConfig({
489
- fileConfig,
490
- endpoint: 'Ollama',
491
- });
492
-
493
- expect(result.disabled).toBe(true);
494
- expect(result.fileLimit).toBe(6);
495
- });
496
-
497
- it('should use agents fallback for explicitly agents endpoint', () => {
498
- const fileConfig: FileConfig = {
499
- ...baseFileConfig,
500
- endpoints: {
501
- ...baseFileConfig.endpoints,
502
- [EModelEndpoint.agents]: {
503
- disabled: false,
504
- fileLimit: 11,
505
- },
506
- },
507
- };
508
-
509
- const result = getEndpointFileConfig({
510
- fileConfig,
511
- endpoint: EModelEndpoint.agents,
512
- });
513
-
514
- expect(result.disabled).toBe(false);
515
- expect(result.fileLimit).toBe(11);
516
- });
517
-
518
- it('should use agents fallback for unconfigured non-standard endpoint', () => {
519
- const fileConfig: FileConfig = {
520
- ...baseFileConfig,
521
- endpoints: {
522
- ...baseFileConfig.endpoints,
523
- [EModelEndpoint.agents]: {
524
- disabled: false,
525
- fileLimit: 10,
526
- },
527
- default: {
528
- disabled: false,
529
- fileLimit: 100,
530
- },
531
- },
532
- };
533
-
534
- const result = getEndpointFileConfig({
535
- fileConfig,
536
- endpoint: 'unconfiguredEndpoint',
537
- });
538
-
539
- /**
540
- * With new logic, unconfigured endpoints are treated as custom
541
- * and fall back through: specific -> custom -> agents -> default
542
- * So this should use agents (fileLimit: 10), not default
543
- */
544
- expect(result.disabled).toBe(false);
545
- expect(result.fileLimit).toBe(10);
546
- });
547
-
548
- it('should prioritize endpointType over endpoint name', () => {
549
- const fileConfig: FileConfig = {
550
- ...baseFileConfig,
551
- endpoints: {
552
- ...baseFileConfig.endpoints,
553
- [EModelEndpoint.openAI]: {
554
- disabled: true,
555
- fileLimit: 5,
556
- },
557
- [EModelEndpoint.anthropic]: {
558
- disabled: false,
559
- fileLimit: 10,
560
- },
561
- },
562
- };
563
-
564
- /** endpointType should take priority */
565
- const result = getEndpointFileConfig({
566
- fileConfig,
567
- endpoint: EModelEndpoint.openAI,
568
- endpointType: EModelEndpoint.anthropic,
569
- });
570
-
571
- expect(result.disabled).toBe(false);
572
- expect(result.fileLimit).toBe(10);
573
- });
574
- });
575
-
576
- describe('edge cases', () => {
577
- it('should return default when fileConfig is null', () => {
578
- const result = getEndpointFileConfig({
579
- fileConfig: null,
580
- endpoint: EModelEndpoint.openAI,
581
- });
582
-
583
- expect(result).toBeDefined();
584
- expect(result.disabled).toBe(false);
585
- });
586
-
587
- it('should return default when fileConfig is undefined', () => {
588
- const result = getEndpointFileConfig({
589
- fileConfig: undefined,
590
- endpoint: EModelEndpoint.openAI,
591
- });
592
-
593
- expect(result).toBeDefined();
594
- expect(result.disabled).toBe(false);
595
- });
596
-
597
- it('should handle empty endpoint gracefully', () => {
598
- const fileConfig: FileConfig = {
599
- ...baseFileConfig,
600
- endpoints: {
601
- ...baseFileConfig.endpoints,
602
- default: {
603
- disabled: false,
604
- fileLimit: 50,
605
- },
606
- },
607
- };
608
-
609
- const result = getEndpointFileConfig({
610
- fileConfig,
611
- endpoint: '',
612
- });
613
-
614
- expect(result.disabled).toBe(false);
615
- expect(result.fileLimit).toBe(50);
616
- });
617
-
618
- it('should handle null endpoint gracefully', () => {
619
- const fileConfig: FileConfig = {
620
- ...baseFileConfig,
621
- endpoints: {
622
- ...baseFileConfig.endpoints,
623
- default: {
624
- disabled: false,
625
- fileLimit: 50,
626
- },
627
- },
628
- };
629
-
630
- const result = getEndpointFileConfig({
631
- fileConfig,
632
- endpoint: null,
633
- });
634
-
635
- expect(result.disabled).toBe(false);
636
- expect(result.fileLimit).toBe(50);
637
- });
638
-
639
- it('should handle undefined endpoint gracefully', () => {
640
- const fileConfig: FileConfig = {
641
- ...baseFileConfig,
642
- endpoints: {
643
- ...baseFileConfig.endpoints,
644
- default: {
645
- disabled: false,
646
- fileLimit: 50,
647
- },
648
- },
649
- };
650
-
651
- const result = getEndpointFileConfig({
652
- fileConfig,
653
- endpoint: undefined,
654
- });
655
-
656
- expect(result.disabled).toBe(false);
657
- expect(result.fileLimit).toBe(50);
658
- });
659
-
660
- it('should not mutate the input fileConfig', () => {
661
- const fileConfig: FileConfig = {
662
- ...baseFileConfig,
663
- endpoints: {
664
- ...baseFileConfig.endpoints,
665
- [EModelEndpoint.openAI]: {
666
- disabled: false,
667
- fileLimit: 10,
668
- },
669
- },
670
- };
671
-
672
- const originalDisabled = fileConfig.endpoints[EModelEndpoint.openAI]!.disabled;
673
-
674
- getEndpointFileConfig({
675
- fileConfig,
676
- endpoint: EModelEndpoint.openAI,
677
- });
678
-
679
- /** Config should not be mutated */
680
- expect(fileConfig.endpoints[EModelEndpoint.openAI]!.disabled).toBe(originalDisabled);
681
- });
682
- });
683
-
684
- describe('assistants endpoint handling', () => {
685
- it('should find assistants endpoint config', () => {
686
- const fileConfig: FileConfig = {
687
- ...baseFileConfig,
688
- endpoints: {
689
- ...baseFileConfig.endpoints,
690
- [EModelEndpoint.assistants]: {
691
- disabled: false,
692
- fileLimit: 20,
693
- },
694
- },
695
- };
696
-
697
- const result = getEndpointFileConfig({
698
- fileConfig,
699
- endpoint: EModelEndpoint.assistants,
700
- });
701
-
702
- expect(result.disabled).toBe(false);
703
- expect(result.fileLimit).toBe(20);
704
- });
705
-
706
- it('should find azureAssistants endpoint config', () => {
707
- const fileConfig: FileConfig = {
708
- ...baseFileConfig,
709
- endpoints: {
710
- ...baseFileConfig.endpoints,
711
- [EModelEndpoint.azureAssistants]: {
712
- disabled: true,
713
- fileLimit: 15,
714
- },
715
- },
716
- };
717
-
718
- const result = getEndpointFileConfig({
719
- fileConfig,
720
- endpoint: EModelEndpoint.azureAssistants,
721
- });
722
-
723
- expect(result.disabled).toBe(true);
724
- expect(result.fileLimit).toBe(15);
725
- });
726
-
727
- it('should not fallback to agents for assistants endpoints', () => {
728
- const fileConfig: FileConfig = {
729
- ...baseFileConfig,
730
- endpoints: {
731
- ...baseFileConfig.endpoints,
732
- [EModelEndpoint.agents]: {
733
- disabled: true,
734
- fileLimit: 5,
735
- },
736
- default: {
737
- disabled: false,
738
- fileLimit: 10,
739
- },
740
- },
741
- };
742
-
743
- const result = getEndpointFileConfig({
744
- fileConfig,
745
- endpoint: 'unknownAssistants',
746
- endpointType: EModelEndpoint.assistants,
747
- });
748
-
749
- /** Should use default, not agents */
750
- expect(result.fileLimit).toBe(10);
751
- });
752
- });
753
-
754
- describe('agents endpoint handling', () => {
755
- it('should find agents endpoint config', () => {
756
- const fileConfig: FileConfig = {
757
- ...baseFileConfig,
758
- endpoints: {
759
- ...baseFileConfig.endpoints,
760
- [EModelEndpoint.agents]: {
761
- disabled: false,
762
- fileLimit: 9,
763
- },
764
- },
765
- };
766
-
767
- const result = getEndpointFileConfig({
768
- fileConfig,
769
- endpoint: EModelEndpoint.agents,
770
- });
771
-
772
- expect(result.disabled).toBe(false);
773
- expect(result.fileLimit).toBe(9);
774
- });
775
- });
776
-
777
- describe('mergeFileConfig integration', () => {
778
- it('should work with mergeFileConfig output for disabled endpoint', () => {
779
- const dynamicConfig = {
780
- endpoints: {
781
- [EModelEndpoint.openAI]: {
782
- disabled: true,
783
- },
784
- },
785
- };
786
-
787
- const merged = mergeFileConfig(dynamicConfig);
788
- const result = getEndpointFileConfig({
789
- fileConfig: merged,
790
- endpoint: EModelEndpoint.openAI,
791
- });
792
-
793
- expect(result.disabled).toBe(true);
794
- /** When disabled: true, merge sets these to 0 */
795
- expect(result.fileLimit).toBe(0);
796
- expect(result.fileSizeLimit).toBe(0);
797
- expect(result.totalSizeLimit).toBe(0);
798
- expect(result.supportedMimeTypes).toEqual([]);
799
- });
800
-
801
- it('should work with mergeFileConfig output for enabled endpoint', () => {
802
- const dynamicConfig = {
803
- endpoints: {
804
- [EModelEndpoint.anthropic]: {
805
- disabled: false,
806
- fileLimit: 5,
807
- fileSizeLimit: 10,
808
- },
809
- },
810
- };
811
-
812
- const merged = mergeFileConfig(dynamicConfig);
813
- const result = getEndpointFileConfig({
814
- fileConfig: merged,
815
- endpoint: EModelEndpoint.anthropic,
816
- });
817
-
818
- expect(result.disabled).toBe(false);
819
- expect(result.fileLimit).toBe(5);
820
- /** Should convert MB to bytes */
821
- expect(result.fileSizeLimit).toBe(10 * 1024 * 1024);
822
- });
823
-
824
- it('should preserve disabled: false in merged config', () => {
825
- const dynamicConfig = {
826
- endpoints: {
827
- [EModelEndpoint.anthropic]: {
828
- disabled: false,
829
- fileLimit: 8,
830
- },
831
- },
832
- };
833
-
834
- const merged = mergeFileConfig(dynamicConfig);
835
- const result = getEndpointFileConfig({
836
- fileConfig: merged,
837
- endpoint: EModelEndpoint.anthropic,
838
- });
839
-
840
- expect(result.disabled).toBe(false);
841
- expect(result.fileLimit).toBe(8);
842
- });
843
-
844
- it('should not mutate base fileConfig during merge', () => {
845
- const originalBaseAgentsConfig = { ...baseFileConfig.endpoints.agents };
846
-
847
- const dynamicConfig = {
848
- endpoints: {
849
- [EModelEndpoint.agents]: {
850
- disabled: true,
851
- fileLimit: 1,
852
- },
853
- },
854
- };
855
-
856
- mergeFileConfig(dynamicConfig);
857
-
858
- /** Base config should not be mutated */
859
- expect(baseFileConfig.endpoints.agents).toEqual(originalBaseAgentsConfig);
860
- });
861
- });
862
-
863
- describe('lookup priority verification', () => {
864
- it('should check endpointType before endpoint for standard endpoints', () => {
865
- const fileConfig: FileConfig = {
866
- ...baseFileConfig,
867
- endpoints: {
868
- ...baseFileConfig.endpoints,
869
- [EModelEndpoint.openAI]: {
870
- disabled: true,
871
- fileLimit: 1,
872
- },
873
- wrongEndpoint: {
874
- disabled: false,
875
- fileLimit: 99,
876
- },
877
- },
878
- };
879
-
880
- const result = getEndpointFileConfig({
881
- fileConfig,
882
- endpoint: 'wrongEndpoint',
883
- endpointType: EModelEndpoint.openAI,
884
- });
885
-
886
- /** Should use endpointType config, not endpoint */
887
- expect(result.fileLimit).toBe(1);
888
- });
889
-
890
- it('should check endpoint when endpointType not found', () => {
891
- const fileConfig: FileConfig = {
892
- ...baseFileConfig,
893
- endpoints: {
894
- ...baseFileConfig.endpoints,
895
- myCustomEndpoint: {
896
- disabled: true,
897
- fileLimit: 7,
898
- },
899
- },
900
- };
901
-
902
- const result = getEndpointFileConfig({
903
- fileConfig,
904
- endpoint: 'myCustomEndpoint',
905
- endpointType: 'notFound',
906
- });
907
-
908
- expect(result.fileLimit).toBe(7);
909
- });
910
- });
911
-
912
- describe('disabled handling', () => {
913
- it('should properly handle disabled: true', () => {
914
- const fileConfig: FileConfig = {
915
- ...baseFileConfig,
916
- endpoints: {
917
- ...baseFileConfig.endpoints,
918
- [EModelEndpoint.openAI]: {
919
- disabled: true,
920
- fileLimit: 0,
921
- fileSizeLimit: 0,
922
- totalSizeLimit: 0,
923
- supportedMimeTypes: [],
924
- },
925
- },
926
- };
927
-
928
- const result = getEndpointFileConfig({
929
- fileConfig,
930
- endpoint: EModelEndpoint.openAI,
931
- });
932
-
933
- expect(result.disabled).toBe(true);
934
- expect(result.fileLimit).toBe(0);
935
- expect(result.fileSizeLimit).toBe(0);
936
- expect(result.totalSizeLimit).toBe(0);
937
- expect(result.supportedMimeTypes).toEqual([]);
938
- });
939
-
940
- it('should properly handle disabled: false', () => {
941
- const fileConfig: FileConfig = {
942
- ...baseFileConfig,
943
- endpoints: {
944
- ...baseFileConfig.endpoints,
945
- [EModelEndpoint.anthropic]: {
946
- disabled: false,
947
- fileLimit: 10,
948
- },
949
- },
950
- };
951
-
952
- const result = getEndpointFileConfig({
953
- fileConfig,
954
- endpoint: EModelEndpoint.anthropic,
955
- });
956
-
957
- expect(result.disabled).toBe(false);
958
- expect(result.fileLimit).toBe(10);
959
- });
960
-
961
- it('should treat undefined disabled as enabled', () => {
962
- const fileConfig: FileConfig = {
963
- ...baseFileConfig,
964
- endpoints: {
965
- ...baseFileConfig.endpoints,
966
- [EModelEndpoint.google]: {
967
- fileLimit: 10,
968
- },
969
- },
970
- };
971
-
972
- const result = getEndpointFileConfig({
973
- fileConfig,
974
- endpoint: EModelEndpoint.google,
975
- });
976
-
977
- /** disabled should not be explicitly true */
978
- expect(result.disabled).not.toBe(true);
979
- });
980
- });
981
-
982
- describe('partial config merging', () => {
983
- it('should merge partial endpoint config with default config', () => {
984
- const dynamicConfig = {
985
- endpoints: {
986
- google: {
987
- fileSizeLimit: 500,
988
- /** Note: supportedMimeTypes not configured */
989
- },
990
- },
991
- };
992
-
993
- const merged = mergeFileConfig(dynamicConfig);
994
- const result = getEndpointFileConfig({
995
- fileConfig: merged,
996
- endpoint: EModelEndpoint.google,
997
- });
998
-
999
- /** Should have the configured fileSizeLimit */
1000
- expect(result.fileSizeLimit).toBe(500 * 1024 * 1024);
1001
- /** Should have supportedMimeTypes from default config */
1002
- expect(result.supportedMimeTypes).toBeDefined();
1003
- expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
1004
- expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
1005
- /** Should have other fields from default */
1006
- expect(result.fileLimit).toBeDefined();
1007
- expect(result.totalSizeLimit).toBeDefined();
1008
- });
1009
-
1010
- it('should not override explicitly set fields with default', () => {
1011
- const dynamicConfig = {
1012
- endpoints: {
1013
- anthropic: {
1014
- disabled: true,
1015
- fileLimit: 3,
1016
- },
1017
- },
1018
- };
1019
-
1020
- const merged = mergeFileConfig(dynamicConfig);
1021
- const result = getEndpointFileConfig({
1022
- fileConfig: merged,
1023
- endpoint: EModelEndpoint.anthropic,
1024
- });
1025
-
1026
- /** Should keep explicitly configured values */
1027
- expect(result.disabled).toBe(true);
1028
- expect(result.fileLimit).toBe(0); /** disabled: true sets to 0 in merge */
1029
- /** But still get supportedMimeTypes from... wait, disabled: true clears this */
1030
- expect(result.supportedMimeTypes).toEqual([]);
1031
- });
1032
-
1033
- it('should handle endpoint with only fileSizeLimit configured', () => {
1034
- const dynamicConfig = {
1035
- endpoints: {
1036
- openAI: {
1037
- fileSizeLimit: 100,
1038
- },
1039
- },
1040
- };
1041
-
1042
- const merged = mergeFileConfig(dynamicConfig);
1043
- const result = getEndpointFileConfig({
1044
- fileConfig: merged,
1045
- endpoint: EModelEndpoint.openAI,
1046
- });
1047
-
1048
- expect(result.fileSizeLimit).toBe(100 * 1024 * 1024);
1049
- /** Should get these from default */
1050
- expect(result.supportedMimeTypes).toBeDefined();
1051
- expect(result.fileLimit).toBeDefined();
1052
- expect(result.disabled).not.toBe(true);
1053
- });
1054
-
1055
- it('should merge supportedMimeTypes from default when only fileSizeLimit is configured', () => {
1056
- /** This tests the exact scenario from the issue */
1057
- const dynamicConfig = {
1058
- endpoints: {
1059
- google: {
1060
- fileSizeLimit: 1000000024,
1061
- },
1062
- },
1063
- };
1064
-
1065
- const merged = mergeFileConfig(dynamicConfig);
1066
- const result = getEndpointFileConfig({
1067
- fileConfig: merged,
1068
- endpoint: EModelEndpoint.google,
1069
- });
1070
-
1071
- /** Should have the massive fileSizeLimit configured */
1072
- expect(result.fileSizeLimit).toBe(1000000024 * 1024 * 1024);
1073
- /** CRITICAL: Should have supportedMimeTypes from default, not undefined or [] */
1074
- expect(result.supportedMimeTypes).toBeDefined();
1075
- expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
1076
- expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
1077
- /** Should have other default fields */
1078
- expect(result.fileLimit).toBe(10);
1079
- expect(result.disabled).toBe(false);
1080
- });
1081
- });
1082
-
1083
- describe('real-world scenarios', () => {
1084
- it('should handle multi-provider custom endpoint configuration', () => {
1085
- const fileConfig: FileConfig = {
1086
- ...baseFileConfig,
1087
- endpoints: {
1088
- ...baseFileConfig.endpoints,
1089
- ollama: {
1090
- disabled: false,
1091
- fileLimit: 5,
1092
- },
1093
- lmstudio: {
1094
- disabled: true,
1095
- fileLimit: 3,
1096
- },
1097
- [EModelEndpoint.custom]: {
1098
- disabled: false,
1099
- fileLimit: 10,
1100
- },
1101
- },
1102
- };
1103
-
1104
- const ollamaResult = getEndpointFileConfig({
1105
- fileConfig,
1106
- endpoint: 'ollama',
1107
- endpointType: EModelEndpoint.custom,
1108
- });
1109
- expect(ollamaResult.fileLimit).toBe(5);
1110
-
1111
- const lmstudioResult = getEndpointFileConfig({
1112
- fileConfig,
1113
- endpoint: 'lmstudio',
1114
- endpointType: EModelEndpoint.custom,
1115
- });
1116
- expect(lmstudioResult.disabled).toBe(true);
1117
- expect(lmstudioResult.fileLimit).toBe(3);
1118
-
1119
- const unknownResult = getEndpointFileConfig({
1120
- fileConfig,
1121
- endpoint: 'unknownProvider',
1122
- endpointType: EModelEndpoint.custom,
1123
- });
1124
- expect(unknownResult.fileLimit).toBe(10);
1125
- });
1126
-
1127
- it('should handle switching between endpoints correctly', () => {
1128
- const fileConfig: FileConfig = {
1129
- ...baseFileConfig,
1130
- endpoints: {
1131
- ...baseFileConfig.endpoints,
1132
- [EModelEndpoint.openAI]: {
1133
- disabled: true,
1134
- },
1135
- [EModelEndpoint.anthropic]: {
1136
- disabled: false,
1137
- fileLimit: 15,
1138
- },
1139
- },
1140
- };
1141
-
1142
- const openaiResult = getEndpointFileConfig({
1143
- fileConfig,
1144
- endpoint: EModelEndpoint.openAI,
1145
- });
1146
- expect(openaiResult.disabled).toBe(true);
1147
-
1148
- const anthropicResult = getEndpointFileConfig({
1149
- fileConfig,
1150
- endpoint: EModelEndpoint.anthropic,
1151
- });
1152
- expect(anthropicResult.disabled).toBe(false);
1153
- expect(anthropicResult.fileLimit).toBe(15);
1154
- });
1155
- });
1156
-
1157
- describe('user-configured default behavior', () => {
1158
- it('should use user-configured default as effective default when endpoint not found', () => {
1159
- const dynamicConfig = {
1160
- endpoints: {
1161
- default: {
1162
- fileLimit: 7,
1163
- },
1164
- },
1165
- };
1166
-
1167
- const merged = mergeFileConfig(dynamicConfig);
1168
- const result = getEndpointFileConfig({
1169
- fileConfig: merged,
1170
- endpoint: EModelEndpoint.google,
1171
- });
1172
-
1173
- expect(result.fileLimit).toBe(7);
1174
- expect(result.disabled).toBe(false);
1175
- expect(result.supportedMimeTypes).toBeDefined();
1176
- expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
1177
- expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
1178
- });
1179
-
1180
- it('should merge endpoint config against user default (not base default)', () => {
1181
- const dynamicConfig = {
1182
- endpoints: {
1183
- default: {
1184
- fileLimit: 7,
1185
- },
1186
- google: {
1187
- fileSizeLimit: 123,
1188
- },
1189
- },
1190
- };
1191
-
1192
- const merged = mergeFileConfig(dynamicConfig);
1193
- const result = getEndpointFileConfig({
1194
- fileConfig: merged,
1195
- endpoint: EModelEndpoint.google,
1196
- });
1197
-
1198
- /** fileLimit should come from user default */
1199
- expect(result.fileLimit).toBe(7);
1200
- /** fileSizeLimit should come from endpoint (converted to bytes) */
1201
- expect(result.fileSizeLimit).toBe(123 * 1024 * 1024);
1202
- });
1203
-
1204
- it('should respect user-configured default supportedMimeTypes override', () => {
1205
- const dynamicConfig = {
1206
- endpoints: {
1207
- default: {
1208
- supportedMimeTypes: ['^text\\/plain$'],
1209
- },
1210
- },
1211
- };
1212
-
1213
- const merged = mergeFileConfig(dynamicConfig);
1214
- const result = getEndpointFileConfig({
1215
- fileConfig: merged,
1216
- });
1217
-
1218
- /** Only text/plain should be allowed */
1219
- expect(result.supportedMimeTypes).toBeDefined();
1220
- expect(result.supportedMimeTypes!.length).toBe(1);
1221
- const [onlyRegex] = result.supportedMimeTypes as RegExp[];
1222
- expect(onlyRegex.test('text/plain')).toBe(true);
1223
- expect(onlyRegex.test('image/png')).toBe(false);
1224
- });
1225
-
1226
- it('should propagate disabled from user default across fallbacks', () => {
1227
- const dynamicConfig = {
1228
- endpoints: {
1229
- default: {
1230
- disabled: true,
1231
- },
1232
- },
1233
- };
1234
-
1235
- const merged = mergeFileConfig(dynamicConfig);
1236
- const result = getEndpointFileConfig({
1237
- fileConfig: merged,
1238
- endpoint: EModelEndpoint.google,
1239
- });
1240
-
1241
- expect(result.disabled).toBe(true);
1242
- expect(result.fileLimit).toBe(0);
1243
- expect(result.fileSizeLimit).toBe(0);
1244
- expect(result.totalSizeLimit).toBe(0);
1245
- expect(result.supportedMimeTypes).toEqual([]);
1246
- });
1247
- });
1248
- });