Skip to content

Commit d37db43

Browse files
Gopal Sharmas10gopal
authored andcommitted
refactor resolveHeaders
1 parent eec10bf commit d37db43

File tree

10 files changed

+78
-63
lines changed

10 files changed

+78
-63
lines changed

api/app/clients/OpenAIClient.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -652,10 +652,10 @@ class OpenAIClient extends BaseClient {
652652
const { headers } = this.options;
653653
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
654654
configOptions.baseOptions = {
655-
headers: resolveHeaders({
655+
headers: resolveHeaders({ headers: {
656656
...headers,
657657
...configOptions?.baseOptions?.headers,
658-
}),
658+
} }),
659659
};
660660
}
661661

@@ -749,7 +749,7 @@ class OpenAIClient extends BaseClient {
749749
groupMap,
750750
});
751751

752-
this.options.headers = resolveHeaders(headers);
752+
this.options.headers = resolveHeaders({ headers });
753753
this.options.reverseProxyUrl = baseURL ?? null;
754754
this.langchainProxy = extractBaseURL(this.options.reverseProxyUrl);
755755
this.apiKey = azureOptions.azureOpenAIApiKey;
@@ -1181,7 +1181,7 @@ ${convo}
11811181
modelGroupMap,
11821182
groupMap,
11831183
});
1184-
opts.defaultHeaders = resolveHeaders(headers);
1184+
opts.defaultHeaders = resolveHeaders({ headers });
11851185
this.langchainProxy = extractBaseURL(baseURL);
11861186
this.apiKey = azureOptions.azureOpenAIApiKey;
11871187

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
109109

110110
apiKey = azureOptions.azureOpenAIApiKey;
111111
opts.defaultQuery = { 'api-version': azureOptions.azureOpenAIApiVersion };
112-
opts.defaultHeaders = resolveHeaders(
113-
{
112+
opts.defaultHeaders = resolveHeaders({
113+
headers: {
114114
...headers,
115115
'api-key': apiKey,
116116
'OpenAI-Beta': `assistants=${version}`,
117117
},
118-
req.user,
119-
);
118+
user: req.user,
119+
});
120120
opts.model = azureOptions.azureOpenAIApiDeploymentName;
121121

122122
if (initAppClient) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +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-
let resolvedHeaders = resolveHeaders(endpointConfig.headers, req.user, undefined, req.body);
31+
let resolvedHeaders = resolveHeaders({ headers: endpointConfig.headers, user: req.user, body: req.body });
3232

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

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,11 @@ describe('custom/initializeClient', () => {
6767
it('calls resolveHeaders with headers, user, and body for body placeholder support', async () => {
6868
const { resolveHeaders } = require('@librechat/api');
6969
await initializeClient({ req: mockRequest, res: mockResponse, optionsOnly: true });
70-
expect(resolveHeaders).toHaveBeenCalledWith(
71-
{ 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
72-
{ id: 'user-123', email: '[email protected]' },
73-
undefined, // customUserVars
74-
{ endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
75-
);
70+
expect(resolveHeaders).toHaveBeenCalledWith({
71+
headers: { 'x-user': '{{LIBRECHAT_USER_ID}}', 'x-email': '{{LIBRECHAT_USER_EMAIL}}' },
72+
user: { id: 'user-123', email: '[email protected]' },
73+
body: { endpoint: 'test-endpoint' }, // body - supports {{LIBRECHAT_BODY_*}} placeholders
74+
});
7675
});
7776

7877
it('throws if endpoint config is missing', async () => {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ const initializeClient = async ({
8181
serverless = _serverless;
8282

8383
clientOptions.reverseProxyUrl = baseURL ?? clientOptions.reverseProxyUrl;
84-
clientOptions.headers = resolveHeaders(
85-
{ ...headers, ...(clientOptions.headers ?? {}) },
86-
req.user,
87-
);
84+
clientOptions.headers = resolveHeaders({
85+
headers: { ...headers, ...(clientOptions.headers ?? {}) },
86+
user: req.user,
87+
});
8888

8989
clientOptions.titleConvo = azureConfig.titleConvo;
9090
clientOptions.titleModel = azureConfig.titleModel;

packages/api/src/endpoints/openai/initialize.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export const initializeOpenAI = async ({
8787
});
8888

8989
clientOptions.reverseProxyUrl = configBaseURL ?? clientOptions.reverseProxyUrl;
90-
clientOptions.headers = resolveHeaders(
91-
{ ...headers, ...(clientOptions.headers ?? {}) },
92-
req.user,
93-
);
90+
clientOptions.headers = resolveHeaders({
91+
headers: { ...headers, ...(clientOptions.headers ?? {}) },
92+
user: req.user,
93+
});
9494

9595
const groupName = modelGroupMap[modelName || '']?.group;
9696
if (groupName && groupMap[groupName]) {

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ describe('resolveHeaders', () => {
3636
});
3737

3838
it('should return empty object when headers is null', () => {
39-
const result = resolveHeaders(null as unknown as Record<string, string> | undefined);
39+
const result = resolveHeaders({ headers: null as unknown as Record<string, string> | null });
4040
expect(result).toEqual({});
4141
});
4242

4343
it('should return empty object when headers is empty', () => {
44-
const result = resolveHeaders({});
44+
const result = resolveHeaders({ headers: {} });
4545
expect(result).toEqual({});
4646
});
4747

@@ -52,7 +52,7 @@ describe('resolveHeaders', () => {
5252
'Content-Type': 'application/json',
5353
};
5454

55-
const result = resolveHeaders(headers);
55+
const result = resolveHeaders({ headers });
5656

5757
expect(result).toEqual({
5858
Authorization: 'test-api-key-value',
@@ -68,7 +68,7 @@ describe('resolveHeaders', () => {
6868
'Content-Type': 'application/json',
6969
};
7070

71-
const result = resolveHeaders(headers, user);
71+
const result = resolveHeaders({ headers, user });
7272

7373
expect(result).toEqual({
7474
'User-Id': 'test-user-123',
@@ -82,7 +82,7 @@ describe('resolveHeaders', () => {
8282
'Content-Type': 'application/json',
8383
};
8484

85-
const result = resolveHeaders(headers);
85+
const result = resolveHeaders({ headers });
8686

8787
expect(result).toEqual({
8888
'User-Id': '{{LIBRECHAT_USER_ID}}',
@@ -97,7 +97,7 @@ describe('resolveHeaders', () => {
9797
'Content-Type': 'application/json',
9898
};
9999

100-
const result = resolveHeaders(headers, user);
100+
const result = resolveHeaders({ headers, user });
101101

102102
expect(result).toEqual({
103103
'User-Id': '{{LIBRECHAT_USER_ID}}',
@@ -123,7 +123,7 @@ describe('resolveHeaders', () => {
123123
'Content-Type': 'application/json',
124124
};
125125

126-
const result = resolveHeaders(headers, user);
126+
const result = resolveHeaders({ headers, user });
127127

128128
expect(result).toEqual({
129129
'User-Email': '[email protected]',
@@ -148,7 +148,7 @@ describe('resolveHeaders', () => {
148148
'Non-Existent': '{{LIBRECHAT_USER_NONEXISTENT}}',
149149
};
150150

151-
const result = resolveHeaders(headers, user);
151+
const result = resolveHeaders({ headers, user });
152152

153153
expect(result).toEqual({
154154
'User-Email': '[email protected]',
@@ -171,7 +171,7 @@ describe('resolveHeaders', () => {
171171
'X-User-Id': '{{LIBRECHAT_USER_ID}}',
172172
};
173173

174-
const result = resolveHeaders(headers, user, customUserVars);
174+
const result = resolveHeaders({ headers, user, customUserVars });
175175

176176
expect(result).toEqual({
177177
Authorization: 'Bearer user-specific-token',
@@ -194,7 +194,7 @@ describe('resolveHeaders', () => {
194194
'Test-Email': '{{LIBRECHAT_USER_EMAIL}}',
195195
};
196196

197-
const result = resolveHeaders(headers, user, customUserVars);
197+
const result = resolveHeaders({ headers, user, customUserVars });
198198

199199
expect(result).toEqual({
200200
'Test-Email': '[email protected]',
@@ -213,7 +213,7 @@ describe('resolveHeaders', () => {
213213
'User-Id': '{{LIBRECHAT_USER_ID}}',
214214
};
215215

216-
const result = resolveHeaders(headers, user);
216+
const result = resolveHeaders({ headers, user });
217217

218218
expect(result).toEqual({
219219
'User-Role': 'admin',
@@ -233,7 +233,7 @@ describe('resolveHeaders', () => {
233233
'Backup-Email': '{{LIBRECHAT_USER_EMAIL}}',
234234
};
235235

236-
const result = resolveHeaders(headers, user);
236+
const result = resolveHeaders({ headers, user });
237237

238238
expect(result).toEqual({
239239
'Primary-Email': '[email protected]',
@@ -259,7 +259,7 @@ describe('resolveHeaders', () => {
259259
'Content-Type': 'application/json',
260260
};
261261

262-
const result = resolveHeaders(headers, user, customUserVars);
262+
const result = resolveHeaders({ headers, user, customUserVars });
263263

264264
expect(result).toEqual({
265265
Authorization: 'Bearer secret-token',
@@ -277,7 +277,7 @@ describe('resolveHeaders', () => {
277277
};
278278
const user = { id: 'user-123' };
279279

280-
const result = resolveHeaders(originalHeaders, user);
280+
const result = resolveHeaders({ headers: originalHeaders, user });
281281

282282
// Verify the result is processed
283283
expect(result).toEqual({
@@ -306,7 +306,7 @@ describe('resolveHeaders', () => {
306306
'Dot-Header': '{{CUSTOM.VAR}}',
307307
};
308308

309-
const result = resolveHeaders(headers, user, customUserVars);
309+
const result = resolveHeaders({ headers, user, customUserVars });
310310

311311
expect(result).toEqual({
312312
'Dash-Header': 'dash-value',
@@ -357,7 +357,7 @@ describe('resolveHeaders', () => {
357357
'X-User-TermsAccepted': '{{LIBRECHAT_USER_TERMSACCEPTED}}',
358358
};
359359

360-
const result = resolveHeaders(headers, user);
360+
const result = resolveHeaders({ headers, user });
361361

362362
expect(result['X-User-ID']).toBe('abc');
363363
expect(result['X-User-Name']).toBe('Test User');
@@ -384,7 +384,7 @@ describe('resolveHeaders', () => {
384384
'X-Multi': 'User: {{LIBRECHAT_USER_ID}}, Env: ${TEST_API_KEY}, Custom: {{MY_CUSTOM}}',
385385
};
386386
const customVars = { MY_CUSTOM: 'custom-value' };
387-
const result = resolveHeaders(headers, user, customVars);
387+
const result = resolveHeaders({ headers, user, customUserVars: customVars });
388388
expect(result['X-Multi']).toBe('User: abc, Env: test-api-key-value, Custom: custom-value');
389389
});
390390

@@ -394,7 +394,7 @@ describe('resolveHeaders', () => {
394394
'X-Unknown': '{{SOMETHING_NOT_RECOGNIZED}}',
395395
'X-Known': '{{LIBRECHAT_USER_ID}}',
396396
};
397-
const result = resolveHeaders(headers, user);
397+
const result = resolveHeaders({ headers, user });
398398
expect(result['X-Unknown']).toBe('{{SOMETHING_NOT_RECOGNIZED}}');
399399
expect(result['X-Known']).toBe('abc');
400400
});
@@ -416,7 +416,7 @@ describe('resolveHeaders', () => {
416416
'X-Boolean': '{{LIBRECHAT_USER_EMAILVERIFIED}}',
417417
};
418418
const customVars = { MY_CUSTOM: 'custom-value' };
419-
const result = resolveHeaders(headers, user, customVars);
419+
const result = resolveHeaders({ headers, user, customUserVars: customVars });
420420

421421
expect(result['X-User']).toBe('abc');
422422
expect(result['X-Env']).toBe('test-api-key-value');
@@ -430,7 +430,7 @@ describe('resolveHeaders', () => {
430430
it('should process LIBRECHAT_BODY placeholders', () => {
431431
const body = { conversationId: 'conv-123', parentMessageId: 'parent-456', messageId: 'msg-789' };
432432
const headers = { 'X-Conversation': '{{LIBRECHAT_BODY_CONVERSATIONID}}' };
433-
const result = resolveHeaders(headers, undefined, undefined, body);
433+
const result = resolveHeaders({ headers, body });
434434
expect(result['X-Conversation']).toBe('conv-123');
435435
});
436436
});

packages/api/src/utils/env.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { extractEnvVariable } from 'librechat-data-provider';
2-
import type { TUser, MCPOptions } from 'librechat-data-provider';
2+
import type { TUser, MCPOptions, RequestBody } from 'librechat-data-provider';
33

44
/**
55
* List of allowed user fields that can be used in MCP environment variables.
@@ -72,12 +72,15 @@ function processUserPlaceholders(value: string, user?: TUser): string {
7272
}
7373

7474
/**
75-
* Processes a string value to replace request body field placeholders
75+
* Replaces request body field placeholders within a string.
76+
* Recognized placeholders: `{{LIBRECHAT_BODY_<FIELD>}}` where `<FIELD>` ∈ ALLOWED_BODY_FIELDS.
77+
* If a body field is absent or null/undefined, it is replaced with an empty string.
78+
*
7679
* @param value - The string value to process
7780
* @param body - The request body object
7881
* @returns The processed string with placeholders replaced
7982
*/
80-
function processBodyPlaceholders(value: string, body: Record<string, any>): string {
83+
function processBodyPlaceholders(value: string, body: RequestBody): string {
8184

8285
for (const field of ALLOWED_BODY_FIELDS) {
8386
const placeholder = `{{LIBRECHAT_BODY_${field.toUpperCase()}}}`;
@@ -110,7 +113,7 @@ function processSingleValue({
110113
originalValue: string;
111114
customUserVars?: Record<string, string>;
112115
user?: TUser;
113-
body?: Record<string, any>;
116+
body?: RequestBody;
114117
}): string {
115118
let value = originalValue;
116119

@@ -191,25 +194,30 @@ export function processMCPEnv(
191194
}
192195

193196
/**
194-
* Resolves header values by replacing user placeholders, custom variables, body variables, and environment variables
195-
* @param headers - The headers object to process
196-
* @param user - Optional user object for replacing user field placeholders (can be partial with just id)
197-
* @param customUserVars - Optional custom user variables to replace placeholders
198-
* @param body - Optional request body object for replacing body field placeholders
199-
* @returns - The processed headers with all placeholders replaced
197+
* Resolves header values by replacing user placeholders, body variables, custom variables, and environment variables.
198+
*
199+
* @param options - Optional configuration object.
200+
* @param options.headers - The headers object to process.
201+
* @param options.user - Optional user object for replacing user field placeholders (can be partial with just id).
202+
* @param options.body - Optional request body object for replacing body field placeholders.
203+
* @param options.customUserVars - Optional custom user variables to replace placeholders.
204+
* @returns The processed headers with all placeholders replaced.
200205
*/
201-
export function resolveHeaders(
202-
headers: Record<string, string> | undefined,
203-
user?: Partial<TUser> | { id: string },
204-
customUserVars?: Record<string, string>,
205-
body?: Record<string, any>,
206-
) {
207-
const resolvedHeaders = { ...(headers ?? {}) };
206+
export function resolveHeaders(options?: {
207+
headers: Record<string, string> | undefined;
208+
user?: Partial<TUser> | { id: string };
209+
body?: RequestBody;
210+
customUserVars?: Record<string, string>;
211+
}) {
212+
const { headers, user, body, customUserVars } = options ?? {};
213+
const inputHeaders = headers ?? {};
214+
215+
const resolvedHeaders: Record<string, string> = { ...inputHeaders };
208216

209-
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
210-
Object.keys(headers).forEach((key) => {
217+
if (inputHeaders && typeof inputHeaders === 'object' && !Array.isArray(inputHeaders)) {
218+
Object.keys(inputHeaders).forEach((key) => {
211219
resolvedHeaders[key] = processSingleValue({
212-
originalValue: headers[key],
220+
originalValue: inputHeaders[key],
213221
customUserVars,
214222
user: user as TUser,
215223
body,

packages/data-provider/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './types/mutations';
2727
export * from './types/queries';
2828
export * from './types/runs';
2929
export * from './types/web';
30+
export * from './types/http';
3031
/* query/mutation keys */
3132
export * from './keys';
3233
/* api call helpers */
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface RequestBody {
2+
parentMessageId: string;
3+
messageId: string;
4+
conversationId?: string;
5+
[key: string]: unknown;
6+
}
7+

0 commit comments

Comments
 (0)