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

View file

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

View file

@ -1,6 +1,6 @@
const { logger } = require('@librechat/data-schemas');
const { validateAgentModel } = require('@librechat/api');
const { createContentAggregator } = require('@librechat/agents');
const { validateAgentModel, getCustomEndpointConfig } = require('@librechat/api');
const {
Constants,
EModelEndpoint,
@ -11,11 +11,11 @@ const {
createToolEndCallback,
getDefaultHandlers,
} = require('~/server/controllers/agents/callbacks');
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
const { getModelsConfig } = require('~/server/controllers/ModelController');
const { loadAgentTools } = require('~/server/services/ToolService');
const AgentClient = require('~/server/controllers/agents/client');
const { getAppConfig } = require('~/server/services/Config');
const { getAgent } = require('~/models/Agent');
const { logViolation } = require('~/cache');
@ -147,7 +147,10 @@ const initializeClient = async ({ req, res, endpointOption }) => {
let endpointConfig = appConfig.endpoints?.[primaryConfig.endpoint];
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
try {
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);
endpointConfig = getCustomEndpointConfig({
endpoint: primaryConfig.endpoint,
appConfig,
});
} catch (err) {
logger.error(
'[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 {
CacheKeys,
ErrorTypes,
@ -5,13 +8,10 @@ const {
FetchTokenConfig,
extractEnvVariable,
} = require('librechat-data-provider');
const { Providers } = require('@librechat/agents');
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { fetchModels } = require('~/server/services/ModelService');
const { getAppConfig } = require('~/server/services/Config');
const OpenAIClient = require('~/app/clients/OpenAIClient');
const { isUserProvided } = require('~/server/utils');
const getLogStores = require('~/cache/getLogStores');
const { PROXY } = process.env;
@ -21,7 +21,10 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
const { key: expiresAt } = req.body;
const endpoint = overrideEndpoint ?? req.body.endpoint;
const endpointConfig = await getCustomEndpointConfig(endpoint);
const endpointConfig = getCustomEndpointConfig({
endpoint,
appConfig,
});
if (!endpointConfig) {
throw new Error(`Config not found for the ${endpoint} custom endpoint.`);
}

View file

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

View file

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