Skip to content

Commit ee3e954

Browse files
author
Gopal Sharma
committed
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
1 parent 3a8bd57 commit ee3e954

File tree

5 files changed

+58
-34
lines changed

5 files changed

+58
-34
lines changed

api/server/services/Endpoints/custom/initialize.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
2828
const CUSTOM_API_KEY = extractEnvVariable(endpointConfig.apiKey);
2929
const CUSTOM_BASE_URL = extractEnvVariable(endpointConfig.baseURL);
3030

31-
const customUserVars = {};
32-
if (req.body.conversationId) {
33-
customUserVars.LIBRECHAT_CONVERSATION_ID = req.body.conversationId;
34-
}
35-
36-
let resolvedHeaders = resolveHeaders(endpointConfig.headers, req.user, customUserVars);
37-
38-
// Filter out headers with unresolved placeholders
39-
const filteredHeaders = {};
40-
for (const [key, value] of Object.entries(resolvedHeaders)) {
41-
if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) {
42-
continue;
43-
}
44-
filteredHeaders[key] = value;
45-
}
46-
47-
resolvedHeaders = filteredHeaders;
31+
let resolvedHeaders = resolveHeaders(endpointConfig.headers, req.user, undefined, req.body);
4832

4933
if (CUSTOM_API_KEY.match(envVarRegex)) {
5034
throw new Error(`Missing API Key for ${endpoint}.`);

api/server/services/Endpoints/custom/initialize.spec.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,14 @@ describe('custom/initializeClient', () => {
6464
jest.clearAllMocks();
6565
});
6666

67-
it('calls resolveHeaders with conversation ID when provided', async () => {
68-
const { resolveHeaders } = require('@librechat/api');
69-
const requestWithConversationId = {
70-
...mockRequest,
71-
body: { ...mockRequest.body, conversationId: 'existing-conversation-123' },
72-
};
73-
await initializeClient({ req: requestWithConversationId, res: mockResponse, optionsOnly: true });
74-
expect(resolveHeaders).toHaveBeenCalledWith(
75-
{ 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
76-
{ id: 'user-123', email: '[email protected]' },
77-
{ LIBRECHAT_CONVERSATION_ID: 'existing-conversation-123' },
78-
);
79-
});
80-
81-
it('calls resolveHeaders with headers and user', async () => {
67+
it('calls resolveHeaders with headers, user, and body for body placeholder support', async () => {
8268
const { resolveHeaders } = require('@librechat/api');
8369
await initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true });
8470
expect(resolveHeaders).toHaveBeenCalledWith(
8571
{ 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
8672
{ id: 'user-123', email: '[email protected]' },
73+
undefined, // customUserVars
74+
{ endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
8775
);
8876
});
8977

librechat.example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ endpoints:
259259
# recommended environment variables:
260260
apiKey: '${OPENROUTER_KEY}'
261261
baseURL: 'https://openrouter.ai/api/v1'
262+
headers:
263+
x-librechat-body-parentmessageid: '{{LIBRECHAT_BODY_PARENTMESSAGEID}}'
262264
models:
263265
default: ['meta-llama/llama-3-70b-instruct']
264266
fetch: true

packages/api/src/utils/env.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,11 @@ describe('resolveHeaders', () => {
426426
expect(result['X-Empty']).toBe('');
427427
expect(result['X-Boolean']).toBe('true');
428428
});
429+
430+
it('should process LIBRECHAT_BODY placeholders', () => {
431+
const body = { conversationId: 'conv-123', parentMessageId: 'parent-456', messageId: 'msg-789' };
432+
const headers = { 'X-Conversation': '{{LIBRECHAT_BODY_CONVERSATIONID}}' };
433+
const result = resolveHeaders(headers, undefined, undefined, body);
434+
expect(result['X-Conversation']).toBe('conv-123');
435+
});
429436
});

packages/api/src/utils/env.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const ALLOWED_USER_FIELDS = [
2525
'termsAccepted',
2626
] as const;
2727

28+
/**
29+
* List of allowed request body fields that can be used in header placeholders.
30+
* These are common fields from the request body that are safe to expose in headers.
31+
*/
32+
const ALLOWED_BODY_FIELDS = [
33+
'conversationId',
34+
'parentMessageId',
35+
'messageId'
36+
] as const;
37+
2838
/**
2939
* Processes a string value to replace user field placeholders
3040
* @param value - The string value to process
@@ -61,21 +71,46 @@ function processUserPlaceholders(value: string, user?: TUser): string {
6171
return value;
6272
}
6373

74+
/**
75+
* Processes a string value to replace request body field placeholders
76+
* @param value - The string value to process
77+
* @param body - The request body object
78+
* @returns The processed string with placeholders replaced
79+
*/
80+
function processBodyPlaceholders(value: string, body: Record<string, any>): string {
81+
82+
for (const field of ALLOWED_BODY_FIELDS) {
83+
const placeholder = `{{LIBRECHAT_BODY_${field.toUpperCase()}}}`;
84+
if (!value.includes(placeholder)) {
85+
continue;
86+
}
87+
88+
const fieldValue = body[field];
89+
const replacementValue = fieldValue == null ? '' : String(fieldValue);
90+
value = value.replace(new RegExp(placeholder, 'g'), replacementValue);
91+
}
92+
93+
return value;
94+
}
95+
6496
/**
6597
* Processes a single string value by replacing various types of placeholders
6698
* @param originalValue - The original string value to process
6799
* @param customUserVars - Optional custom user variables to replace placeholders
68100
* @param user - Optional user object for replacing user field placeholders
101+
* @param body - Optional request body object for replacing body field placeholders
69102
* @returns The processed string with all placeholders replaced
70103
*/
71104
function processSingleValue({
72105
originalValue,
73106
customUserVars,
74107
user,
108+
body = undefined,
75109
}: {
76110
originalValue: string;
77111
customUserVars?: Record<string, string>;
78112
user?: TUser;
113+
body?: Record<string, any>;
79114
}): string {
80115
let value = originalValue;
81116

@@ -92,7 +127,12 @@ function processSingleValue({
92127
// 2. Replace user field placeholders (e.g., {{LIBRECHAT_USER_EMAIL}}, {{LIBRECHAT_USER_ID}})
93128
value = processUserPlaceholders(value, user);
94129

95-
// 3. Replace system environment variables
130+
// 3. Replace body field placeholders (e.g., {{LIBRECHAT_BODY_CONVERSATIONID}}, {{LIBRECHAT_BODY_PARENTMESSAGEID}})
131+
if (body) {
132+
value = processBodyPlaceholders(value, body);
133+
}
134+
135+
// 4. Replace system environment variables
96136
value = extractEnvVariable(value);
97137

98138
return value;
@@ -151,16 +191,18 @@ export function processMCPEnv(
151191
}
152192

153193
/**
154-
* Resolves header values by replacing user placeholders, custom variables, and environment variables
194+
* Resolves header values by replacing user placeholders, custom variables, body variables, and environment variables
155195
* @param headers - The headers object to process
156196
* @param user - Optional user object for replacing user field placeholders (can be partial with just id)
157197
* @param customUserVars - Optional custom user variables to replace placeholders
198+
* @param body - Optional request body object for replacing body field placeholders
158199
* @returns - The processed headers with all placeholders replaced
159200
*/
160201
export function resolveHeaders(
161202
headers: Record<string, string> | undefined,
162203
user?: Partial<TUser> | { id: string },
163204
customUserVars?: Record<string, string>,
205+
body?: Record<string, any>,
164206
) {
165207
const resolvedHeaders = { ...(headers ?? {}) };
166208

@@ -170,6 +212,7 @@ export function resolveHeaders(
170212
originalValue: headers[key],
171213
customUserVars,
172214
user: user as TUser,
215+
body,
173216
});
174217
});
175218
}

0 commit comments

Comments
 (0)