mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-06 10:38:50 +01:00
* feat: add filterFilesByEndpointConfig to filter disabled file processing by provider * chore: explicit define of endpointFileConfig for better debugging * refactor: move `normalizeEndpointName` to data-provider as used app-wide * chore: remove overrideEndpoint from useFileHandling * refactor: improve endpoint file config selection * refactor: update filterFilesByEndpointConfig to accept structured parameters and improve endpoint file config handling * refactor: replace defaultFileConfig with getEndpointFileConfig for improved file configuration handling across components * test: add comprehensive unit tests for getEndpointFileConfig to validate endpoint configuration handling * refactor: streamline agent endpoint assignment and improve file filtering logic * feat: add error handling for disabled file uploads in endpoint configuration * refactor: update encodeAndFormat functions to accept structured parameters for provider and endpoint * refactor: streamline requestFiles handling in initializeAgent function * fix: getEndpointFileConfig partial config merging scenarios * refactor: enhance mergeWithDefault function to support document-supported providers with comprehensive MIME types * refactor: user-configured default file config in getEndpointFileConfig * fix: prevent file handling when endpoint is disabled and file is dragged to chat * refactor: move `getEndpointField` to `data-provider` and update usage across components and hooks * fix: prioritize endpointType based on agent.endpoint in file filtering logic * fix: prioritize agent.endpoint in file filtering logic and remove unnecessary endpointType defaulting
1097 lines
30 KiB
TypeScript
1097 lines
30 KiB
TypeScript
import type { FileConfig } from './types/files';
|
|
import {
|
|
fileConfig as baseFileConfig,
|
|
getEndpointFileConfig,
|
|
mergeFileConfig,
|
|
} from './file-config';
|
|
import { EModelEndpoint } from './schemas';
|
|
|
|
describe('getEndpointFileConfig', () => {
|
|
describe('custom endpoint lookup', () => {
|
|
it('should find custom endpoint by direct lookup', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
ollama: {
|
|
disabled: true,
|
|
fileLimit: 5,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'ollama',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(5);
|
|
});
|
|
|
|
it('should find custom endpoint by normalized lookup', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
ollama: {
|
|
disabled: true,
|
|
fileLimit: 7,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** Test with non-normalized name */
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'Ollama',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(7);
|
|
});
|
|
|
|
it('should fallback to generic custom config when specific endpoint not found', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.custom]: {
|
|
disabled: false,
|
|
fileLimit: 3,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'unknownCustomEndpoint',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(3);
|
|
});
|
|
|
|
it('should fallback to agents config when custom and specific endpoint not found', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 8,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'unknownCustomEndpoint',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(8);
|
|
});
|
|
|
|
it('should use base agents config when only default is dynamically configured', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 12,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: 'unknownCustomEndpoint',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
/**
|
|
* Should use base agents config (fileLimit: 10) since it exists in baseFileConfig
|
|
* and custom endpoints fall back to agents
|
|
*/
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(10); /** From baseFileConfig.endpoints.agents */
|
|
});
|
|
|
|
it('should prioritize specific custom endpoint over generic custom config', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.custom]: {
|
|
disabled: false,
|
|
fileLimit: 20,
|
|
},
|
|
ollama: {
|
|
disabled: true,
|
|
fileLimit: 3,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'ollama',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
/** Should use ollama config, not generic custom */
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(3);
|
|
});
|
|
|
|
it('should skip standard endpoint keys in normalized lookup', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 99,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** "default" should not match via normalized lookup for custom endpoints */
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'default',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
|
|
/** Should not use direct lookup, should fall back to default */
|
|
expect(result.fileLimit).toBe(99);
|
|
});
|
|
|
|
it('should handle complete fallback chain: specific -> custom -> agents -> default', () => {
|
|
const customConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
myOllama: {
|
|
disabled: true,
|
|
fileLimit: 1,
|
|
},
|
|
[EModelEndpoint.custom]: {
|
|
disabled: false,
|
|
fileLimit: 2,
|
|
},
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 3,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 4,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** 1. Should find specific config */
|
|
const specific = getEndpointFileConfig({
|
|
fileConfig: customConfig,
|
|
endpoint: 'myOllama',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(specific.fileLimit).toBe(1);
|
|
|
|
/** 2. Should fallback to custom when specific not found */
|
|
const customOnlyConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.custom]: {
|
|
disabled: false,
|
|
fileLimit: 2,
|
|
},
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 3,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 4,
|
|
},
|
|
},
|
|
};
|
|
const customFallback = getEndpointFileConfig({
|
|
fileConfig: customOnlyConfig,
|
|
endpoint: 'unknownCustom',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(customFallback.fileLimit).toBe(2);
|
|
|
|
/** 3. Should fallback to agents */
|
|
const agentsFallback = getEndpointFileConfig({
|
|
fileConfig: {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 3,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 4,
|
|
},
|
|
},
|
|
},
|
|
endpoint: 'unknownCustom',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(agentsFallback.fileLimit).toBe(3);
|
|
|
|
/**
|
|
* 4. Should use agents even if disabled (caller decides based on disabled flag)
|
|
* getEndpointFileConfig returns the config, doesn't filter based on disabled
|
|
*/
|
|
const agentsDisabledConfig = mergeFileConfig({
|
|
endpoints: {
|
|
[EModelEndpoint.agents]: {
|
|
disabled: true,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 4,
|
|
},
|
|
},
|
|
});
|
|
const agentsDisabled = getEndpointFileConfig({
|
|
fileConfig: agentsDisabledConfig,
|
|
endpoint: 'unknownCustom',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
/** Should return agents config (disabled: true), not skip to default */
|
|
expect(agentsDisabled.disabled).toBe(true);
|
|
expect(agentsDisabled.fileLimit).toBe(0); /** disabled: true sets fileLimit to 0 */
|
|
});
|
|
});
|
|
|
|
describe('standard endpoint lookup', () => {
|
|
it('should find endpoint by endpointType', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
fileLimit: 15,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'someOtherName',
|
|
endpointType: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(15);
|
|
});
|
|
|
|
it('should find endpoint by direct endpoint name', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 25,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(25);
|
|
});
|
|
|
|
it('should find endpoint by normalized name', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
ollama: {
|
|
disabled: true,
|
|
fileLimit: 6,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** Test normalization (Ollama -> ollama) */
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'Ollama',
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(6);
|
|
});
|
|
|
|
it('should use agents fallback for explicitly agents endpoint', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 11,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.agents,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(11);
|
|
});
|
|
|
|
it('should use agents fallback for unconfigured non-standard endpoint', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 100,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'unconfiguredEndpoint',
|
|
});
|
|
|
|
/**
|
|
* With new logic, unconfigured endpoints are treated as custom
|
|
* and fall back through: specific -> custom -> agents -> default
|
|
* So this should use agents (fileLimit: 10), not default
|
|
*/
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(10);
|
|
});
|
|
|
|
it('should prioritize endpointType over endpoint name', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
fileLimit: 5,
|
|
},
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** endpointType should take priority */
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.openAI,
|
|
endpointType: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should return default when fileConfig is null', () => {
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: null,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.disabled).toBe(false);
|
|
});
|
|
|
|
it('should return default when fileConfig is undefined', () => {
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: undefined,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.disabled).toBe(false);
|
|
});
|
|
|
|
it('should handle empty endpoint gracefully', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 50,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: '',
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(50);
|
|
});
|
|
|
|
it('should handle null endpoint gracefully', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 50,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: null,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(50);
|
|
});
|
|
|
|
it('should handle undefined endpoint gracefully', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 50,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: undefined,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(50);
|
|
});
|
|
|
|
it('should not mutate the input fileConfig', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const originalDisabled = fileConfig.endpoints[EModelEndpoint.openAI]!.disabled;
|
|
|
|
getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
/** Config should not be mutated */
|
|
expect(fileConfig.endpoints[EModelEndpoint.openAI]!.disabled).toBe(originalDisabled);
|
|
});
|
|
});
|
|
|
|
describe('assistants endpoint handling', () => {
|
|
it('should find assistants endpoint config', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.assistants]: {
|
|
disabled: false,
|
|
fileLimit: 20,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.assistants,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(20);
|
|
});
|
|
|
|
it('should find azureAssistants endpoint config', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.azureAssistants]: {
|
|
disabled: true,
|
|
fileLimit: 15,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.azureAssistants,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(15);
|
|
});
|
|
|
|
it('should not fallback to agents for assistants endpoints', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: true,
|
|
fileLimit: 5,
|
|
},
|
|
default: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'unknownAssistants',
|
|
endpointType: EModelEndpoint.assistants,
|
|
});
|
|
|
|
/** Should use default, not agents */
|
|
expect(result.fileLimit).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe('agents endpoint handling', () => {
|
|
it('should find agents endpoint config', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.agents]: {
|
|
disabled: false,
|
|
fileLimit: 9,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.agents,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(9);
|
|
});
|
|
});
|
|
|
|
describe('mergeFileConfig integration', () => {
|
|
it('should work with mergeFileConfig output for disabled endpoint', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
/** When disabled: true, merge sets these to 0 */
|
|
expect(result.fileLimit).toBe(0);
|
|
expect(result.fileSizeLimit).toBe(0);
|
|
expect(result.totalSizeLimit).toBe(0);
|
|
expect(result.supportedMimeTypes).toEqual([]);
|
|
});
|
|
|
|
it('should work with mergeFileConfig output for enabled endpoint', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 5,
|
|
fileSizeLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(5);
|
|
/** Should convert MB to bytes */
|
|
expect(result.fileSizeLimit).toBe(10 * 1024 * 1024);
|
|
});
|
|
|
|
it('should preserve disabled: false in merged config', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 8,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(8);
|
|
});
|
|
|
|
it('should not mutate base fileConfig during merge', () => {
|
|
const originalBaseAgentsConfig = { ...baseFileConfig.endpoints.agents };
|
|
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
[EModelEndpoint.agents]: {
|
|
disabled: true,
|
|
fileLimit: 1,
|
|
},
|
|
},
|
|
};
|
|
|
|
mergeFileConfig(dynamicConfig);
|
|
|
|
/** Base config should not be mutated */
|
|
expect(baseFileConfig.endpoints.agents).toEqual(originalBaseAgentsConfig);
|
|
});
|
|
});
|
|
|
|
describe('lookup priority verification', () => {
|
|
it('should check endpointType before endpoint for standard endpoints', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
fileLimit: 1,
|
|
},
|
|
wrongEndpoint: {
|
|
disabled: false,
|
|
fileLimit: 99,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'wrongEndpoint',
|
|
endpointType: EModelEndpoint.openAI,
|
|
});
|
|
|
|
/** Should use endpointType config, not endpoint */
|
|
expect(result.fileLimit).toBe(1);
|
|
});
|
|
|
|
it('should check endpoint when endpointType not found', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
myCustomEndpoint: {
|
|
disabled: true,
|
|
fileLimit: 7,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'myCustomEndpoint',
|
|
endpointType: 'notFound',
|
|
});
|
|
|
|
expect(result.fileLimit).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe('disabled handling', () => {
|
|
it('should properly handle disabled: true', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
fileLimit: 0,
|
|
fileSizeLimit: 0,
|
|
totalSizeLimit: 0,
|
|
supportedMimeTypes: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(0);
|
|
expect(result.fileSizeLimit).toBe(0);
|
|
expect(result.totalSizeLimit).toBe(0);
|
|
expect(result.supportedMimeTypes).toEqual([]);
|
|
});
|
|
|
|
it('should properly handle disabled: false', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.fileLimit).toBe(10);
|
|
});
|
|
|
|
it('should treat undefined disabled as enabled', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.google]: {
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
/** disabled should not be explicitly true */
|
|
expect(result.disabled).not.toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('partial config merging', () => {
|
|
it('should merge partial endpoint config with default config', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
google: {
|
|
fileSizeLimit: 500,
|
|
/** Note: supportedMimeTypes not configured */
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
/** Should have the configured fileSizeLimit */
|
|
expect(result.fileSizeLimit).toBe(500 * 1024 * 1024);
|
|
/** Should have supportedMimeTypes from default config */
|
|
expect(result.supportedMimeTypes).toBeDefined();
|
|
expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
|
|
expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
|
|
/** Should have other fields from default */
|
|
expect(result.fileLimit).toBeDefined();
|
|
expect(result.totalSizeLimit).toBeDefined();
|
|
});
|
|
|
|
it('should not override explicitly set fields with default', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
anthropic: {
|
|
disabled: true,
|
|
fileLimit: 3,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
|
|
/** Should keep explicitly configured values */
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(0); /** disabled: true sets to 0 in merge */
|
|
/** But still get supportedMimeTypes from... wait, disabled: true clears this */
|
|
expect(result.supportedMimeTypes).toEqual([]);
|
|
});
|
|
|
|
it('should handle endpoint with only fileSizeLimit configured', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
openAI: {
|
|
fileSizeLimit: 100,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
|
|
expect(result.fileSizeLimit).toBe(100 * 1024 * 1024);
|
|
/** Should get these from default */
|
|
expect(result.supportedMimeTypes).toBeDefined();
|
|
expect(result.fileLimit).toBeDefined();
|
|
expect(result.disabled).not.toBe(true);
|
|
});
|
|
|
|
it('should merge supportedMimeTypes from default when only fileSizeLimit is configured', () => {
|
|
/** This tests the exact scenario from the issue */
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
google: {
|
|
fileSizeLimit: 1000000024,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
/** Should have the massive fileSizeLimit configured */
|
|
expect(result.fileSizeLimit).toBe(1000000024 * 1024 * 1024);
|
|
/** CRITICAL: Should have supportedMimeTypes from default, not undefined or [] */
|
|
expect(result.supportedMimeTypes).toBeDefined();
|
|
expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
|
|
expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
|
|
/** Should have other default fields */
|
|
expect(result.fileLimit).toBe(10);
|
|
expect(result.disabled).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('real-world scenarios', () => {
|
|
it('should handle multi-provider custom endpoint configuration', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
ollama: {
|
|
disabled: false,
|
|
fileLimit: 5,
|
|
},
|
|
lmstudio: {
|
|
disabled: true,
|
|
fileLimit: 3,
|
|
},
|
|
[EModelEndpoint.custom]: {
|
|
disabled: false,
|
|
fileLimit: 10,
|
|
},
|
|
},
|
|
};
|
|
|
|
const ollamaResult = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'ollama',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(ollamaResult.fileLimit).toBe(5);
|
|
|
|
const lmstudioResult = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'lmstudio',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(lmstudioResult.disabled).toBe(true);
|
|
expect(lmstudioResult.fileLimit).toBe(3);
|
|
|
|
const unknownResult = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: 'unknownProvider',
|
|
endpointType: EModelEndpoint.custom,
|
|
});
|
|
expect(unknownResult.fileLimit).toBe(10);
|
|
});
|
|
|
|
it('should handle switching between endpoints correctly', () => {
|
|
const fileConfig: FileConfig = {
|
|
...baseFileConfig,
|
|
endpoints: {
|
|
...baseFileConfig.endpoints,
|
|
[EModelEndpoint.openAI]: {
|
|
disabled: true,
|
|
},
|
|
[EModelEndpoint.anthropic]: {
|
|
disabled: false,
|
|
fileLimit: 15,
|
|
},
|
|
},
|
|
};
|
|
|
|
const openaiResult = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.openAI,
|
|
});
|
|
expect(openaiResult.disabled).toBe(true);
|
|
|
|
const anthropicResult = getEndpointFileConfig({
|
|
fileConfig,
|
|
endpoint: EModelEndpoint.anthropic,
|
|
});
|
|
expect(anthropicResult.disabled).toBe(false);
|
|
expect(anthropicResult.fileLimit).toBe(15);
|
|
});
|
|
});
|
|
|
|
describe('user-configured default behavior', () => {
|
|
it('should use user-configured default as effective default when endpoint not found', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
default: {
|
|
fileLimit: 7,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
expect(result.fileLimit).toBe(7);
|
|
expect(result.disabled).toBe(false);
|
|
expect(result.supportedMimeTypes).toBeDefined();
|
|
expect(Array.isArray(result.supportedMimeTypes)).toBe(true);
|
|
expect(result.supportedMimeTypes!.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should merge endpoint config against user default (not base default)', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
default: {
|
|
fileLimit: 7,
|
|
},
|
|
google: {
|
|
fileSizeLimit: 123,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
/** fileLimit should come from user default */
|
|
expect(result.fileLimit).toBe(7);
|
|
/** fileSizeLimit should come from endpoint (converted to bytes) */
|
|
expect(result.fileSizeLimit).toBe(123 * 1024 * 1024);
|
|
});
|
|
|
|
it('should respect user-configured default supportedMimeTypes override', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
default: {
|
|
supportedMimeTypes: ['^text\\/plain$'],
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
});
|
|
|
|
/** Only text/plain should be allowed */
|
|
expect(result.supportedMimeTypes).toBeDefined();
|
|
expect(result.supportedMimeTypes!.length).toBe(1);
|
|
const [onlyRegex] = result.supportedMimeTypes as RegExp[];
|
|
expect(onlyRegex.test('text/plain')).toBe(true);
|
|
expect(onlyRegex.test('image/png')).toBe(false);
|
|
});
|
|
|
|
it('should propagate disabled from user default across fallbacks', () => {
|
|
const dynamicConfig = {
|
|
endpoints: {
|
|
default: {
|
|
disabled: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
const merged = mergeFileConfig(dynamicConfig);
|
|
const result = getEndpointFileConfig({
|
|
fileConfig: merged,
|
|
endpoint: EModelEndpoint.google,
|
|
});
|
|
|
|
expect(result.disabled).toBe(true);
|
|
expect(result.fileLimit).toBe(0);
|
|
expect(result.fileSizeLimit).toBe(0);
|
|
expect(result.totalSizeLimit).toBe(0);
|
|
expect(result.supportedMimeTypes).toEqual([]);
|
|
});
|
|
});
|
|
});
|