refactor: streamline endpoint configuration and enhance appConfig usage across services

This commit is contained in:
Danny Avila 2025-08-19 00:40:26 -04:00
parent 647b1bbac6
commit 71a14517cd
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
10 changed files with 103 additions and 107 deletions

View file

@ -7,6 +7,7 @@ const {
createRun, createRun,
Tokenizer, Tokenizer,
checkAccess, checkAccess,
hasCustomUserVars,
memoryInstructions, memoryInstructions,
formatContentStrings, formatContentStrings,
createMemoryProcessor, createMemoryProcessor,
@ -39,12 +40,7 @@ const {
deleteMemory, deleteMemory,
setMemory, setMemory,
} = require('~/models'); } = require('~/models');
const { const { checkCapability, getMCPAuthMap, getAppConfig } = require('~/server/services/Config');
hasCustomUserVars,
checkCapability,
getMCPAuthMap,
getAppConfig,
} = require('~/server/services/Config');
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts'); const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent'); const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
@ -917,7 +913,7 @@ class AgentClient extends BaseClient {
} }
try { try {
if (await hasCustomUserVars()) { if (hasCustomUserVars(appConfig)) {
config.configurable.userMCPAuthMap = await getMCPAuthMap({ config.configurable.userMCPAuthMap = await getMCPAuthMap({
tools: agent.tools, tools: agent.tools,
userId: this.options.req.user.id, userId: this.options.req.user.id,
@ -1102,7 +1098,7 @@ class AgentClient extends BaseClient {
model: agent.model || agent.model_parameters.model, model: agent.model || agent.model_parameters.model,
}; };
let titleProviderConfig = await getProviderConfig(endpoint); let titleProviderConfig = getProviderConfig({ provider: endpoint, appConfig });
/** @type {TEndpoint | undefined} */ /** @type {TEndpoint | undefined} */
const endpointConfig = const endpointConfig =
@ -1117,7 +1113,10 @@ class AgentClient extends BaseClient {
if (endpointConfig?.titleEndpoint && endpointConfig.titleEndpoint !== endpoint) { if (endpointConfig?.titleEndpoint && endpointConfig.titleEndpoint !== endpoint) {
try { try {
titleProviderConfig = await getProviderConfig(endpointConfig.titleEndpoint); titleProviderConfig = getProviderConfig({
provider: endpointConfig.titleEndpoint,
appConfig,
});
endpoint = endpointConfig.titleEndpoint; endpoint = endpointConfig.titleEndpoint;
} catch (error) { } catch (error) {
logger.warn( logger.warn(
@ -1126,7 +1125,7 @@ class AgentClient extends BaseClient {
); );
// Fall back to original provider config // Fall back to original provider config
endpoint = agent.endpoint; endpoint = agent.endpoint;
titleProviderConfig = await getProviderConfig(endpoint); titleProviderConfig = getProviderConfig({ provider: endpoint, appConfig });
} }
} }

View file

@ -1,46 +1,5 @@
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { EModelEndpoint } = require('librechat-data-provider'); const { getUserMCPAuthMap } = require('@librechat/api');
const { isEnabled, getUserMCPAuthMap, normalizeEndpointName } = require('@librechat/api');
const { getAppConfig } = require('./app');
/**
* Retrieves the configuration object
* @function getBalanceConfig
* @param {Object} params
* @param {string} [params.role]
* @returns {Promise<TCustomConfig['balance'] | null>}
* */
async function getBalanceConfig({ role }) {
const isLegacyEnabled = isEnabled(process.env.CHECK_BALANCE);
const startBalance = process.env.START_BALANCE;
/** @type {TCustomConfig['balance']} */
const config = {
enabled: isLegacyEnabled,
startBalance: startBalance != null && startBalance ? parseInt(startBalance, 10) : undefined,
};
const appConfig = await getAppConfig({ role });
if (!appConfig) {
return config;
}
return { ...config, ...(appConfig?.['balance'] ?? {}) };
}
/**
*
* @param {string | EModelEndpoint} endpoint
* @returns {Promise<TEndpoint | undefined>}
*/
const getCustomEndpointConfig = async (endpoint) => {
const appConfig = await getAppConfig();
if (!appConfig) {
throw new Error(`Config not found for the ${endpoint} custom endpoint.`);
}
const customEndpoints = appConfig.endpoints?.[EModelEndpoint.custom] ?? [];
return customEndpoints.find(
(endpointConfig) => normalizeEndpointName(endpointConfig.name) === endpoint,
);
};
/** /**
* @param {Object} params * @param {Object} params
@ -67,18 +26,6 @@ async function getMCPAuthMap({ userId, tools, findPluginAuthsByKeys }) {
} }
} }
/**
* @returns {Promise<boolean>}
*/
async function hasCustomUserVars() {
const customConfig = await getAppConfig();
const mcpServers = customConfig?.mcpConfig;
return Object.values(mcpServers ?? {}).some((server) => server.customUserVars);
}
module.exports = { module.exports = {
getMCPAuthMap, getMCPAuthMap,
getBalanceConfig,
hasCustomUserVars,
getCustomEndpointConfig,
}; };

View file

@ -106,7 +106,7 @@ const initializeAgent = async ({
})) ?? {}; })) ?? {};
agent.endpoint = provider; agent.endpoint = provider;
const { getOptions, overrideProvider } = await getProviderConfig(provider); const { getOptions, overrideProvider } = getProviderConfig({ provider, appConfig });
if (overrideProvider !== agent.provider) { if (overrideProvider !== agent.provider) {
agent.provider = overrideProvider; agent.provider = overrideProvider;
} }

View file

@ -1,6 +1,6 @@
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { validateAgentModel } = require('@librechat/api');
const { createContentAggregator } = require('@librechat/agents'); const { createContentAggregator } = require('@librechat/agents');
const { validateAgentModel, getCustomEndpointConfig } = require('@librechat/api');
const { const {
Constants, Constants,
EModelEndpoint, EModelEndpoint,
@ -11,11 +11,11 @@ const {
createToolEndCallback, createToolEndCallback,
getDefaultHandlers, getDefaultHandlers,
} = require('~/server/controllers/agents/callbacks'); } = require('~/server/controllers/agents/callbacks');
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent'); const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
const { getModelsConfig } = require('~/server/controllers/ModelController'); const { getModelsConfig } = require('~/server/controllers/ModelController');
const { loadAgentTools } = require('~/server/services/ToolService'); const { loadAgentTools } = require('~/server/services/ToolService');
const AgentClient = require('~/server/controllers/agents/client'); const AgentClient = require('~/server/controllers/agents/client');
const { getAppConfig } = require('~/server/services/Config');
const { getAgent } = require('~/models/Agent'); const { getAgent } = require('~/models/Agent');
const { logViolation } = require('~/cache'); const { logViolation } = require('~/cache');
@ -147,7 +147,10 @@ const initializeClient = async ({ req, res, endpointOption }) => {
let endpointConfig = appConfig.endpoints?.[primaryConfig.endpoint]; let endpointConfig = appConfig.endpoints?.[primaryConfig.endpoint];
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) { if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
try { try {
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint); endpointConfig = getCustomEndpointConfig({
endpoint: primaryConfig.endpoint,
appConfig,
});
} catch (err) { } catch (err) {
logger.error( logger.error(
'[api/server/controllers/agents/client.js #titleConvo] Error getting custom endpoint config', '[api/server/controllers/agents/client.js #titleConvo] Error getting custom endpoint config',

View file

@ -1,3 +1,6 @@
const { Providers } = require('@librechat/agents');
const { isUserProvided, getCustomEndpointConfig } = require('@librechat/api');
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
const { const {
CacheKeys, CacheKeys,
ErrorTypes, ErrorTypes,
@ -5,13 +8,10 @@ const {
FetchTokenConfig, FetchTokenConfig,
extractEnvVariable, extractEnvVariable,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { Providers } = require('@librechat/agents');
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { fetchModels } = require('~/server/services/ModelService'); const { fetchModels } = require('~/server/services/ModelService');
const { getAppConfig } = require('~/server/services/Config');
const OpenAIClient = require('~/app/clients/OpenAIClient'); const OpenAIClient = require('~/app/clients/OpenAIClient');
const { isUserProvided } = require('~/server/utils');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const { PROXY } = process.env; const { PROXY } = process.env;
@ -21,7 +21,10 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
const { key: expiresAt } = req.body; const { key: expiresAt } = req.body;
const endpoint = overrideEndpoint ?? req.body.endpoint; const endpoint = overrideEndpoint ?? req.body.endpoint;
const endpointConfig = await getCustomEndpointConfig(endpoint); const endpointConfig = getCustomEndpointConfig({
endpoint,
appConfig,
});
if (!endpointConfig) { if (!endpointConfig) {
throw new Error(`Config not found for the ${endpoint} custom endpoint.`); throw new Error(`Config not found for the ${endpoint} custom endpoint.`);
} }

View file

@ -1,21 +1,16 @@
const initializeClient = require('./initialize'); const initializeClient = require('./initialize');
jest.mock('@librechat/api', () => ({ jest.mock('@librechat/api', () => ({
...jest.requireActual('@librechat/api'),
resolveHeaders: jest.fn(), resolveHeaders: jest.fn(),
getOpenAIConfig: jest.fn(), getOpenAIConfig: jest.fn(),
createHandleLLMNewToken: jest.fn(), createHandleLLMNewToken: jest.fn(),
})); getCustomEndpointConfig: jest.fn().mockReturnValue({
apiKey: 'test-key',
jest.mock('librechat-data-provider', () => ({ baseURL: 'https://test.com',
CacheKeys: { TOKEN_CONFIG: 'token_config' }, headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
ErrorTypes: { NO_USER_KEY: 'NO_USER_KEY', NO_BASE_URL: 'NO_BASE_URL' }, models: { default: ['test-model'] },
envVarRegex: /\$\{([^}]+)\}/, }),
FetchTokenConfig: {},
extractEnvVariable: jest.fn((value) => value),
}));
jest.mock('@librechat/agents', () => ({
Providers: { OLLAMA: 'ollama' },
})); }));
jest.mock('~/server/services/UserService', () => ({ jest.mock('~/server/services/UserService', () => ({
@ -24,12 +19,6 @@ jest.mock('~/server/services/UserService', () => ({
})); }));
jest.mock('~/server/services/Config', () => ({ jest.mock('~/server/services/Config', () => ({
getCustomEndpointConfig: jest.fn().mockResolvedValue({
apiKey: 'test-key',
baseURL: 'https://test.com',
headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
models: { default: ['test-model'] },
}),
getAppConfig: jest.fn().mockResolvedValue({ getAppConfig: jest.fn().mockResolvedValue({
'test-endpoint': { 'test-endpoint': {
apiKey: 'test-key', apiKey: 'test-key',
@ -48,10 +37,6 @@ jest.mock('~/app/clients/OpenAIClient', () => {
})); }));
}); });
jest.mock('~/server/utils', () => ({
isUserProvided: jest.fn().mockReturnValue(false),
}));
jest.mock('~/cache/getLogStores', () => jest.mock('~/cache/getLogStores', () =>
jest.fn().mockReturnValue({ jest.fn().mockReturnValue({
get: jest.fn(), get: jest.fn(),
@ -61,13 +46,25 @@ jest.mock('~/cache/getLogStores', () =>
describe('custom/initializeClient', () => { describe('custom/initializeClient', () => {
const mockRequest = { const mockRequest = {
body: { endpoint: 'test-endpoint' }, body: { endpoint: 'test-endpoint' },
user: { id: 'user-123', email: 'test@example.com' }, user: { id: 'user-123', email: 'test@example.com', role: 'user' },
app: { locals: {} }, app: { locals: {} },
}; };
const mockResponse = {}; const mockResponse = {};
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
const { getCustomEndpointConfig, resolveHeaders, getOpenAIConfig } = require('@librechat/api');
getCustomEndpointConfig.mockReturnValue({
apiKey: 'test-key',
baseURL: 'https://test.com',
headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
models: { default: ['test-model'] },
});
resolveHeaders.mockReturnValue({ 'x-user': 'user-123', 'x-email': 'test@example.com' });
getOpenAIConfig.mockReturnValue({
useLegacyContent: true,
endpointTokenConfig: null,
});
}); });
it('calls resolveHeaders with headers, user, and body for body placeholder support', async () => { it('calls resolveHeaders with headers, user, and body for body placeholder support', async () => {
@ -75,14 +72,14 @@ describe('custom/initializeClient', () => {
await initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true }); await initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true });
expect(resolveHeaders).toHaveBeenCalledWith({ expect(resolveHeaders).toHaveBeenCalledWith({
headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' }, headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
user: { id: 'user-123', email: 'test@example.com' }, user: { id: 'user-123', email: 'test@example.com', role: 'user' },
body: { endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders body: { endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
}); });
}); });
it('throws if endpoint config is missing', async () => { it('throws if endpoint config is missing', async () => {
const { getCustomEndpointConfig } = require('~/server/services/Config'); const { getCustomEndpointConfig } = require('@librechat/api');
getCustomEndpointConfig.mockResolvedValueOnce(null); getCustomEndpointConfig.mockReturnValueOnce(null);
await expect( await expect(
initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true }), initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true }),
).rejects.toThrow('Config not found for the test-endpoint custom endpoint.'); ).rejects.toThrow('Config not found for the test-endpoint custom endpoint.');

View file

@ -1,11 +1,11 @@
const { Providers } = require('@librechat/agents'); const { Providers } = require('@librechat/agents');
const { EModelEndpoint } = require('librechat-data-provider'); const { EModelEndpoint } = require('librechat-data-provider');
const { getCustomEndpointConfig } = require('@librechat/api');
const initAnthropic = require('~/server/services/Endpoints/anthropic/initialize'); const initAnthropic = require('~/server/services/Endpoints/anthropic/initialize');
const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options'); const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options');
const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); const initOpenAI = require('~/server/services/Endpoints/openAI/initialize');
const initCustom = require('~/server/services/Endpoints/custom/initialize'); const initCustom = require('~/server/services/Endpoints/custom/initialize');
const initGoogle = require('~/server/services/Endpoints/google/initialize'); const initGoogle = require('~/server/services/Endpoints/google/initialize');
const { getCustomEndpointConfig } = require('~/server/services/Config');
/** Check if the provider is a known custom provider /** Check if the provider is a known custom provider
* @param {string | undefined} [provider] - The provider string * @param {string | undefined} [provider] - The provider string
@ -31,14 +31,16 @@ const providerConfigMap = {
/** /**
* Get the provider configuration and override endpoint based on the provider string * Get the provider configuration and override endpoint based on the provider string
* @param {string} provider - The provider string * @param {Object} params
* @returns {Promise<{ * @param {string} params.provider - The provider string
* getOptions: Function, * @param {AppConfig} params.appConfig - The application configuration
* @returns {{
* getOptions: (typeof providerConfigMap)[keyof typeof providerConfigMap],
* overrideProvider: string, * overrideProvider: string,
* customEndpointConfig?: TEndpoint * customEndpointConfig?: TEndpoint
* }>} * }}
*/ */
async function getProviderConfig(provider) { function getProviderConfig({ provider, appConfig }) {
let getOptions = providerConfigMap[provider]; let getOptions = providerConfigMap[provider];
let overrideProvider = provider; let overrideProvider = provider;
/** @type {TEndpoint | undefined} */ /** @type {TEndpoint | undefined} */
@ -48,7 +50,7 @@ async function getProviderConfig(provider) {
overrideProvider = provider.toLowerCase(); overrideProvider = provider.toLowerCase();
getOptions = providerConfigMap[overrideProvider]; getOptions = providerConfigMap[overrideProvider];
} else if (!getOptions) { } else if (!getOptions) {
customEndpointConfig = await getCustomEndpointConfig(provider); customEndpointConfig = getCustomEndpointConfig({ endpoint: provider, appConfig });
if (!customEndpointConfig) { if (!customEndpointConfig) {
throw new Error(`Provider ${provider} not supported`); throw new Error(`Provider ${provider} not supported`);
} }
@ -57,7 +59,7 @@ async function getProviderConfig(provider) {
} }
if (isKnownCustomProvider(overrideProvider) && !customEndpointConfig) { if (isKnownCustomProvider(overrideProvider) && !customEndpointConfig) {
customEndpointConfig = await getCustomEndpointConfig(provider); customEndpointConfig = getCustomEndpointConfig({ endpoint: provider, appConfig });
if (!customEndpointConfig) { if (!customEndpointConfig) {
throw new Error(`Provider ${provider} not supported`); throw new Error(`Provider ${provider} not supported`);
} }

View file

@ -0,0 +1,43 @@
import { EModelEndpoint, removeNullishValues } from 'librechat-data-provider';
import type { TCustomConfig, TEndpoint } from 'librechat-data-provider';
import type { AppConfig } from '~/types';
import { isEnabled, normalizeEndpointName } from '~/utils';
/**
* Retrieves the balance configuration object
* */
export function getBalanceConfig(appConfig?: AppConfig): Partial<TCustomConfig['balance']> | null {
const isLegacyEnabled = isEnabled(process.env.CHECK_BALANCE);
const startBalance = process.env.START_BALANCE;
/** @type {} */
const config: Partial<TCustomConfig['balance']> = removeNullishValues({
enabled: isLegacyEnabled,
startBalance: startBalance != null && startBalance ? parseInt(startBalance, 10) : undefined,
});
if (!appConfig) {
return config;
}
return { ...config, ...(appConfig?.['balance'] ?? {}) };
}
export const getCustomEndpointConfig = ({
endpoint,
appConfig,
}: {
endpoint: string | EModelEndpoint;
appConfig?: AppConfig;
}): Partial<TEndpoint> | undefined => {
if (!appConfig) {
throw new Error(`Config not found for the ${endpoint} custom endpoint.`);
}
const customEndpoints = appConfig.endpoints?.[EModelEndpoint.custom] ?? [];
return customEndpoints.find(
(endpointConfig) => normalizeEndpointName(endpointConfig.name) === endpoint,
);
};
export function hasCustomUserVars(appConfig?: AppConfig): boolean {
const mcpServers = appConfig?.mcpConfig;
return Object.values(mcpServers ?? {}).some((server) => server.customUserVars);
}

View file

@ -0,0 +1 @@
export * from './config';

View file

@ -1,3 +1,4 @@
export * from './app';
/* MCP */ /* MCP */
export * from './mcp/MCPManager'; export * from './mcp/MCPManager';
export * from './mcp/oauth'; export * from './mcp/oauth';