🧹 chore: Clean Up Config Fields (#12537)

* chore: remove unused `interface.endpointsMenu` config field

* chore: address review — restore JSDoc UI-only example, add Zod strip test

* chore: remove unused `interface.sidePanel` config field

* chore: restrict fileStrategy/fileStrategies schema to valid storage backends

* fix: use valid FileStorage value in AppService test

* chore: address review — version bump, exhaustiveness guard, JSDoc, configSchema test

* chore: remove debug logger.log from MessageIcon render path

* fix: rewrite MessageIcon render tests to use render counting instead of logger spying

* chore: bump librechat-data-provider to 0.8.407

* chore: sync example YAML version to 1.3.7
This commit is contained in:
Danny Avila 2026-04-03 12:22:58 -04:00 committed by GitHub
parent b4d97bd888
commit ea28dbfa89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 211 additions and 190 deletions

View file

@ -29,14 +29,11 @@ export async function loadDefaultInterface({
const loadedInterface: AppConfig['interfaceConfig'] = removeNullishValues({
// UI elements - use schema defaults
endpointsMenu:
interfaceConfig?.endpointsMenu ?? (hasModelSpecs ? false : defaults.endpointsMenu),
modelSelect:
interfaceConfig?.modelSelect ??
(hasModelSpecs ? includesAddedEndpoints : defaults.modelSelect),
parameters: interfaceConfig?.parameters ?? (hasModelSpecs ? false : defaults.parameters),
presets: interfaceConfig?.presets ?? (hasModelSpecs ? false : defaults.presets),
sidePanel: interfaceConfig?.sidePanel ?? defaults.sidePanel,
privacyPolicy: interfaceConfig?.privacyPolicy ?? defaults.privacyPolicy,
termsOfService: interfaceConfig?.termsOfService ?? defaults.termsOfService,
mcpServers: interfaceConfig?.mcpServers ?? defaults.mcpServers,

View file

@ -16,7 +16,7 @@ function fakeConfig(overrides: Record<string, unknown>, priority: number): IConf
}
const baseConfig = {
interfaceConfig: { endpointsMenu: true, sidePanel: true },
interfaceConfig: { modelSelect: true, parameters: true },
registration: { enabled: true },
endpoints: ['openAI'],
} as unknown as AppConfig;
@ -32,11 +32,11 @@ describe('mergeConfigOverrides', () => {
});
it('deep merges interface UI fields into interfaceConfig', () => {
const configs = [fakeConfig({ interface: { endpointsMenu: false } }, 10)];
const configs = [fakeConfig({ interface: { modelSelect: false } }, 10)];
const result = mergeConfigOverrides(baseConfig, configs) as unknown as Record<string, unknown>;
const iface = result.interfaceConfig as Record<string, unknown>;
expect(iface.endpointsMenu).toBe(false);
expect(iface.sidePanel).toBe(true);
expect(iface.modelSelect).toBe(false);
expect(iface.parameters).toBe(true);
});
it('sorts by priority — higher priority wins', () => {
@ -58,16 +58,16 @@ describe('mergeConfigOverrides', () => {
it('does not mutate the base config', () => {
const original = JSON.parse(JSON.stringify(baseConfig));
const configs = [fakeConfig({ interface: { endpointsMenu: false } }, 10)];
const configs = [fakeConfig({ interface: { modelSelect: false } }, 10)];
mergeConfigOverrides(baseConfig, configs);
expect(baseConfig).toEqual(original);
});
it('handles null override values', () => {
const configs = [fakeConfig({ interface: { endpointsMenu: null } }, 10)];
const configs = [fakeConfig({ interface: { modelSelect: null } }, 10)];
const result = mergeConfigOverrides(baseConfig, configs) as unknown as Record<string, unknown>;
const iface = result.interfaceConfig as Record<string, unknown>;
expect(iface.endpointsMenu).toBeNull();
expect(iface.modelSelect).toBeNull();
});
it('skips configs with no overrides object', () => {
@ -97,20 +97,20 @@ describe('mergeConfigOverrides', () => {
it('merges three priority levels in order', () => {
const configs = [
fakeConfig({ interface: { endpointsMenu: false } }, 0),
fakeConfig({ interface: { endpointsMenu: true, sidePanel: false } }, 10),
fakeConfig({ interface: { sidePanel: true } }, 100),
fakeConfig({ interface: { modelSelect: false } }, 0),
fakeConfig({ interface: { modelSelect: true, parameters: false } }, 10),
fakeConfig({ interface: { parameters: true } }, 100),
];
const result = mergeConfigOverrides(baseConfig, configs) as unknown as Record<string, unknown>;
const iface = result.interfaceConfig as Record<string, unknown>;
expect(iface.endpointsMenu).toBe(true);
expect(iface.sidePanel).toBe(true);
expect(iface.modelSelect).toBe(true);
expect(iface.parameters).toBe(true);
});
it('remaps all renamed YAML keys (exhaustiveness check)', () => {
const base = {
mcpConfig: null,
interfaceConfig: { endpointsMenu: true },
interfaceConfig: { modelSelect: true },
turnstileConfig: {},
} as unknown as AppConfig;
@ -118,7 +118,7 @@ describe('mergeConfigOverrides', () => {
fakeConfig(
{
mcpServers: { srv: { url: 'http://mcp' } },
interface: { endpointsMenu: false },
interface: { modelSelect: false },
turnstile: { siteKey: 'key-123' },
},
10,
@ -127,7 +127,7 @@ describe('mergeConfigOverrides', () => {
const result = mergeConfigOverrides(base, configs) as unknown as Record<string, unknown>;
expect(result.mcpConfig).toEqual({ srv: { url: 'http://mcp' } });
expect((result.interfaceConfig as Record<string, unknown>).endpointsMenu).toBe(false);
expect((result.interfaceConfig as Record<string, unknown>).modelSelect).toBe(false);
expect((result.turnstileConfig as Record<string, unknown>).siteKey).toBe('key-123');
expect(result.mcpServers).toBeUndefined();
@ -137,14 +137,14 @@ describe('mergeConfigOverrides', () => {
it('strips interface permission fields from overrides', () => {
const base = {
interfaceConfig: { endpointsMenu: true, sidePanel: true },
interfaceConfig: { modelSelect: true, parameters: true },
} as unknown as AppConfig;
const configs = [
fakeConfig(
{
interface: {
endpointsMenu: false,
modelSelect: false,
prompts: false,
agents: { use: false },
marketplace: { use: false },
@ -157,14 +157,14 @@ describe('mergeConfigOverrides', () => {
const iface = result.interfaceConfig as Record<string, unknown>;
// UI field should be merged
expect(iface.endpointsMenu).toBe(false);
expect(iface.modelSelect).toBe(false);
// Boolean permission fields should be stripped
expect(iface.prompts).toBeUndefined();
// Object permission fields with only permission sub-keys should be stripped
expect(iface.agents).toBeUndefined();
expect(iface.marketplace).toBeUndefined();
// Untouched base field preserved
expect(iface.sidePanel).toBe(true);
expect(iface.parameters).toBe(true);
});
it('preserves UI sub-keys in composite permission fields like mcpServers', () => {
@ -220,7 +220,7 @@ describe('mergeConfigOverrides', () => {
it('drops interface entirely when only permission fields are present', () => {
const base = {
interfaceConfig: { endpointsMenu: true },
interfaceConfig: { modelSelect: true },
} as unknown as AppConfig;
const configs = [fakeConfig({ interface: { prompts: false, agents: false } }, 10)];
@ -228,7 +228,7 @@ describe('mergeConfigOverrides', () => {
const iface = result.interfaceConfig as Record<string, unknown>;
// Base should be unchanged
expect(iface.endpointsMenu).toBe(true);
expect(iface.modelSelect).toBe(true);
expect(iface.prompts).toBeUndefined();
expect(iface.agents).toBeUndefined();
});
@ -281,7 +281,7 @@ describe('INTERFACE_PERMISSION_FIELDS', () => {
});
it('does not contain UI-only fields', () => {
const uiFields = ['endpointsMenu', 'modelSelect', 'parameters', 'presets', 'sidePanel'];
const uiFields = ['modelSelect', 'parameters', 'presets'];
for (const field of uiFields) {
expect(INTERFACE_PERMISSION_FIELDS.has(field)).toBe(false);
}

View file

@ -32,7 +32,7 @@ describe('upsertConfig', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: false } },
{ interface: { modelSelect: false } },
10,
);
@ -49,7 +49,7 @@ describe('upsertConfig', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: false } },
{ interface: { modelSelect: false } },
10,
);
@ -57,7 +57,7 @@ describe('upsertConfig', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: true } },
{ interface: { modelSelect: true } },
10,
);
@ -70,7 +70,7 @@ describe('upsertConfig', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: true } },
{ interface: { modelSelect: true } },
10,
);
@ -78,7 +78,7 @@ describe('upsertConfig', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: false } },
{ interface: { modelSelect: false } },
10,
);
@ -240,7 +240,7 @@ describe('patchConfigFields', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: true, sidePanel: true } },
{ interface: { modelSelect: true, parameters: true } },
10,
);
@ -248,14 +248,14 @@ describe('patchConfigFields', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ 'interface.endpointsMenu': false },
{ 'interface.modelSelect': false },
10,
);
const overrides = result!.overrides as Record<string, unknown>;
const iface = overrides.interface as Record<string, unknown>;
expect(iface.endpointsMenu).toBe(false);
expect(iface.sidePanel).toBe(true);
expect(iface.modelSelect).toBe(false);
expect(iface.parameters).toBe(true);
});
it('creates a config if none exists (upsert)', async () => {
@ -263,7 +263,7 @@ describe('patchConfigFields', () => {
PrincipalType.ROLE,
'newrole',
PrincipalModel.ROLE,
{ 'interface.endpointsMenu': false },
{ 'interface.modelSelect': false },
10,
);
@ -278,19 +278,19 @@ describe('unsetConfigField', () => {
PrincipalType.ROLE,
'admin',
PrincipalModel.ROLE,
{ interface: { endpointsMenu: false, sidePanel: false } },
{ interface: { modelSelect: false, parameters: false } },
10,
);
const result = await methods.unsetConfigField(
PrincipalType.ROLE,
'admin',
'interface.endpointsMenu',
'interface.modelSelect',
);
const overrides = result!.overrides as Record<string, unknown>;
const iface = overrides.interface as Record<string, unknown>;
expect(iface.endpointsMenu).toBeUndefined();
expect(iface.sidePanel).toBe(false);
expect(iface.modelSelect).toBeUndefined();
expect(iface.parameters).toBe(false);
});
it('returns null for non-existent config', async () => {

View file

@ -1,6 +1,6 @@
import type {
TEndpoint,
FileSources,
FileStorage,
TFileConfig,
TAzureConfig,
TCustomConfig,
@ -62,7 +62,7 @@ export interface AppConfig {
/** Web search configuration */
webSearch?: TCustomConfig['webSearch'];
/** File storage strategy ('local', 's3', 'firebase', 'azure_blob') */
fileStrategy: FileSources.local | FileSources.s3 | FileSources.firebase | FileSources.azure_blob;
fileStrategy: FileStorage;
/** File strategies configuration */
fileStrategies?: TCustomConfig['fileStrategies'];
/** Registration configurations */