🪦 refactor: Remove Legacy Code (#10533)

* 🗑️ chore: Remove unused Legacy Provider clients and related helpers

* Deleted OpenAIClient and GoogleClient files along with their associated tests.
* Removed references to these clients in the clients index file.
* Cleaned up typedefs by removing the OpenAISpecClient export.
* Updated chat controllers to use the OpenAI SDK directly instead of the removed client classes.

* chore/remove-openapi-specs

* 🗑️ chore: Remove unused mergeSort and misc utility functions

* Deleted mergeSort.js and misc.js files as they are no longer needed.
* Removed references to cleanUpPrimaryKeyValue in messages.js and adjusted related logic.
* Updated mongoMeili.ts to eliminate local implementations of removed functions.

* chore: remove legacy endpoints

* chore: remove all plugins endpoint related code

* chore: remove unused prompt handling code and clean up imports

* Deleted handleInputs.js and instructions.js files as they are no longer needed.
* Removed references to these files in the prompts index.js.
* Updated docker-compose.yml to simplify reverse proxy configuration.

* chore: remove unused LightningIcon import from Icons.tsx

* chore: clean up translation.json by removing deprecated and unused keys

* chore: update Jest configuration and remove unused mock file

    * Simplified the setupFiles array in jest.config.js by removing the fetchEventSource mock.
    * Deleted the fetchEventSource.js mock file as it is no longer needed.

* fix: simplify endpoint type check in Landing and ConversationStarters components

    * Updated the endpoint type check to use strict equality for better clarity and performance.
    * Ensured consistency in the handling of the azureOpenAI endpoint across both components.

* chore: remove unused dependencies from package.json and package-lock.json

* chore: remove legacy EditController, associated routes and imports

* chore: update banResponse logic to refine request handling for banned users

* chore: remove unused validateEndpoint middleware and its references

* chore: remove unused 'res' parameter from initializeClient in multiple endpoint files

* chore: remove unused 'isSmallScreen' prop from BookmarkNav and NewChat components; clean up imports in ArchivedChatsTable and useSetIndexOptions hooks; enhance localization in PromptVersions

* chore: remove unused import of Constants and TMessage from MobileNav; retain only necessary QueryKeys import

* chore: remove unused TResPlugin type and related references; clean up imports in types and schemas
This commit is contained in:
Danny Avila 2025-11-25 15:20:07 -05:00
parent 3e8cb01f84
commit 63926cb874
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
161 changed files with 258 additions and 10514 deletions

View file

@ -1,9 +1,8 @@
const { getLLMConfig } = require('@librechat/api');
const { EModelEndpoint } = require('librechat-data-provider');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const AnthropicClient = require('~/app/clients/AnthropicClient');
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
const initializeClient = async ({ req, endpointOption, overrideModel }) => {
const appConfig = req.config;
const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env;
const expiresAt = req.body.key;
@ -36,35 +35,19 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
clientOptions._lc_stream_delay = allConfig.streamRate;
}
if (optionsOnly) {
clientOptions = Object.assign(
{
proxy: PROXY ?? null,
reverseProxyUrl: ANTHROPIC_REVERSE_PROXY ?? null,
modelOptions: endpointOption?.model_parameters ?? {},
},
clientOptions,
);
if (overrideModel) {
clientOptions.modelOptions.model = overrideModel;
}
clientOptions.modelOptions.user = req.user.id;
return getLLMConfig(anthropicApiKey, clientOptions);
clientOptions = Object.assign(
{
proxy: PROXY ?? null,
reverseProxyUrl: ANTHROPIC_REVERSE_PROXY ?? null,
modelOptions: endpointOption?.model_parameters ?? {},
},
clientOptions,
);
if (overrideModel) {
clientOptions.modelOptions.model = overrideModel;
}
const client = new AnthropicClient(anthropicApiKey, {
req,
res,
reverseProxyUrl: ANTHROPIC_REVERSE_PROXY ?? null,
proxy: PROXY ?? null,
...clientOptions,
...endpointOption,
});
return {
client,
anthropicApiKey,
};
clientOptions.modelOptions.user = req.user.id;
return getLLMConfig(anthropicApiKey, clientOptions);
};
module.exports = initializeClient;

View file

@ -1,15 +1,14 @@
const OpenAI = require('openai');
const { ProxyAgent } = require('undici');
const { isUserProvided } = require('@librechat/api');
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
const {
getUserKeyValues,
getUserKeyExpiry,
checkUserKeyExpiry,
} = require('~/server/services/UserService');
const OAIClient = require('~/app/clients/OpenAIClient');
const { isUserProvided } = require('~/server/utils');
const initializeClient = async ({ req, res, endpointOption, version, initAppClient = false }) => {
const initializeClient = async ({ req, res, version }) => {
const { PROXY, OPENAI_ORGANIZATION, ASSISTANTS_API_KEY, ASSISTANTS_BASE_URL } = process.env;
const userProvidesKey = isUserProvided(ASSISTANTS_API_KEY);
@ -34,14 +33,6 @@ const initializeClient = async ({ req, res, endpointOption, version, initAppClie
},
};
const clientOptions = {
reverseProxyUrl: baseURL ?? null,
proxy: PROXY ?? null,
req,
res,
...endpointOption,
};
if (userProvidesKey & !apiKey) {
throw new Error(
JSON.stringify({
@ -78,15 +69,6 @@ const initializeClient = async ({ req, res, endpointOption, version, initAppClie
openai.req = req;
openai.res = res;
if (endpointOption && initAppClient) {
const client = new OAIClient(apiKey, clientOptions);
return {
client,
openai,
openAIApiKey: apiKey,
};
}
return {
openai,
openAIApiKey: apiKey,

View file

@ -1,113 +0,0 @@
// const OpenAI = require('openai');
const { ProxyAgent } = require('undici');
const { ErrorTypes } = require('librechat-data-provider');
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
const initializeClient = require('./initalize');
// const { OpenAIClient } = require('~/app');
jest.mock('~/server/services/UserService', () => ({
getUserKey: jest.fn(),
getUserKeyExpiry: jest.fn(),
getUserKeyValues: jest.fn(),
checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
}));
const today = new Date();
const tenDaysFromToday = new Date(today.setDate(today.getDate() + 10));
const isoString = tenDaysFromToday.toISOString();
describe('initializeClient', () => {
// Set up environment variables
const originalEnvironment = process.env;
const app = {
locals: {},
};
beforeEach(() => {
jest.resetModules(); // Clears the cache
process.env = { ...originalEnvironment }; // Make a copy
});
afterAll(() => {
process.env = originalEnvironment; // Restore original env vars
});
test('initializes OpenAI client with default API key and URL', async () => {
process.env.ASSISTANTS_API_KEY = 'default-api-key';
process.env.ASSISTANTS_BASE_URL = 'https://default.api.url';
// Assuming 'isUserProvided' to return false for this test case
jest.mock('~/server/utils', () => ({
isUserProvided: jest.fn().mockReturnValueOnce(false),
}));
const req = { user: { id: 'user123' }, app };
const res = {};
const { openai, openAIApiKey } = await initializeClient({ req, res });
expect(openai.apiKey).toBe('default-api-key');
expect(openAIApiKey).toBe('default-api-key');
expect(openai.baseURL).toBe('https://default.api.url');
});
test('initializes OpenAI client with user-provided API key and URL', async () => {
process.env.ASSISTANTS_API_KEY = 'user_provided';
process.env.ASSISTANTS_BASE_URL = 'user_provided';
getUserKeyValues.mockResolvedValue({ apiKey: 'user-api-key', baseURL: 'https://user.api.url' });
getUserKeyExpiry.mockResolvedValue(isoString);
const req = { user: { id: 'user123' }, app };
const res = {};
const { openai, openAIApiKey } = await initializeClient({ req, res });
expect(openAIApiKey).toBe('user-api-key');
expect(openai.apiKey).toBe('user-api-key');
expect(openai.baseURL).toBe('https://user.api.url');
});
test('throws error for invalid JSON in user-provided values', async () => {
process.env.ASSISTANTS_API_KEY = 'user_provided';
getUserKey.mockResolvedValue('invalid-json');
getUserKeyExpiry.mockResolvedValue(isoString);
getUserKeyValues.mockImplementation(() => {
let userValues = getUserKey();
try {
userValues = JSON.parse(userValues);
} catch (e) {
throw new Error(
JSON.stringify({
type: ErrorTypes.INVALID_USER_KEY,
}),
);
}
return userValues;
});
const req = { user: { id: 'user123' } };
const res = {};
await expect(initializeClient({ req, res })).rejects.toThrow(/invalid_user_key/);
});
test('throws error if API key is not provided', async () => {
delete process.env.ASSISTANTS_API_KEY; // Simulate missing API key
const req = { user: { id: 'user123' }, app };
const res = {};
await expect(initializeClient({ req, res })).rejects.toThrow(/Assistants API key not/);
});
test('initializes OpenAI client with proxy configuration', async () => {
process.env.ASSISTANTS_API_KEY = 'test-key';
process.env.PROXY = 'http://proxy.server';
const req = { user: { id: 'user123' }, app };
const res = {};
const { openai } = await initializeClient({ req, res });
expect(openai.fetchOptions).toBeDefined();
expect(openai.fetchOptions.dispatcher).toBeInstanceOf(ProxyAgent);
});
});

View file

@ -1,32 +1,84 @@
const { isEnabled } = require('@librechat/api');
const { isEnabled, sanitizeTitle } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { CacheKeys } = require('librechat-data-provider');
const { saveConvo } = require('~/models/Conversation');
const getLogStores = require('~/cache/getLogStores');
const initializeClient = require('./initalize');
const addTitle = async (req, { text, responseText, conversationId, client }) => {
/**
* Generates a conversation title using OpenAI SDK
* @param {Object} params
* @param {OpenAI} params.openai - The OpenAI SDK client instance
* @param {string} params.text - User's message text
* @param {string} params.responseText - Assistant's response text
* @returns {Promise<string>}
*/
const generateTitle = async ({ openai, text, responseText }) => {
const titlePrompt = `Please generate a concise title (max 40 characters) for a conversation that starts with:
User: ${text}
Assistant: ${responseText}
Title:`;
const completion = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'user',
content: titlePrompt,
},
],
temperature: 0.7,
max_tokens: 20,
});
const title = completion.choices[0]?.message?.content?.trim() || 'New conversation';
return sanitizeTitle(title);
};
/**
* Adds a title to a conversation asynchronously
* @param {ServerRequest} req
* @param {Object} params
* @param {string} params.text - User's message text
* @param {string} params.responseText - Assistant's response text
* @param {string} params.conversationId - Conversation ID
*/
const addTitle = async (req, { text, responseText, conversationId }) => {
const { TITLE_CONVO = 'true' } = process.env ?? {};
if (!isEnabled(TITLE_CONVO)) {
return;
}
if (client.options.titleConvo === false) {
return;
}
const titleCache = getLogStores(CacheKeys.GEN_TITLE);
const key = `${req.user.id}-${conversationId}`;
const title = await client.titleConvo({ text, conversationId, responseText });
await titleCache.set(key, title, 120000);
try {
const { openai } = await initializeClient({ req });
const title = await generateTitle({ openai, text, responseText });
await titleCache.set(key, title, 120000);
await saveConvo(
req,
{
conversationId,
title,
},
{ context: 'api/server/services/Endpoints/assistants/addTitle.js' },
);
await saveConvo(
req,
{
conversationId,
title,
},
{ context: 'api/server/services/Endpoints/assistants/addTitle.js' },
);
} catch (error) {
logger.error('[addTitle] Error generating title:', error);
const fallbackTitle = text.length > 40 ? text.substring(0, 37) + '...' : text;
await titleCache.set(key, fallbackTitle, 120000);
await saveConvo(
req,
{
conversationId,
title: fallbackTitle,
},
{ context: 'api/server/services/Endpoints/assistants/addTitle.js' },
);
}
};
module.exports = addTitle;

View file

@ -7,7 +7,6 @@ const {
getUserKeyValues,
getUserKeyExpiry,
} = require('~/server/services/UserService');
const OAIClient = require('~/app/clients/OpenAIClient');
class Files {
constructor(client) {
@ -184,15 +183,6 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
openai.locals = { ...(openai.locals ?? {}), azureOptions };
}
if (endpointOption && initAppClient) {
const client = new OAIClient(apiKey, clientOptions);
return {
client,
openai,
openAIApiKey: apiKey,
};
}
return {
openai,
openAIApiKey: apiKey,

View file

@ -1,134 +0,0 @@
// const OpenAI = require('openai');
const { ProxyAgent } = require('undici');
const { ErrorTypes, EModelEndpoint } = require('librechat-data-provider');
const { getUserKey, getUserKeyExpiry, getUserKeyValues } = require('~/server/services/UserService');
const initializeClient = require('./initialize');
// const { OpenAIClient } = require('~/app');
jest.mock('~/server/services/UserService', () => ({
getUserKey: jest.fn(),
getUserKeyExpiry: jest.fn(),
getUserKeyValues: jest.fn(),
checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
}));
// Config is now passed via req.config, not getAppConfig
const today = new Date();
const tenDaysFromToday = new Date(today.setDate(today.getDate() + 10));
const isoString = tenDaysFromToday.toISOString();
describe('initializeClient', () => {
// Set up environment variables
const originalEnvironment = process.env;
const app = {
locals: {},
};
beforeEach(() => {
jest.resetModules(); // Clears the cache
process.env = { ...originalEnvironment }; // Make a copy
});
afterAll(() => {
process.env = originalEnvironment; // Restore original env vars
});
test('initializes OpenAI client with default API key and URL', async () => {
process.env.AZURE_ASSISTANTS_API_KEY = 'default-api-key';
process.env.AZURE_ASSISTANTS_BASE_URL = 'https://default.api.url';
// Assuming 'isUserProvided' to return false for this test case
jest.mock('~/server/utils', () => ({
isUserProvided: jest.fn().mockReturnValueOnce(false),
}));
const req = {
user: { id: 'user123' },
app,
config: { endpoints: { [EModelEndpoint.azureOpenAI]: {} } },
};
const res = {};
const { openai, openAIApiKey } = await initializeClient({ req, res });
expect(openai.apiKey).toBe('default-api-key');
expect(openAIApiKey).toBe('default-api-key');
expect(openai.baseURL).toBe('https://default.api.url');
});
test('initializes OpenAI client with user-provided API key and URL', async () => {
process.env.AZURE_ASSISTANTS_API_KEY = 'user_provided';
process.env.AZURE_ASSISTANTS_BASE_URL = 'user_provided';
getUserKeyValues.mockResolvedValue({ apiKey: 'user-api-key', baseURL: 'https://user.api.url' });
getUserKeyExpiry.mockResolvedValue(isoString);
const req = {
user: { id: 'user123' },
app,
config: { endpoints: { [EModelEndpoint.azureOpenAI]: {} } },
};
const res = {};
const { openai, openAIApiKey } = await initializeClient({ req, res });
expect(openAIApiKey).toBe('user-api-key');
expect(openai.apiKey).toBe('user-api-key');
expect(openai.baseURL).toBe('https://user.api.url');
});
test('throws error for invalid JSON in user-provided values', async () => {
process.env.AZURE_ASSISTANTS_API_KEY = 'user_provided';
getUserKey.mockResolvedValue('invalid-json');
getUserKeyExpiry.mockResolvedValue(isoString);
getUserKeyValues.mockImplementation(() => {
let userValues = getUserKey();
try {
userValues = JSON.parse(userValues);
} catch {
throw new Error(
JSON.stringify({
type: ErrorTypes.INVALID_USER_KEY,
}),
);
}
return userValues;
});
const req = {
user: { id: 'user123' },
config: { endpoints: { [EModelEndpoint.azureOpenAI]: {} } },
};
const res = {};
await expect(initializeClient({ req, res })).rejects.toThrow(/invalid_user_key/);
});
test('throws error if API key is not provided', async () => {
delete process.env.AZURE_ASSISTANTS_API_KEY; // Simulate missing API key
const req = {
user: { id: 'user123' },
app,
config: { endpoints: { [EModelEndpoint.azureOpenAI]: {} } },
};
const res = {};
await expect(initializeClient({ req, res })).rejects.toThrow(/Assistants API key not/);
});
test('initializes OpenAI client with proxy configuration', async () => {
process.env.AZURE_ASSISTANTS_API_KEY = 'test-key';
process.env.PROXY = 'http://proxy.server';
const req = {
user: { id: 'user123' },
app,
config: { endpoints: { [EModelEndpoint.azureOpenAI]: {} } },
};
const res = {};
const { openai } = await initializeClient({ req, res });
expect(openai.fetchOptions).toBeDefined();
expect(openai.fetchOptions.dispatcher).toBeInstanceOf(ProxyAgent);
});
});

View file

@ -8,12 +8,11 @@ const {
} = require('librechat-data-provider');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const { fetchModels } = require('~/server/services/ModelService');
const OpenAIClient = require('~/app/clients/OpenAIClient');
const getLogStores = require('~/cache/getLogStores');
const { PROXY } = process.env;
const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => {
const initializeClient = async ({ req, endpointOption, overrideEndpoint }) => {
const appConfig = req.config;
const { key: expiresAt } = req.body;
const endpoint = overrideEndpoint ?? req.body.endpoint;
@ -120,38 +119,27 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
let clientOptions = {
reverseProxyUrl: baseURL ?? null,
proxy: PROXY ?? null,
req,
res,
...customOptions,
...endpointOption,
};
if (optionsOnly) {
const modelOptions = endpointOption?.model_parameters ?? {};
clientOptions = Object.assign(
{
modelOptions,
},
clientOptions,
);
clientOptions.modelOptions.user = req.user.id;
const options = getOpenAIConfig(apiKey, clientOptions, endpoint);
if (options != null) {
options.useLegacyContent = true;
options.endpointTokenConfig = endpointTokenConfig;
}
if (!clientOptions.streamRate) {
return options;
}
options.llmConfig._lc_stream_delay = clientOptions.streamRate;
return options;
const modelOptions = endpointOption?.model_parameters ?? {};
clientOptions = Object.assign(
{
modelOptions,
},
clientOptions,
);
clientOptions.modelOptions.user = req.user.id;
const options = getOpenAIConfig(apiKey, clientOptions, endpoint);
if (options != null) {
options.useLegacyContent = true;
options.endpointTokenConfig = endpointTokenConfig;
}
const client = new OpenAIClient(apiKey, clientOptions);
return {
client,
openAIApiKey: apiKey,
};
if (clientOptions.streamRate) {
options.llmConfig._lc_stream_delay = clientOptions.streamRate;
}
return options;
};
module.exports = initializeClient;

View file

@ -1,106 +0,0 @@
const initializeClient = require('./initialize');
jest.mock('@librechat/api', () => ({
...jest.requireActual('@librechat/api'),
resolveHeaders: jest.fn(),
getOpenAIConfig: jest.fn(),
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', () => ({
getUserKeyValues: jest.fn(),
checkUserKeyExpiry: jest.fn(),
}));
// Config is now passed via req.config, not getAppConfig
jest.mock('~/server/services/ModelService', () => ({
fetchModels: jest.fn(),
}));
jest.mock('~/app/clients/OpenAIClient', () => {
return jest.fn().mockImplementation(() => ({
options: {},
}));
});
jest.mock('~/cache/getLogStores', () =>
jest.fn().mockReturnValue({
get: jest.fn(),
}),
);
describe('custom/initializeClient', () => {
const mockRequest = {
body: { endpoint: 'test-endpoint' },
user: { id: 'user-123', email: 'test@example.com', role: 'user' },
app: { locals: {} },
config: {
endpoints: {
all: {
streamRate: 25,
},
},
},
};
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,
llmConfig: {
callbacks: [],
},
});
});
it('stores original template headers for deferred resolution', async () => {
/**
* Note: Request-based Header Resolution is deferred until right before LLM request is made
* in the OpenAIClient or AgentClient, not during initialization.
* This test verifies that the initialize function completes successfully with optionsOnly flag,
* and that headers are passed through to be resolved later during the actual LLM request.
*/
const result = await initializeClient({
req: mockRequest,
res: mockResponse,
optionsOnly: true,
});
// Verify that options are returned for later use
expect(result).toBeDefined();
expect(result).toHaveProperty('useLegacyContent', true);
});
it('throws if endpoint config is missing', async () => {
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.');
});
it('throws if user is missing', async () => {
await expect(
initializeClient({
req: { ...mockRequest, user: undefined },
res: mockResponse,
optionsOnly: true,
}),
).rejects.toThrow("Cannot read properties of undefined (reading 'id')");
});
});

View file

@ -2,9 +2,8 @@ const path = require('path');
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { GoogleClient } = require('~/app');
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
const initializeClient = async ({ req, endpointOption, overrideModel }) => {
const { GOOGLE_KEY, GOOGLE_REVERSE_PROXY, GOOGLE_AUTH_HEADER, PROXY } = process.env;
const isUserProvided = GOOGLE_KEY === 'user_provided';
const { key: expiresAt } = req.body;
@ -62,8 +61,6 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
}
clientOptions = {
req,
res,
reverseProxyUrl: GOOGLE_REVERSE_PROXY ?? null,
authHeader: isEnabled(GOOGLE_AUTH_HEADER) ?? null,
proxy: PROXY ?? null,
@ -71,25 +68,16 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
...endpointOption,
};
if (optionsOnly) {
clientOptions = Object.assign(
{
modelOptions: endpointOption?.model_parameters ?? {},
},
clientOptions,
);
if (overrideModel) {
clientOptions.modelOptions.model = overrideModel;
}
return getGoogleConfig(credentials, clientOptions);
clientOptions = Object.assign(
{
modelOptions: endpointOption?.model_parameters ?? {},
},
clientOptions,
);
if (overrideModel) {
clientOptions.modelOptions.model = overrideModel;
}
const client = new GoogleClient(credentials, clientOptions);
return {
client,
credentials,
};
return getGoogleConfig(credentials, clientOptions);
};
module.exports = initializeClient;

View file

@ -1,101 +0,0 @@
// file deepcode ignore HardcodedNonCryptoSecret: No hardcoded secrets
const { getUserKey } = require('~/server/services/UserService');
const initializeClient = require('./initialize');
const { GoogleClient } = require('~/app');
jest.mock('~/server/services/UserService', () => ({
checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
getUserKey: jest.fn().mockImplementation(() => ({})),
}));
// Config is now passed via req.config, not getAppConfig
const app = { locals: {} };
describe('google/initializeClient', () => {
afterEach(() => {
jest.clearAllMocks();
});
test('should initialize GoogleClient with user-provided credentials', async () => {
process.env.GOOGLE_KEY = 'user_provided';
process.env.GOOGLE_REVERSE_PROXY = 'http://reverse.proxy';
process.env.PROXY = 'http://proxy';
const expiresAt = new Date(Date.now() + 60000).toISOString();
const req = {
body: { key: expiresAt },
user: { id: '123' },
app,
config: {
endpoints: {
all: {},
google: {},
},
},
};
const res = {};
const endpointOption = { modelOptions: { model: 'default-model' } };
const { client, credentials } = await initializeClient({ req, res, endpointOption });
expect(getUserKey).toHaveBeenCalledWith({ userId: '123', name: 'google' });
expect(client).toBeInstanceOf(GoogleClient);
expect(client.options.reverseProxyUrl).toBe('http://reverse.proxy');
expect(client.options.proxy).toBe('http://proxy');
expect(credentials).toEqual({});
});
test('should initialize GoogleClient with service key credentials', async () => {
process.env.GOOGLE_KEY = 'service_key';
process.env.GOOGLE_REVERSE_PROXY = 'http://reverse.proxy';
process.env.PROXY = 'http://proxy';
const req = {
body: { key: null },
user: { id: '123' },
app,
config: {
endpoints: {
all: {},
google: {},
},
},
};
const res = {};
const endpointOption = { modelOptions: { model: 'default-model' } };
const { client, credentials } = await initializeClient({ req, res, endpointOption });
expect(client).toBeInstanceOf(GoogleClient);
expect(client.options.reverseProxyUrl).toBe('http://reverse.proxy');
expect(client.options.proxy).toBe('http://proxy');
expect(credentials).toEqual({
GOOGLE_SERVICE_KEY: {},
GOOGLE_API_KEY: 'service_key',
});
});
test('should handle expired user-provided key', async () => {
process.env.GOOGLE_KEY = 'user_provided';
const expiresAt = new Date(Date.now() - 10000).toISOString(); // Expired
const req = {
body: { key: expiresAt },
user: { id: '123' },
app,
config: {
endpoints: {
all: {},
google: {},
},
},
};
const res = {};
const endpointOption = { modelOptions: { model: 'default-model' } };
await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow(
/expired_user_key/,
);
});
});

View file

@ -7,16 +7,8 @@ const {
getAzureCredentials,
} = require('@librechat/api');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const OpenAIClient = require('~/app/clients/OpenAIClient');
const initializeClient = async ({
req,
res,
endpointOption,
optionsOnly,
overrideEndpoint,
overrideModel,
}) => {
const initializeClient = async ({ req, endpointOption, overrideEndpoint, overrideModel }) => {
const appConfig = req.config;
const {
PROXY,
@ -137,28 +129,19 @@ const initializeClient = async ({
throw new Error(`${endpoint} API Key not provided.`);
}
if (optionsOnly) {
const modelOptions = endpointOption?.model_parameters ?? {};
modelOptions.model = modelName;
clientOptions = Object.assign({ modelOptions }, clientOptions);
clientOptions.modelOptions.user = req.user.id;
const options = getOpenAIConfig(apiKey, clientOptions, endpoint);
if (options != null && serverless === true) {
options.useLegacyContent = true;
}
const streamRate = clientOptions.streamRate;
if (!streamRate) {
return options;
}
options.llmConfig._lc_stream_delay = streamRate;
return options;
const modelOptions = endpointOption?.model_parameters ?? {};
modelOptions.model = modelName;
clientOptions = Object.assign({ modelOptions }, clientOptions);
clientOptions.modelOptions.user = req.user.id;
const options = getOpenAIConfig(apiKey, clientOptions, endpoint);
if (options != null && serverless === true) {
options.useLegacyContent = true;
}
const client = new OpenAIClient(apiKey, Object.assign({ req, res }, clientOptions));
return {
client,
openAIApiKey: apiKey,
};
const streamRate = clientOptions.streamRate;
if (streamRate) {
options.llmConfig._lc_stream_delay = streamRate;
}
return options;
};
module.exports = initializeClient;

View file

@ -1,431 +0,0 @@
jest.mock('~/cache/getLogStores', () => ({
getLogStores: jest.fn().mockReturnValue({
get: jest.fn().mockResolvedValue({
openAI: { apiKey: 'test-key' },
}),
set: jest.fn(),
delete: jest.fn(),
}),
}));
const { EModelEndpoint, ErrorTypes, validateAzureGroups } = require('librechat-data-provider');
const { getUserKey, getUserKeyValues } = require('~/server/services/UserService');
const initializeClient = require('./initialize');
const { OpenAIClient } = require('~/app');
// Mock getUserKey since it's the only function we want to mock
jest.mock('~/server/services/UserService', () => ({
getUserKey: jest.fn(),
getUserKeyValues: jest.fn(),
checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
}));
const mockAppConfig = {
endpoints: {
openAI: {
apiKey: 'test-key',
},
azureOpenAI: {
apiKey: 'test-azure-key',
modelNames: ['gpt-4-vision-preview', 'gpt-3.5-turbo', 'gpt-4'],
modelGroupMap: {
'gpt-4-vision-preview': {
group: 'librechat-westus',
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
},
groupMap: {
'librechat-westus': {
apiKey: 'WESTUS_API_KEY',
instanceName: 'librechat-westus',
version: '2023-12-01-preview',
models: {
'gpt-4-vision-preview': {
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
},
},
},
},
},
};
describe('initializeClient', () => {
// Set up environment variables
const originalEnvironment = process.env;
const app = {
locals: {},
};
const validAzureConfigs = [
{
group: 'librechat-westus',
apiKey: 'WESTUS_API_KEY',
instanceName: 'librechat-westus',
version: '2023-12-01-preview',
models: {
'gpt-4-vision-preview': {
deploymentName: 'gpt-4-vision-preview',
version: '2024-02-15-preview',
},
'gpt-3.5-turbo': {
deploymentName: 'gpt-35-turbo',
},
'gpt-3.5-turbo-1106': {
deploymentName: 'gpt-35-turbo-1106',
},
'gpt-4': {
deploymentName: 'gpt-4',
},
'gpt-4-1106-preview': {
deploymentName: 'gpt-4-1106-preview',
},
},
},
{
group: 'librechat-eastus',
apiKey: 'EASTUS_API_KEY',
instanceName: 'librechat-eastus',
deploymentName: 'gpt-4-turbo',
version: '2024-02-15-preview',
models: {
'gpt-4-turbo': true,
},
baseURL: 'https://eastus.example.com',
additionalHeaders: {
'x-api-key': 'x-api-key-value',
},
},
{
group: 'mistral-inference',
apiKey: 'AZURE_MISTRAL_API_KEY',
baseURL:
'https://Mistral-large-vnpet-serverless.region.inference.ai.azure.com/v1/chat/completions',
serverless: true,
models: {
'mistral-large': true,
},
},
{
group: 'llama-70b-chat',
apiKey: 'AZURE_LLAMA2_70B_API_KEY',
baseURL:
'https://Llama-2-70b-chat-qmvyb-serverless.region.inference.ai.azure.com/v1/chat/completions',
serverless: true,
models: {
'llama-70b-chat': true,
},
},
];
const { modelNames } = validateAzureGroups(validAzureConfigs);
beforeEach(() => {
jest.resetModules(); // Clears the cache
process.env = { ...originalEnvironment }; // Make a copy
});
afterAll(() => {
process.env = originalEnvironment; // Restore original env vars
});
test('should initialize client with OpenAI API key and default options', async () => {
process.env.OPENAI_API_KEY = 'test-openai-api-key';
process.env.DEBUG_OPENAI = 'false';
process.env.OPENAI_SUMMARIZE = 'false';
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const result = await initializeClient({ req, res, endpointOption });
expect(result.openAIApiKey).toBe('test-openai-api-key');
expect(result.client).toBeInstanceOf(OpenAIClient);
});
test('should initialize client with Azure credentials when endpoint is azureOpenAI', async () => {
process.env.AZURE_API_KEY = 'test-azure-api-key';
(process.env.AZURE_OPENAI_API_INSTANCE_NAME = 'some-value'),
(process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME = 'some-value'),
(process.env.AZURE_OPENAI_API_VERSION = 'some-value'),
(process.env.AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME = 'some-value'),
(process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME = 'some-value'),
(process.env.OPENAI_API_KEY = 'test-openai-api-key');
process.env.DEBUG_OPENAI = 'false';
process.env.OPENAI_SUMMARIZE = 'false';
const req = {
body: {
key: null,
endpoint: 'azureOpenAI',
model: 'gpt-4-vision-preview',
},
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.openAIApiKey).toBe('WESTUS_API_KEY');
expect(client.client).toBeInstanceOf(OpenAIClient);
});
test('should use the debug option when DEBUG_OPENAI is enabled', async () => {
process.env.OPENAI_API_KEY = 'test-openai-api-key';
process.env.DEBUG_OPENAI = 'true';
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.client.options.debug).toBe(true);
});
test('should set contextStrategy to summarize when OPENAI_SUMMARIZE is enabled', async () => {
process.env.OPENAI_API_KEY = 'test-openai-api-key';
process.env.OPENAI_SUMMARIZE = 'true';
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.client.options.contextStrategy).toBe('summarize');
});
test('should set reverseProxyUrl and proxy when they are provided in the environment', async () => {
process.env.OPENAI_API_KEY = 'test-openai-api-key';
process.env.OPENAI_REVERSE_PROXY = 'http://reverse.proxy';
process.env.PROXY = 'http://proxy';
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.client.options.reverseProxyUrl).toBe('http://reverse.proxy');
expect(client.client.options.proxy).toBe('http://proxy');
});
test('should throw an error if the user-provided key has expired', async () => {
process.env.OPENAI_API_KEY = 'user_provided';
process.env.AZURE_API_KEY = 'user_provided';
process.env.DEBUG_OPENAI = 'false';
process.env.OPENAI_SUMMARIZE = 'false';
const expiresAt = new Date(Date.now() - 10000).toISOString(); // Expired
const req = {
body: { key: expiresAt, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow(
/expired_user_key/,
);
});
test('should throw an error if no API keys are provided in the environment', async () => {
// Clear the environment variables for API keys
delete process.env.OPENAI_API_KEY;
delete process.env.AZURE_API_KEY;
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow(
`${EModelEndpoint.openAI} API Key not provided.`,
);
});
it('should handle user-provided keys and check expiry', async () => {
// Set up the req.body to simulate user-provided key scenario
const req = {
body: {
key: new Date(Date.now() + 10000).toISOString(),
endpoint: EModelEndpoint.openAI,
},
user: {
id: '123',
},
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
// Ensure the environment variable is set to 'user_provided' to match the isUserProvided condition
process.env.OPENAI_API_KEY = 'user_provided';
// Mock getUserKey to return the expected key
getUserKeyValues.mockResolvedValue({ apiKey: 'test-user-provided-openai-api-key' });
// Call the initializeClient function
const result = await initializeClient({ req, res, endpointOption });
// Assertions
expect(result.openAIApiKey).toBe('test-user-provided-openai-api-key');
});
test('should throw an error if the user-provided key is invalid', async () => {
const invalidKey = new Date(Date.now() - 100000).toISOString();
const req = {
body: { key: invalidKey, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
// Ensure the environment variable is set to 'user_provided' to match the isUserProvided condition
process.env.OPENAI_API_KEY = 'user_provided';
// Mock getUserKey to return an invalid key
getUserKey.mockResolvedValue(invalidKey);
await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow(
/expired_user_key/,
);
});
test('should throw an error when user-provided values are not valid JSON', async () => {
process.env.OPENAI_API_KEY = 'user_provided';
const req = {
body: { key: new Date(Date.now() + 10000).toISOString(), endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
// Mock getUserKey to return a non-JSON string
getUserKey.mockResolvedValue('not-a-json');
getUserKeyValues.mockImplementation(() => {
let userValues = getUserKey();
try {
userValues = JSON.parse(userValues);
} catch {
throw new Error(
JSON.stringify({
type: ErrorTypes.INVALID_USER_KEY,
}),
);
}
return userValues;
});
await expect(initializeClient({ req, res, endpointOption })).rejects.toThrow(
/invalid_user_key/,
);
});
test('should initialize client correctly for Azure OpenAI with valid configuration', async () => {
// Set up Azure environment variables
process.env.WESTUS_API_KEY = 'test-westus-key';
const req = {
body: {
key: null,
endpoint: EModelEndpoint.azureOpenAI,
model: modelNames[0],
},
user: { id: '123' },
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.client.options.azure).toBeDefined();
});
test('should initialize client with default options when certain env vars are not set', async () => {
delete process.env.DEBUG_OPENAI;
delete process.env.OPENAI_SUMMARIZE;
process.env.OPENAI_API_KEY = 'some-api-key';
const req = {
body: { key: null, endpoint: EModelEndpoint.openAI },
user: { id: '123' },
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
const client = await initializeClient({ req, res, endpointOption });
expect(client.client.options.debug).toBe(false);
expect(client.client.options.contextStrategy).toBe(null);
});
test('should correctly use user-provided apiKey and baseURL when provided', async () => {
process.env.OPENAI_API_KEY = 'user_provided';
process.env.OPENAI_REVERSE_PROXY = 'user_provided';
const req = {
body: {
key: new Date(Date.now() + 10000).toISOString(),
endpoint: EModelEndpoint.openAI,
},
user: {
id: '123',
},
app,
config: mockAppConfig,
};
const res = {};
const endpointOption = {};
getUserKeyValues.mockResolvedValue({
apiKey: 'test',
baseURL: 'https://user-provided-url.com',
});
const result = await initializeClient({ req, res, endpointOption });
expect(result.openAIApiKey).toBe('test');
expect(result.client.options.reverseProxyUrl).toBe('https://user-provided-url.com');
});
});