This commit is contained in:
ChrisJr404 2026-05-12 15:59:52 -04:00 committed by GitHub
commit c12aecae9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 117 additions and 0 deletions

View file

@ -159,6 +159,88 @@ describe('replaceSpecialVars', () => {
expect(result).toContain('2024-04-29T16:34:56.000Z'); // iso_datetime
expect(result).toContain('Test User'); // current_user
});
describe('LIBRECHAT_USER_* placeholders', () => {
const fullUser = {
id: 'user-abc-123',
name: 'Test User',
username: 'tuser',
email: 'tuser@example.com',
role: 'USER',
provider: 'ldap',
} as TUser;
test('expands LIBRECHAT_USER_USERNAME and LIBRECHAT_USER_ID', () => {
const result = replaceSpecialVars({
text: 'USERNAME={{LIBRECHAT_USER_USERNAME}} USER_ID={{LIBRECHAT_USER_ID}}',
user: fullUser,
});
expect(result).toBe('USERNAME=tuser USER_ID=user-abc-123');
});
test('alongside {{current_date}} (modelSpecs.promptPrefix scenario from #12686)', () => {
const result = replaceSpecialVars({
text: [
'DATE={{current_date}}',
'USERNAME={{LIBRECHAT_USER_USERNAME}}',
'USER_ID={{LIBRECHAT_USER_ID}}',
].join('\n'),
user: fullUser,
});
expect(result).toBe(
['DATE=2024-04-29 (Monday)', 'USERNAME=tuser', 'USER_ID=user-abc-123'].join('\n'),
);
});
test('expands email, role, provider, and name fields', () => {
const result = replaceSpecialVars({
text:
'email={{LIBRECHAT_USER_EMAIL}} role={{LIBRECHAT_USER_ROLE}} ' +
'provider={{LIBRECHAT_USER_PROVIDER}} name={{LIBRECHAT_USER_NAME}}',
user: fullUser,
});
expect(result).toBe(
'email=tuser@example.com role=USER provider=ldap name=Test User',
);
});
test('is case-insensitive and tolerates surrounding whitespace', () => {
const result = replaceSpecialVars({
text: '{{ librechat_user_username }} / {{LIBRECHAT_USER_username}}',
user: fullUser,
});
expect(result).toBe('tuser / tuser');
});
test('leaves placeholder intact when the user field is missing', () => {
const partialUser = { id: 'u1', name: 'Only Name' } as TUser;
const result = replaceSpecialVars({
text: 'u={{LIBRECHAT_USER_USERNAME}} id={{LIBRECHAT_USER_ID}}',
user: partialUser,
});
expect(result).toBe('u={{LIBRECHAT_USER_USERNAME}} id=u1');
});
test('leaves placeholders intact when no user is supplied', () => {
const result = replaceSpecialVars({
text: '{{LIBRECHAT_USER_USERNAME}} {{LIBRECHAT_USER_ID}}',
});
expect(result).toBe('{{LIBRECHAT_USER_USERNAME}} {{LIBRECHAT_USER_ID}}');
});
test('does not expand fields outside the allowlist', () => {
const userWithExtras = {
id: 'u1',
name: 'N',
avatar: 'https://example.com/a.png',
} as TUser;
const result = replaceSpecialVars({
text: '{{LIBRECHAT_USER_AVATAR}} {{LIBRECHAT_USER_PASSWORD}}',
user: userWithExtras,
});
expect(result).toBe('{{LIBRECHAT_USER_AVATAR}} {{LIBRECHAT_USER_PASSWORD}}');
});
});
});
describe('parseCompactConvo', () => {

View file

@ -424,6 +424,27 @@ export function findLastSeparatorIndex(text: string, separators = SEPARATORS): n
return lastIndex;
}
/**
* Fields on the user object that may be expanded via
* `{{LIBRECHAT_USER_<FIELD>}}` placeholders inside a prompt template
* (e.g. modelSpecs `promptPrefix`, agent instructions, prompts).
*
* The set is intentionally limited to non-sensitive identity fields and
* mirrors the allowlist used by MCP/header placeholder resolution in
* `@librechat/api`. Placeholders for fields that are missing on the user
* object are left intact (matching the existing `{{current_user}}` contract).
*/
export const librechatUserFields = [
'id',
'name',
'username',
'email',
'provider',
'role',
] as const;
export type LibreChatUserField = (typeof librechatUserFields)[number];
export function replaceSpecialVars({
text,
user,
@ -454,6 +475,20 @@ export function replaceSpecialVars({
result = result.replace(/{{\s*current_user\s*}}/gi, user.name);
}
if (user) {
for (const field of librechatUserFields) {
const fieldValue = (user as Record<string, unknown>)[field];
if (fieldValue == null || fieldValue === '') {
continue;
}
const pattern = new RegExp(
`{{\\s*LIBRECHAT_USER_${field.toUpperCase()}\\s*}}`,
'gi',
);
result = result.replace(pattern, String(fieldValue));
}
}
return result;
}