📂 refactor: Cleanup File Filtering Logic, Improve Validation (#10414)

* 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
This commit is contained in:
Danny Avila 2025-11-10 19:05:30 -05:00 committed by GitHub
parent 06c060b983
commit 2524d33362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 2352 additions and 290 deletions

View file

@ -1,12 +1,12 @@
import { z } from 'zod';
import type { ZodError } from 'zod';
import type { TModelsConfig } from './types';
import type { TEndpointsConfig, TModelsConfig, TConfig } from './types';
import { EModelEndpoint, eModelEndpointSchema } from './schemas';
import { specsConfigSchema, TSpecsConfig } from './models';
import { fileConfigSchema } from './file-config';
import { apiBaseUrl } from './api-endpoints';
import { FileSources } from './types/files';
import { MCPServersSchema } from './mcp';
import { apiBaseUrl } from './api-endpoints';
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord', 'saml'];
@ -1737,3 +1737,24 @@ export const specialVariables = {
};
export type TSpecialVarLabel = `com_ui_special_var_${keyof typeof specialVariables}`;
/**
* Retrieves a specific field from the endpoints configuration for a given endpoint key.
* Does not infer or default any endpoint type when absent.
*/
export function getEndpointField<
K extends TConfig[keyof TConfig] extends never ? never : keyof TConfig,
>(
endpointsConfig: TEndpointsConfig | undefined | null,
endpoint: EModelEndpoint | string | null | undefined,
property: K,
): TConfig[K] | undefined {
if (!endpointsConfig || endpoint === null || endpoint === undefined) {
return undefined;
}
const config = endpointsConfig[endpoint];
if (!config) {
return undefined;
}
return config[property];
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
import { z } from 'zod';
import { EModelEndpoint } from './schemas';
import type { EndpointFileConfig, FileConfig } from './types/files';
import { EModelEndpoint, isAgentsEndpoint, isDocumentSupportedProvider } from './schemas';
import { normalizeEndpointName } from './utils';
export const supportsFiles = {
[EModelEndpoint.openAI]: true,
@ -331,9 +332,146 @@ export const convertStringsToRegex = (patterns: string[]): RegExp[] =>
return acc;
}, []);
/**
* Gets the appropriate endpoint file configuration with standardized lookup logic.
*
* @param params - Object containing fileConfig, endpoint, and optional conversationEndpoint
* @param params.fileConfig - The merged file configuration
* @param params.endpoint - The endpoint name to look up
* @param params.conversationEndpoint - Optional conversation endpoint for additional context
* @returns The endpoint file configuration or undefined
*/
/**
* Merges an endpoint config with the default config to ensure all fields are populated.
* For document-supported providers, uses the comprehensive MIME type list (includes videos/audio).
*/
function mergeWithDefault(
endpointConfig: EndpointFileConfig,
defaultConfig: EndpointFileConfig,
endpoint?: string | null,
): EndpointFileConfig {
/** Use comprehensive MIME types for document-supported providers */
const defaultMimeTypes = isDocumentSupportedProvider(endpoint)
? supportedMimeTypes
: defaultConfig.supportedMimeTypes;
return {
disabled: endpointConfig.disabled ?? defaultConfig.disabled,
fileLimit: endpointConfig.fileLimit ?? defaultConfig.fileLimit,
fileSizeLimit: endpointConfig.fileSizeLimit ?? defaultConfig.fileSizeLimit,
totalSizeLimit: endpointConfig.totalSizeLimit ?? defaultConfig.totalSizeLimit,
supportedMimeTypes: endpointConfig.supportedMimeTypes ?? defaultMimeTypes,
};
}
export function getEndpointFileConfig(params: {
fileConfig?: FileConfig | null;
endpoint?: string | null;
endpointType?: string | null;
}): EndpointFileConfig {
const { fileConfig: mergedFileConfig, endpoint, endpointType } = params;
if (!mergedFileConfig?.endpoints) {
return fileConfig.endpoints.default;
}
/** Compute an effective default by merging user-configured default over the base default */
const baseDefaultConfig = fileConfig.endpoints.default;
const userDefaultConfig = mergedFileConfig.endpoints.default;
const defaultConfig = userDefaultConfig
? mergeWithDefault(userDefaultConfig, baseDefaultConfig, 'default')
: baseDefaultConfig;
const normalizedEndpoint = normalizeEndpointName(endpoint ?? '');
const standardEndpoints = new Set([
'default',
EModelEndpoint.agents,
EModelEndpoint.assistants,
EModelEndpoint.azureAssistants,
EModelEndpoint.openAI,
EModelEndpoint.azureOpenAI,
EModelEndpoint.anthropic,
EModelEndpoint.google,
EModelEndpoint.bedrock,
]);
const normalizedEndpointType = normalizeEndpointName(endpointType ?? '');
const isCustomEndpoint =
endpointType === EModelEndpoint.custom ||
(!standardEndpoints.has(normalizedEndpointType) &&
normalizedEndpoint &&
!standardEndpoints.has(normalizedEndpoint));
if (isCustomEndpoint) {
/** 1. Check direct endpoint lookup (could be normalized or not) */
if (endpoint && mergedFileConfig.endpoints[endpoint]) {
return mergeWithDefault(mergedFileConfig.endpoints[endpoint], defaultConfig, endpoint);
}
/** 2. Check normalized endpoint lookup (skip standard endpoint keys) */
for (const key in mergedFileConfig.endpoints) {
if (!standardEndpoints.has(key) && normalizeEndpointName(key) === normalizedEndpoint) {
return mergeWithDefault(mergedFileConfig.endpoints[key], defaultConfig, key);
}
}
/** 3. Fallback to generic 'custom' config if any */
if (mergedFileConfig.endpoints[EModelEndpoint.custom]) {
return mergeWithDefault(
mergedFileConfig.endpoints[EModelEndpoint.custom],
defaultConfig,
endpoint,
);
}
/** 4. Fallback to 'agents' (all custom endpoints are non-assistants) */
if (mergedFileConfig.endpoints[EModelEndpoint.agents]) {
return mergeWithDefault(
mergedFileConfig.endpoints[EModelEndpoint.agents],
defaultConfig,
endpoint,
);
}
/** 5. Fallback to default */
return defaultConfig;
}
/** Check endpointType first (most reliable for standard endpoints) */
if (endpointType && mergedFileConfig.endpoints[endpointType]) {
return mergeWithDefault(mergedFileConfig.endpoints[endpointType], defaultConfig, endpointType);
}
/** Check direct endpoint lookup */
if (endpoint && mergedFileConfig.endpoints[endpoint]) {
return mergeWithDefault(mergedFileConfig.endpoints[endpoint], defaultConfig, endpoint);
}
/** Check normalized endpoint */
if (normalizedEndpoint && mergedFileConfig.endpoints[normalizedEndpoint]) {
return mergeWithDefault(
mergedFileConfig.endpoints[normalizedEndpoint],
defaultConfig,
normalizedEndpoint,
);
}
/** Fallback to agents if endpoint is explicitly agents */
const isAgents = isAgentsEndpoint(normalizedEndpointType || normalizedEndpoint);
if (isAgents && mergedFileConfig.endpoints[EModelEndpoint.agents]) {
return mergeWithDefault(
mergedFileConfig.endpoints[EModelEndpoint.agents],
defaultConfig,
EModelEndpoint.agents,
);
}
/** Return default config */
return defaultConfig;
}
export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | undefined): FileConfig {
const mergedConfig: FileConfig = {
...fileConfig,
endpoints: {
...fileConfig.endpoints,
},
ocr: {
...fileConfig.ocr,
supportedMimeTypes: fileConfig.ocr?.supportedMimeTypes || [],
@ -398,8 +536,11 @@ export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | unde
for (const key in dynamic.endpoints) {
const dynamicEndpoint = (dynamic.endpoints as Record<string, EndpointFileConfig>)[key];
/** Deep copy the base endpoint config if it exists to prevent mutation */
if (!mergedConfig.endpoints[key]) {
mergedConfig.endpoints[key] = {};
} else {
mergedConfig.endpoints[key] = { ...mergedConfig.endpoints[key] };
}
const mergedEndpoint = mergedConfig.endpoints[key];
@ -428,6 +569,10 @@ export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | unde
}
});
if (dynamicEndpoint.disabled !== undefined) {
mergedEndpoint.disabled = dynamicEndpoint.disabled;
}
if (dynamicEndpoint.supportedMimeTypes) {
mergedEndpoint.supportedMimeTypes = convertStringsToRegex(
dynamicEndpoint.supportedMimeTypes as unknown as string[],

View file

@ -52,3 +52,11 @@ export function extractEnvVariable(value: string) {
return result;
}
/**
* Normalize the endpoint name to system-expected value.
* @param name
*/
export function normalizeEndpointName(name = ''): string {
return name.toLowerCase() === 'ollama' ? 'ollama' : name;
}