mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
feat: add support for request body placeholders in custom endpoint headers
- Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId
- Update tests to reflect new body placeholder functionality
This commit is contained in:
parent
3508839d6d
commit
eec10bf745
5 changed files with 58 additions and 34 deletions
|
|
@ -28,23 +28,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
|
|||
const CUSTOM_API_KEY = extractEnvVariable(endpointConfig.apiKey);
|
||||
const CUSTOM_BASE_URL = extractEnvVariable(endpointConfig.baseURL);
|
||||
|
||||
const customUserVars = {};
|
||||
if (req.body.conversationId) {
|
||||
customUserVars.LIBRECHAT_CONVERSATION_ID = req.body.conversationId;
|
||||
}
|
||||
|
||||
let resolvedHeaders = resolveHeaders(endpointConfig.headers, req.user, customUserVars);
|
||||
|
||||
// Filter out headers with unresolved placeholders
|
||||
const filteredHeaders = {};
|
||||
for (const [key, value] of Object.entries(resolvedHeaders)) {
|
||||
if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) {
|
||||
continue;
|
||||
}
|
||||
filteredHeaders[key] = value;
|
||||
}
|
||||
|
||||
resolvedHeaders = filteredHeaders;
|
||||
let resolvedHeaders = resolveHeaders(endpointConfig.headers, req.user, undefined, req.body);
|
||||
|
||||
if (CUSTOM_API_KEY.match(envVarRegex)) {
|
||||
throw new Error(`Missing API Key for ${endpoint}.`);
|
||||
|
|
|
|||
|
|
@ -64,26 +64,14 @@ describe('custom/initializeClient', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('calls resolveHeaders with conversation ID when provided', async () => {
|
||||
const { resolveHeaders } = require('@librechat/api');
|
||||
const requestWithConversationId = {
|
||||
...mockRequest,
|
||||
body: { ...mockRequest.body, conversationId: 'existing-conversation-123' },
|
||||
};
|
||||
await initializeClient({ req: requestWithConversationId, res: mockResponse, optionsOnly: true });
|
||||
expect(resolveHeaders).toHaveBeenCalledWith(
|
||||
{ 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
|
||||
{ id: 'user-123', email: 'test@example.com' },
|
||||
{ LIBRECHAT_CONVERSATION_ID: 'existing-conversation-123' },
|
||||
);
|
||||
});
|
||||
|
||||
it('calls resolveHeaders with headers and user', async () => {
|
||||
it('calls resolveHeaders with headers, user, and body for body placeholder support', async () => {
|
||||
const { resolveHeaders } = require('@librechat/api');
|
||||
await initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true });
|
||||
expect(resolveHeaders).toHaveBeenCalledWith(
|
||||
{ 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
|
||||
{ id: 'user-123', email: 'test@example.com' },
|
||||
undefined, // customUserVars
|
||||
{ endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ endpoints:
|
|||
# recommended environment variables:
|
||||
apiKey: '${OPENROUTER_KEY}'
|
||||
baseURL: 'https://openrouter.ai/api/v1'
|
||||
headers:
|
||||
x-librechat-body-parentmessageid: '{{LIBRECHAT_BODY_PARENTMESSAGEID}}'
|
||||
models:
|
||||
default: ['meta-llama/llama-3-70b-instruct']
|
||||
fetch: true
|
||||
|
|
|
|||
|
|
@ -426,4 +426,11 @@ describe('resolveHeaders', () => {
|
|||
expect(result['X-Empty']).toBe('');
|
||||
expect(result['X-Boolean']).toBe('true');
|
||||
});
|
||||
|
||||
it('should process LIBRECHAT_BODY placeholders', () => {
|
||||
const body = { conversationId: 'conv-123', parentMessageId: 'parent-456', messageId: 'msg-789' };
|
||||
const headers = { 'X-Conversation': '{{LIBRECHAT_BODY_CONVERSATIONID}}' };
|
||||
const result = resolveHeaders(headers, undefined, undefined, body);
|
||||
expect(result['X-Conversation']).toBe('conv-123');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ const ALLOWED_USER_FIELDS = [
|
|||
'termsAccepted',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* List of allowed request body fields that can be used in header placeholders.
|
||||
* These are common fields from the request body that are safe to expose in headers.
|
||||
*/
|
||||
const ALLOWED_BODY_FIELDS = [
|
||||
'conversationId',
|
||||
'parentMessageId',
|
||||
'messageId'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Processes a string value to replace user field placeholders
|
||||
* @param value - The string value to process
|
||||
|
|
@ -61,21 +71,46 @@ function processUserPlaceholders(value: string, user?: TUser): string {
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a string value to replace request body field placeholders
|
||||
* @param value - The string value to process
|
||||
* @param body - The request body object
|
||||
* @returns The processed string with placeholders replaced
|
||||
*/
|
||||
function processBodyPlaceholders(value: string, body: Record<string, any>): string {
|
||||
|
||||
for (const field of ALLOWED_BODY_FIELDS) {
|
||||
const placeholder = `{{LIBRECHAT_BODY_${field.toUpperCase()}}}`;
|
||||
if (!value.includes(placeholder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldValue = body[field];
|
||||
const replacementValue = fieldValue == null ? '' : String(fieldValue);
|
||||
value = value.replace(new RegExp(placeholder, 'g'), replacementValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single string value by replacing various types of placeholders
|
||||
* @param originalValue - The original string value to process
|
||||
* @param customUserVars - Optional custom user variables to replace placeholders
|
||||
* @param user - Optional user object for replacing user field placeholders
|
||||
* @param body - Optional request body object for replacing body field placeholders
|
||||
* @returns The processed string with all placeholders replaced
|
||||
*/
|
||||
function processSingleValue({
|
||||
originalValue,
|
||||
customUserVars,
|
||||
user,
|
||||
body = undefined,
|
||||
}: {
|
||||
originalValue: string;
|
||||
customUserVars?: Record<string, string>;
|
||||
user?: TUser;
|
||||
body?: Record<string, any>;
|
||||
}): string {
|
||||
let value = originalValue;
|
||||
|
||||
|
|
@ -92,7 +127,12 @@ function processSingleValue({
|
|||
// 2. Replace user field placeholders (e.g., {{LIBRECHAT_USER_EMAIL}}, {{LIBRECHAT_USER_ID}})
|
||||
value = processUserPlaceholders(value, user);
|
||||
|
||||
// 3. Replace system environment variables
|
||||
// 3. Replace body field placeholders (e.g., {{LIBRECHAT_BODY_CONVERSATIONID}}, {{LIBRECHAT_BODY_PARENTMESSAGEID}})
|
||||
if (body) {
|
||||
value = processBodyPlaceholders(value, body);
|
||||
}
|
||||
|
||||
// 4. Replace system environment variables
|
||||
value = extractEnvVariable(value);
|
||||
|
||||
return value;
|
||||
|
|
@ -151,16 +191,18 @@ export function processMCPEnv(
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolves header values by replacing user placeholders, custom variables, and environment variables
|
||||
* Resolves header values by replacing user placeholders, custom variables, body variables, and environment variables
|
||||
* @param headers - The headers object to process
|
||||
* @param user - Optional user object for replacing user field placeholders (can be partial with just id)
|
||||
* @param customUserVars - Optional custom user variables to replace placeholders
|
||||
* @param body - Optional request body object for replacing body field placeholders
|
||||
* @returns - The processed headers with all placeholders replaced
|
||||
*/
|
||||
export function resolveHeaders(
|
||||
headers: Record<string, string> | undefined,
|
||||
user?: Partial<TUser> | { id: string },
|
||||
customUserVars?: Record<string, string>,
|
||||
body?: Record<string, any>,
|
||||
) {
|
||||
const resolvedHeaders = { ...(headers ?? {}) };
|
||||
|
||||
|
|
@ -170,6 +212,7 @@ export function resolveHeaders(
|
|||
originalValue: headers[key],
|
||||
customUserVars,
|
||||
user: user as TUser,
|
||||
body,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue