⚙️ perf: reduce first-load MongoDB round trips (#14101)

* perf(api): reduce first-load database round trips

* docs: move agent guidance to claude docs

* refactor(api): move message validation into api package

* fix(api): narrow active generation job lookup

* fix(api): preserve omitted source identity
This commit is contained in:
Ravi Kumar L 2026-07-06 15:36:34 +02:00 committed by GitHub
parent a0aa1f2b9d
commit 44d1275f36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 895 additions and 242 deletions

View file

@ -5,6 +5,7 @@ const {
needsRefresh,
MCPOAuthHandler,
MCPTokenStorage,
getAppConfigOptionsFromUser,
normalizeHttpError,
extractWebSearchEnvVars,
deleteAgentCheckpoints,
@ -59,13 +60,7 @@ const sanitizeUserForResponse = (user) => {
};
const getUserController = async (req, res) => {
const appConfig =
req.config ??
(await getAppConfig({
role: req.user?.role,
userId: req.user?.id,
tenantId: req.user?.tenantId,
}));
const appConfig = req.config ?? (await getAppConfig(getAppConfigOptionsFromUser(req.user)));
/** @type {IUser} */
const userData = sanitizeUserForResponse(req.user);
if (appConfig.fileStrategy === FileSources.s3 && userData.avatar) {
@ -203,13 +198,7 @@ const deleteUserMcpServers = async (userId) => {
};
const updateUserPluginsController = async (req, res) => {
const appConfig =
req.config ??
(await getAppConfig({
role: req.user?.role,
userId: req.user?.id,
tenantId: req.user?.tenantId,
}));
const appConfig = req.config ?? (await getAppConfig(getAppConfigOptionsFromUser(req.user)));
const { user } = req;
const { pluginKey, action, auth, isEntityTool } = req.body;
try {

View file

@ -40,6 +40,16 @@ jest.mock('@librechat/api', () => ({
},
normalizeHttpError: jest.fn((error) => error),
extractWebSearchEnvVars: jest.fn((params) => params.keys),
getAppConfigOptionsFromUser: jest.fn((user) => {
const hasSourceIdentity =
user != null && Object.prototype.hasOwnProperty.call(user, 'idOnTheSource');
return {
role: user?.role,
userId: user?.id,
idOnTheSource: user?.id && hasSourceIdentity ? (user.idOnTheSource ?? null) : undefined,
tenantId: user?.tenantId,
};
}),
needsRefresh: jest.fn(),
getNewS3URL: jest.fn(),
}));

View file

@ -3,9 +3,12 @@ jest.mock('~/models', () => ({
}));
jest.mock('@librechat/api', () => ({
createMessageRequestMiddleware:
jest.requireActual('@librechat/api').createMessageRequestMiddleware,
GenerationJobManager: {
getJob: jest.fn(),
},
isPendingActionStale: jest.fn(() => false),
}));
jest.mock('@librechat/data-schemas', () => ({

View file

@ -1,5 +1,5 @@
const { logger } = require('@librechat/data-schemas');
const { isEmailDomainAllowed } = require('@librechat/api');
const { getAppConfigOptionsFromUser, isEmailDomainAllowed } = require('@librechat/api');
const { getAppConfig } = require('~/server/services/Config');
/**
@ -16,11 +16,7 @@ const { getAppConfig } = require('~/server/services/Config');
const checkDomainAllowed = async (req, res, next) => {
try {
const email = req?.user?.email;
const appConfig = await getAppConfig({
role: req?.user?.role,
userId: req?.user?.id,
tenantId: req?.user?.tenantId,
});
const appConfig = await getAppConfig(getAppConfigOptionsFromUser(req?.user));
if (email && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) {
logger.error(`[Social Login] [Social Login not allowed] [Email: ${email}]`);

View file

@ -1,12 +1,10 @@
const { logger } = require('@librechat/data-schemas');
const { getAppConfigOptionsFromUser } = require('@librechat/api');
const { getAppConfig } = require('~/server/services/Config');
const configMiddleware = async (req, res, next) => {
try {
const userRole = req.user?.role;
const userId = req.user?.id;
const tenantId = req.user?.tenantId;
req.config = await getAppConfig({ role: userRole, userId, tenantId });
req.config = await getAppConfig(getAppConfigOptionsFromUser(req.user));
next();
} catch (error) {

View file

@ -3,6 +3,7 @@ const setTwoFactorTempUser = require('./setTwoFactorTempUser');
const validateRegistration = require('./validateRegistration');
const buildEndpointOption = require('./buildEndpointOption');
const validateMessageReq = require('./validateMessageReq');
const { prepareMessageRequestValidation, sendValidationResponse } = require('./messageValidation');
const checkDomainAllowed = require('./checkDomainAllowed');
const requireLocalAuth = require('./requireLocalAuth');
const canDeleteAccount = require('./canDeleteAccount');
@ -47,6 +48,8 @@ module.exports = {
configMiddleware,
checkDomainAllowed,
validateMessageReq,
sendValidationResponse,
prepareMessageRequestValidation,
buildEndpointOption,
validateRegistration,
validatePasswordReset,

View file

@ -0,0 +1,14 @@
const {
GenerationJobManager,
createMessageRequestMiddleware,
isPendingActionStale,
} = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { getConvo } = require('~/models');
module.exports = createMessageRequestMiddleware({
getConvo,
getJob: (conversationId) => GenerationJobManager.getJob(conversationId),
isPendingActionStale,
logger,
});

View file

@ -1,78 +1,3 @@
const { GenerationJobManager, isPendingActionStale } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { getConvo } = require('~/models');
function hasTenantMismatch(job, user) {
// Untenanted jobs remain readable by their owner for pre-multi-tenancy deployments.
return job.metadata?.tenantId != null && job.metadata.tenantId !== user.tenantId;
}
async function canReadActiveJobConversation(req, conversationId) {
if (req.method !== 'GET' || req.params?.messageId) {
return false;
}
let job;
try {
job = await GenerationJobManager.getJob(conversationId);
} catch (error) {
logger.warn(`[validateMessageReq] Active job lookup failed for ${conversationId}:`, error);
return false;
}
// A job paused for human review is still active (consistent with /chat/status
// and /chat/active), so a new-conversation run that pauses before its final
// save can still recover the prompt — but only while it has a live,
// resolvable prompt (missing/malformed or past-expiry reads as inactive).
const isActive =
!!job &&
(job.status === 'running' ||
(job.status === 'requires_action' &&
!isPendingActionStale({ pendingAction: job.metadata?.pendingAction })));
if (!isActive) {
return false;
}
return job.metadata?.userId === req.user.id && !hasTenantMismatch(job, req.user);
}
// Middleware to validate conversationId and user relationship
const validateMessageReq = async (req, res, next) => {
const body = req.body ?? {};
const paramConversationId = req.params?.conversationId;
const bodyConversationId = body.conversationId;
const nestedConversationId = body.message?.conversationId;
if (
(paramConversationId &&
((bodyConversationId && paramConversationId !== bodyConversationId) ||
(nestedConversationId && paramConversationId !== nestedConversationId))) ||
(bodyConversationId && nestedConversationId && bodyConversationId !== nestedConversationId)
) {
return res.status(400).json({ error: 'Conversation ID mismatch' });
}
const conversationId = paramConversationId || bodyConversationId || nestedConversationId;
if (conversationId === 'new') {
return res.status(200).send([]);
}
const conversation = await getConvo(req.user.id, conversationId);
if (!conversation) {
if (await canReadActiveJobConversation(req, conversationId)) {
return next();
}
return res.status(404).json({ error: 'Conversation not found' });
}
if (conversation.user !== req.user.id) {
return res.status(403).json({ error: 'User not authorized for this conversation' });
}
next();
};
const { validateMessageReq } = require('./messageValidation');
module.exports = validateMessageReq;

View file

@ -290,6 +290,7 @@ describe('GET /api/config', () => {
expect(mockGetAppConfig).toHaveBeenCalledWith({
role: 'USER',
userId: 'user123',
idOnTheSource: undefined,
tenantId: 'fallback-tenant',
});
});
@ -304,6 +305,7 @@ describe('GET /api/config', () => {
expect(mockGetAppConfig).toHaveBeenCalledWith({
role: 'USER',
userId: 'user123',
idOnTheSource: undefined,
tenantId: 'user-tenant',
});
});

View file

@ -48,11 +48,31 @@ jest.mock('~/server/services/Artifacts/update', () => ({
jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next());
jest.mock('~/server/middleware', () => ({
requireJwtAuth: (req, res, next) => next(),
validateMessageReq: (req, res, next) => next(),
configMiddleware: (req, res, next) => next(),
}));
jest.mock('~/server/middleware', () => {
const validateMessageReq = jest.fn((req, res, next) => next());
const prepareMessageRequestValidation = jest.fn((req, res, next) => {
req.messageRequestValidation = {
conversationId: 'convo-1',
shouldFetchMessages: true,
promise: Promise.resolve({ ok: true }),
};
next();
});
const sendValidationResponse = jest.fn((res, result) => {
if (result.send) {
return res.status(result.status).send(result.body);
}
return res.status(result.status).json(result.body);
});
return {
requireJwtAuth: (req, res, next) => next(),
validateMessageReq,
sendValidationResponse,
prepareMessageRequestValidation,
configMiddleware: (req, res, next) => next(),
};
});
jest.mock('~/db/models', () => ({
Message: {
@ -200,88 +220,3 @@ describe('DELETE /:conversationId/:messageId route handler', () => {
expect(response.body).toEqual({ error: 'Internal server error' });
});
});
describe('message route conversation ownership filters', () => {
let app;
const { getMessages, saveConvo, saveMessage } = require('~/models');
const authenticatedUserId = 'user-owner-123';
beforeAll(() => {
const messagesRouter = require('../messages');
app = express();
app.use(express.json());
app.use((req, res, next) => {
req.user = { id: authenticatedUserId };
next();
});
app.use('/api/messages', messagesRouter);
});
beforeEach(() => {
jest.clearAllMocks();
});
it('should save POST messages with the validated URL conversationId', async () => {
const urlConversationId = '11111111-1111-4111-8111-111111111111';
const bodyConversationId = '22222222-2222-4222-8222-222222222222';
const savedMessage = {
messageId: 'message-1',
conversationId: urlConversationId,
text: 'hello',
user: authenticatedUserId,
};
saveMessage.mockResolvedValue(savedMessage);
saveConvo.mockResolvedValue({ conversationId: urlConversationId });
const response = await request(app).post(`/api/messages/${urlConversationId}`).send({
messageId: savedMessage.messageId,
conversationId: bodyConversationId,
text: savedMessage.text,
});
expect(response.status).toBe(201);
expect(saveMessage).toHaveBeenCalledWith(
expect.objectContaining({ userId: authenticatedUserId }),
expect.objectContaining({
messageId: savedMessage.messageId,
conversationId: urlConversationId,
text: savedMessage.text,
user: authenticatedUserId,
}),
{ context: 'POST /api/messages/:conversationId' },
);
expect(saveMessage.mock.calls[0][1].conversationId).not.toBe(bodyConversationId);
expect(saveConvo).toHaveBeenCalledWith(
expect.objectContaining({ userId: authenticatedUserId }),
savedMessage,
{ context: 'POST /api/messages/:conversationId' },
);
});
it('should filter conversation message reads by authenticated user', async () => {
getMessages.mockResolvedValue([{ messageId: 'message-1', conversationId: 'convo-1' }]);
const response = await request(app).get('/api/messages/convo-1');
expect(response.status).toBe(200);
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', user: authenticatedUserId },
'-_id -__v -user',
);
});
it('should filter single message reads by authenticated user', async () => {
getMessages.mockResolvedValue([{ messageId: 'message-1', conversationId: 'convo-1' }]);
const response = await request(app).get('/api/messages/convo-1/message-1');
expect(response.status).toBe(200);
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', messageId: 'message-1', user: authenticatedUserId },
'-_id -__v -user',
);
});
});

View file

@ -0,0 +1,167 @@
const express = require('express');
const request = require('supertest');
jest.mock('@librechat/agents', () => ({
...jest.requireActual('@librechat/agents'),
CODE_EXECUTION_TOOLS: new Set(['execute_code', 'bash_tool']),
BashExecutionToolDefinition: {
name: 'bash_tool',
description: 'bash',
schema: { type: 'object', properties: {} },
},
ReadFileToolDefinition: {
name: 'read_file',
description: 'Read a file',
parameters: { type: 'object', properties: {} },
responseFormat: 'content',
},
buildBashExecutionToolDescription: () => 'bash',
sleep: jest.fn(),
}));
jest.mock('@librechat/api', () => ({
createMessageRequestMiddleware:
jest.requireActual('@librechat/api').createMessageRequestMiddleware,
unescapeLaTeX: jest.fn((x) => x),
countTokens: jest.fn().mockResolvedValue(10),
sendFeedbackScore: jest.fn().mockResolvedValue(undefined),
traceIdForMessage: jest.fn((messageId) => `trace-${messageId}`),
mergeQuotedTextForCount: jest.fn((text) => text),
GenerationJobManager: {
getJob: jest.fn(),
},
isPendingActionStale: jest.fn(() => false),
}));
jest.mock('@librechat/data-schemas', () => ({
...jest.requireActual('@librechat/data-schemas'),
logger: {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('librechat-data-provider', () => ({
...jest.requireActual('librechat-data-provider'),
}));
jest.mock('~/models', () => ({
saveConvo: jest.fn(),
getConvo: jest.fn(),
getMessage: jest.fn(),
saveMessage: jest.fn(),
getMessages: jest.fn(),
updateMessage: jest.fn(),
deleteMessages: jest.fn(),
getConvosQueried: jest.fn(),
searchMessages: jest.fn(),
getMessagesByCursor: jest.fn(),
}));
jest.mock('~/server/services/Artifacts/update', () => ({
findAllArtifacts: jest.fn(),
replaceArtifactContent: jest.fn(),
}));
jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next());
jest.mock('~/server/middleware', () => {
const { sendValidationResponse, validateMessageReq, prepareMessageRequestValidation } =
jest.requireActual('~/server/middleware/messageValidation');
return {
requireJwtAuth: (req, res, next) => next(),
validateMessageReq,
sendValidationResponse,
prepareMessageRequestValidation,
configMiddleware: (req, res, next) => next(),
};
});
jest.mock('~/db/models', () => ({
Message: {
findOne: jest.fn(),
find: jest.fn(),
meiliSearch: jest.fn(),
},
}));
describe('GET /api/messages/:conversationId with real validation middleware', () => {
let app;
const { getConvo, getMessages } = require('~/models');
const authenticatedUserId = 'user-owner-123';
beforeAll(() => {
const messagesRouter = require('../messages');
app = express();
app.use(express.json());
app.use((req, res, next) => {
req.user = { id: authenticatedUserId };
next();
});
app.use('/api/messages', messagesRouter);
});
beforeEach(() => {
jest.clearAllMocks();
});
it('returns the existing empty response for new conversations without fetching messages', async () => {
const response = await request(app).get('/api/messages/new');
expect(response.status).toBe(200);
expect(response.body).toEqual([]);
expect(getConvo).not.toHaveBeenCalled();
expect(getMessages).not.toHaveBeenCalled();
});
it('starts user-scoped message reads before real conversation validation resolves', async () => {
const events = [];
let resolveConvo;
const convoPromise = new Promise((resolve) => {
resolveConvo = resolve;
});
getConvo.mockImplementation(() => {
events.push('convo-started');
return convoPromise;
});
let resolveMessagesStarted;
const messagesStartedPromise = new Promise((resolve) => {
resolveMessagesStarted = resolve;
});
getMessages.mockImplementation(() => {
events.push('messages-started');
resolveMessagesStarted();
return Promise.resolve([{ messageId: 'message-1', conversationId: 'convo-1' }]);
});
const responsePromise = new Promise((resolve, reject) => {
request(app)
.get('/api/messages/convo-1')
.end((error, response) => (error ? reject(error) : resolve(response)));
});
await Promise.race([
messagesStartedPromise,
new Promise((resolve) => setTimeout(resolve, 100)),
]);
const eventsBeforeValidation = [...events];
resolveConvo({ conversationId: 'convo-1', user: authenticatedUserId });
const response = await responsePromise;
expect(eventsBeforeValidation).toEqual(['convo-started', 'messages-started']);
expect(getConvo).toHaveBeenCalledWith(authenticatedUserId, 'convo-1');
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', user: authenticatedUserId },
'-_id -__v -user',
);
expect(response.status).toBe(200);
expect(response.body).toEqual([{ messageId: 'message-1', conversationId: 'convo-1' }]);
});
});

View file

@ -0,0 +1,247 @@
const express = require('express');
const request = require('supertest');
jest.mock('@librechat/agents', () => ({
sleep: jest.fn(),
}));
jest.mock('@librechat/api', () => ({
unescapeLaTeX: jest.fn((x) => x),
countTokens: jest.fn().mockResolvedValue(10),
sendFeedbackScore: jest.fn().mockResolvedValue(undefined),
traceIdForMessage: jest.fn((messageId) => `trace-${messageId}`),
}));
jest.mock('@librechat/data-schemas', () => ({
...jest.requireActual('@librechat/data-schemas'),
logger: {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('librechat-data-provider', () => ({
...jest.requireActual('librechat-data-provider'),
}));
jest.mock('~/models', () => ({
saveConvo: jest.fn(),
getMessage: jest.fn(),
saveMessage: jest.fn(),
getMessages: jest.fn(),
updateMessage: jest.fn(),
deleteMessages: jest.fn(),
getConvosQueried: jest.fn(),
searchMessages: jest.fn(),
getMessagesByCursor: jest.fn(),
}));
jest.mock('~/server/services/Artifacts/update', () => ({
findAllArtifacts: jest.fn(),
replaceArtifactContent: jest.fn(),
}));
jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next());
jest.mock('~/server/middleware', () => {
const validateMessageReq = jest.fn((req, res, next) => next());
const prepareMessageRequestValidation = jest.fn((req, res, next) => {
req.messageRequestValidation = {
conversationId: 'convo-1',
shouldFetchMessages: true,
promise: Promise.resolve({ ok: true }),
};
next();
});
const sendValidationResponse = jest.fn((res, result) => {
if (result.send) {
return res.status(result.status).send(result.body);
}
return res.status(result.status).json(result.body);
});
return {
requireJwtAuth: (req, res, next) => next(),
validateMessageReq,
sendValidationResponse,
prepareMessageRequestValidation,
configMiddleware: (req, res, next) => next(),
};
});
jest.mock('~/db/models', () => ({
Message: {
findOne: jest.fn(),
find: jest.fn(),
meiliSearch: jest.fn(),
},
}));
describe('message route conversation ownership filters', () => {
let app;
const { getMessages, saveConvo, saveMessage } = require('~/models');
const { prepareMessageRequestValidation } = require('~/server/middleware');
const authenticatedUserId = 'user-owner-123';
beforeAll(() => {
const messagesRouter = require('../messages');
app = express();
app.use(express.json());
app.use((req, res, next) => {
req.user = { id: authenticatedUserId };
next();
});
app.use('/api/messages', messagesRouter);
});
beforeEach(() => {
jest.clearAllMocks();
prepareMessageRequestValidation.mockImplementation((req, res, next) => {
req.messageRequestValidation = {
conversationId: 'convo-1',
shouldFetchMessages: true,
promise: Promise.resolve({ ok: true }),
};
next();
});
});
it('should save POST messages with the validated URL conversationId', async () => {
const urlConversationId = '11111111-1111-4111-8111-111111111111';
const bodyConversationId = '22222222-2222-4222-8222-222222222222';
const savedMessage = {
messageId: 'message-1',
conversationId: urlConversationId,
text: 'hello',
user: authenticatedUserId,
};
saveMessage.mockResolvedValue(savedMessage);
saveConvo.mockResolvedValue({ conversationId: urlConversationId });
const response = await request(app).post(`/api/messages/${urlConversationId}`).send({
messageId: savedMessage.messageId,
conversationId: bodyConversationId,
text: savedMessage.text,
});
expect(response.status).toBe(201);
expect(saveMessage).toHaveBeenCalledWith(
expect.objectContaining({ userId: authenticatedUserId }),
expect.objectContaining({
messageId: savedMessage.messageId,
conversationId: urlConversationId,
text: savedMessage.text,
user: authenticatedUserId,
}),
{ context: 'POST /api/messages/:conversationId' },
);
expect(saveMessage.mock.calls[0][1].conversationId).not.toBe(bodyConversationId);
expect(saveConvo).toHaveBeenCalledWith(
expect.objectContaining({ userId: authenticatedUserId }),
savedMessage,
{ context: 'POST /api/messages/:conversationId' },
);
});
it('should filter conversation message reads by authenticated user', async () => {
getMessages.mockResolvedValue([{ messageId: 'message-1', conversationId: 'convo-1' }]);
const response = await request(app).get('/api/messages/convo-1');
expect(response.status).toBe(200);
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', user: authenticatedUserId },
'-_id -__v -user',
);
});
it('should start conversation message reads before validation resolves', async () => {
const events = [];
let resolveValidation;
const validationPromise = new Promise((resolve) => {
resolveValidation = resolve;
});
prepareMessageRequestValidation.mockImplementationOnce((req, res, next) => {
req.messageRequestValidation = {
conversationId: 'convo-1',
shouldFetchMessages: true,
promise: validationPromise,
};
next();
});
let resolveMessagesStarted;
const messagesStartedPromise = new Promise((resolve) => {
resolveMessagesStarted = resolve;
});
getMessages.mockImplementation(() => {
events.push('messages-started');
resolveMessagesStarted();
return Promise.resolve([{ messageId: 'message-1', conversationId: 'convo-1' }]);
});
const responsePromise = new Promise((resolve, reject) => {
request(app)
.get('/api/messages/convo-1')
.end((error, response) => (error ? reject(error) : resolve(response)));
});
await Promise.race([
messagesStartedPromise,
new Promise((resolve) => setTimeout(resolve, 100)),
]);
const eventsBeforeValidation = [...events];
resolveValidation({ ok: true });
const response = await responsePromise;
expect(eventsBeforeValidation).toEqual(['messages-started']);
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', user: authenticatedUserId },
'-_id -__v -user',
);
expect(response.status).toBe(200);
expect(response.body).toEqual([{ messageId: 'message-1', conversationId: 'convo-1' }]);
});
it('should not return fetched messages when conversation validation fails', async () => {
prepareMessageRequestValidation.mockImplementationOnce((req, res, next) => {
req.messageRequestValidation = {
conversationId: 'convo-1',
shouldFetchMessages: true,
promise: Promise.resolve({
ok: false,
status: 404,
body: { error: 'Conversation not found' },
}),
};
next();
});
getMessages.mockResolvedValue([{ messageId: 'secret-message', conversationId: 'convo-1' }]);
const response = await request(app).get('/api/messages/convo-1');
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', user: authenticatedUserId },
'-_id -__v -user',
);
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Conversation not found' });
});
it('should filter single message reads by authenticated user', async () => {
getMessages.mockResolvedValue([{ messageId: 'message-1', conversationId: 'convo-1' }]);
const response = await request(app).get('/api/messages/convo-1/message-1');
expect(response.status).toBe(200);
expect(getMessages).toHaveBeenCalledWith(
{ conversationId: 'convo-1', messageId: 'message-1', user: authenticatedUserId },
'-_id -__v -user',
);
});
});

View file

@ -3,6 +3,7 @@ const {
isEnabled,
getBalanceConfig,
getCloudFrontConfig,
getAppConfigOptionsFromUser,
resolveBuildInfo,
resolveTitleTiming,
sanitizeModelSpecs,
@ -244,11 +245,7 @@ router.get('/', async function (req, res) {
return res.status(200).send(payload);
}
const appConfig = await getAppConfig({
role: req.user.role,
userId: req.user.id,
tenantId: req.user.tenantId || getTenantId(),
});
const appConfig = await getAppConfig(getAppConfigOptionsFromUser(req.user));
const balanceConfig = getBalanceConfig(appConfig);
const cloudFront = buildCloudFrontStartupConfig();

View file

@ -10,7 +10,13 @@ const {
mergeQuotedTextForCount,
} = require('@librechat/api');
const { findAllArtifacts, replaceArtifactContent } = require('~/server/services/Artifacts/update');
const { requireJwtAuth, validateMessageReq, configMiddleware } = require('~/server/middleware');
const {
requireJwtAuth,
validateMessageReq,
configMiddleware,
sendValidationResponse,
prepareMessageRequestValidation,
} = require('~/server/middleware');
const db = require('~/models');
const router = express.Router();
@ -271,11 +277,30 @@ router.post('/artifact/:messageId', async (req, res) => {
}
});
/* Note: It's necessary to add `validateMessageReq` within route definition for correct params */
router.get('/:conversationId', validateMessageReq, async (req, res) => {
router.get('/:conversationId', prepareMessageRequestValidation, async (req, res) => {
try {
const { conversationId } = req.params;
const messages = await db.getMessages({ conversationId, user: req.user.id }, '-_id -__v -user');
const validation = req.messageRequestValidation;
// This intentionally starts a user-scoped read before validation resolves;
// the response remains gated on validation success below.
const messagesPromise = validation.shouldFetchMessages
? db.getMessages({ conversationId, user: req.user.id }, '-_id -__v -user').then(
(messages) => ({ messages }),
(error) => ({ error }),
)
: null;
const validationResult = await validation.promise;
if (!validationResult.ok) {
return sendValidationResponse(res, validationResult);
}
const messagesResult = await messagesPromise;
if (messagesResult?.error) {
throw messagesResult.error;
}
const messages = messagesResult?.messages ?? [];
res.status(200).json(messages);
} catch (error) {
logger.error('Error fetching messages:', error);

View file

@ -104,6 +104,7 @@ describe('loadConfigModels', () => {
role: 'USER',
userId: 'testUserId',
tenantId: 'tenant-a',
idOnTheSource: undefined,
});
});

View file

@ -4,6 +4,7 @@ const {
mergeHeaders,
getAnthropicModels,
getBedrockModels,
getAppConfigOptionsFromUser,
getOpenAIModels,
getGoogleModels,
} = require('@librechat/api');
@ -17,13 +18,7 @@ const { getAppConfig } = require('./app');
*/
async function loadDefaultModels(req) {
try {
const appConfig =
req.config ??
(await getAppConfig({
role: req.user?.role,
userId: req.user?.id,
tenantId: req.user?.tenantId,
}));
const appConfig = req.config ?? (await getAppConfig(getAppConfigOptionsFromUser(req.user)));
const vertexConfig = appConfig?.endpoints?.[EModelEndpoint.anthropic]?.vertexConfig;
/** Forward configured custom headers (endpoint over global `all`) so model