🎨 chore: prettier --write all workspaces (#13281)

Run `prettier --write` over the source trees of every workspace to align
with the repo's own `.prettierrc` (`printWidth: 100`, `singleQuote: true`,
`trailingComma: 'all'`, etc.). **19 files reformatted total** — purely
whitespace and line-wrap changes, no functional edits and no API changes.

Scope:
- `packages/api/src/**/*.{ts,tsx}` — 14 files
- `packages/client/src/**/*.{ts,tsx}` — 1 file
- `packages/data-schemas/src/**/*.{ts,tsx}` — 4 files
- `api/**`, `client/**`, `packages/data-provider/**` — already prettier-clean

Most of the drift is in argument-list / type-annotation wrapping where
the formatted form fits within `printWidth` but the current source keeps
a hand-wrapped multi-line shape. Example:

  // before
  function countWebSearchDefinitions(
    toolDefinitions: Array<{ name: string }> | undefined,
  ): number { … }

  // after (still well under 100 cols)
  function countWebSearchDefinitions(toolDefinitions: Array<{ name: string }> | undefined): number { … }

`npx prettier --check` across all workspaces is now clean. The local
pre-commit hook (`lint-staged` → `prettier --write`) would have produced
the same result on any future edit to these files.

There are no prettier-checking workflows in CI today, so drift like this
can re-appear if PRs are merged with the hook bypassed. Companion PR
#13282 adds a `prettier --check` step to `eslint-ci.yml` so future
drift gets caught.
This commit is contained in:
Danny Avila 2026-05-23 17:52:58 -04:00 committed by GitHub
parent 2bcf3e8582
commit 83c7d637c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 349 additions and 318 deletions

View file

@ -8,12 +8,8 @@ jest.mock('@librechat/agents', () => ({
}
return null;
}),
estimateAnthropicImageTokens: jest.fn(
(w: number, h: number) => Math.ceil((w * h) / 750),
),
estimateOpenAIImageTokens: jest.fn(
(w: number, h: number) => Math.ceil((w * h) / 512) + 85,
),
estimateAnthropicImageTokens: jest.fn((w: number, h: number) => Math.ceil((w * h) / 750)),
estimateOpenAIImageTokens: jest.fn((w: number, h: number) => Math.ceil((w * h) / 512) + 85),
}));
const fakeTokenCount = (text: string) => Math.ceil(text.length / 4);
@ -72,14 +68,18 @@ describe('estimateMediaTokensForMessage', () => {
});
it('estimates tokens from decoded dimensions (OpenAI path)', () => {
const content = [{ type: 'image_url', image_url: 'data:image/png;base64,VALID_PNG_LONG_DATA' }];
const content = [
{ type: 'image_url', image_url: 'data:image/png;base64,VALID_PNG_LONG_DATA' },
];
const result = estimateMediaTokensForMessage(content, false);
expect(result).toBeGreaterThan(0);
expect(result).not.toBe(1024);
});
it('estimates tokens from decoded dimensions (Claude path)', () => {
const content = [{ type: 'image_url', image_url: { url: 'data:image/png;base64,VALID_PNG_LONG_DATA' } }];
const content = [
{ type: 'image_url', image_url: { url: 'data:image/png;base64,VALID_PNG_LONG_DATA' } },
];
const result = estimateMediaTokensForMessage(content, true);
expect(result).toBeGreaterThan(0);
expect(result).not.toBe(1024);
@ -114,78 +114,92 @@ describe('estimateMediaTokensForMessage', () => {
describe('document blocks - LangChain format (source_type)', () => {
it('counts tokens for text source_type with getTokenCount', () => {
const content = [{
type: 'document',
source_type: 'text',
text: 'a'.repeat(400),
}];
const content = [
{
type: 'document',
source_type: 'text',
text: 'a'.repeat(400),
},
];
expect(estimateMediaTokensForMessage(content, false, fakeTokenCount)).toBe(100);
});
it('falls back to length/4 without getTokenCount', () => {
const content = [{
type: 'document',
source_type: 'text',
text: 'a'.repeat(400),
}];
const content = [
{
type: 'document',
source_type: 'text',
text: 'a'.repeat(400),
},
];
expect(estimateMediaTokensForMessage(content, false)).toBe(100);
});
it('estimates PDF pages for base64 source_type with application/pdf mime', () => {
const pdfData = 'x'.repeat(150_000);
const content = [{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: 'application/pdf',
}];
const content = [
{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: 'application/pdf',
},
];
const result = estimateMediaTokensForMessage(content, false);
expect(result).toBe(2 * 1500);
});
it('uses Claude PDF rate when isClaude is true', () => {
const pdfData = 'x'.repeat(150_000);
const content = [{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: 'application/pdf',
}];
const content = [
{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: 'application/pdf',
},
];
const result = estimateMediaTokensForMessage(content, true);
expect(result).toBe(2 * 2000);
});
it('defaults to PDF estimation for empty mime_type', () => {
const pdfData = 'x'.repeat(10);
const content = [{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: '',
}];
const content = [
{
type: 'document',
source_type: 'base64',
data: pdfData,
mime_type: '',
},
];
const result = estimateMediaTokensForMessage(content, false);
expect(result).toBe(1 * 1500);
});
it('handles image/* mime inside base64 source_type', () => {
const content = [{
type: 'document',
source_type: 'base64',
data: 'VALID_PNG',
mime_type: 'image/png',
}];
const content = [
{
type: 'document',
source_type: 'base64',
data: 'VALID_PNG',
mime_type: 'image/png',
},
];
const result = estimateMediaTokensForMessage(content, true);
expect(result).toBeGreaterThan(0);
expect(result).not.toBe(1024);
});
it('falls back to 1024 for undecodable image in base64 source_type', () => {
const content = [{
type: 'document',
source_type: 'base64',
data: 'BAD_DATA',
mime_type: 'image/jpeg',
}];
const content = [
{
type: 'document',
source_type: 'base64',
data: 'BAD_DATA',
mime_type: 'image/jpeg',
},
];
expect(estimateMediaTokensForMessage(content, false)).toBe(1024);
});
@ -197,50 +211,60 @@ describe('estimateMediaTokensForMessage', () => {
describe('document blocks - Anthropic format (source object)', () => {
it('counts tokens for text source type with getTokenCount', () => {
const content = [{
type: 'document',
source: { type: 'text', data: 'a'.repeat(800) },
}];
const content = [
{
type: 'document',
source: { type: 'text', data: 'a'.repeat(800) },
},
];
expect(estimateMediaTokensForMessage(content, true, fakeTokenCount)).toBe(200);
});
it('falls back to length/4 for text source without getTokenCount', () => {
const content = [{
type: 'document',
source: { type: 'text', data: 'a'.repeat(800) },
}];
const content = [
{
type: 'document',
source: { type: 'text', data: 'a'.repeat(800) },
},
];
expect(estimateMediaTokensForMessage(content, true)).toBe(200);
});
it('estimates PDF pages for base64 source with application/pdf', () => {
const pdfData = 'x'.repeat(225_000);
const content = [{
type: 'document',
source: { type: 'base64', data: pdfData, media_type: 'application/pdf' },
}];
const content = [
{
type: 'document',
source: { type: 'base64', data: pdfData, media_type: 'application/pdf' },
},
];
const result = estimateMediaTokensForMessage(content, true);
expect(result).toBe(3 * 2000);
});
it('returns URL fallback for url source type', () => {
const content = [{
type: 'document',
source: { type: 'url' },
}];
const content = [
{
type: 'document',
source: { type: 'url' },
},
];
expect(estimateMediaTokensForMessage(content, false)).toBe(2000);
});
it('handles content source type with nested images', () => {
const content = [{
type: 'document',
source: {
type: 'content',
content: [
{ type: 'image', source: { type: 'base64', data: 'VALID_PNG' } },
{ type: 'image', source: { type: 'base64', data: 'UNDECODABLE' } },
],
const content = [
{
type: 'document',
source: {
type: 'content',
content: [
{ type: 'image', source: { type: 'base64', data: 'VALID_PNG' } },
{ type: 'image', source: { type: 'base64', data: 'UNDECODABLE' } },
],
},
},
}];
];
const result = estimateMediaTokensForMessage(content, true);
expect(result).toBeGreaterThan(1024);
});
@ -253,11 +277,13 @@ describe('estimateMediaTokensForMessage', () => {
describe('file blocks', () => {
it('uses same logic as document for file type blocks', () => {
const content = [{
type: 'file',
source_type: 'text',
text: 'a'.repeat(120),
}];
const content = [
{
type: 'file',
source_type: 'text',
text: 'a'.repeat(120),
},
];
expect(estimateMediaTokensForMessage(content, false, fakeTokenCount)).toBe(30);
});

View file

@ -236,9 +236,7 @@ function countGoogleSearchTools(tools: unknown[] | undefined): number {
);
}
function countWebSearchDefinitions(
toolDefinitions: Array<{ name: string }> | undefined,
): number {
function countWebSearchDefinitions(toolDefinitions: Array<{ name: string }> | undefined): number {
return (
toolDefinitions?.filter((toolDefinition) => toolDefinition.name === Tools.web_search).length ??
0

View file

@ -82,13 +82,7 @@ describe('cleanCodeToolOutput', () => {
'Session files: 4 persisted file(s) are available in /mnt/data, including 1 image(s). ' +
'Use known /mnt/data paths directly in later code-tool calls. ' +
'The app displays files/images automatically; do not invent download links or wrap generated images in Markdown.';
const input = [
'stdout:',
'Report generated',
'',
'Generated files:',
summary,
].join('\n');
const input = ['stdout:', 'Report generated', '', 'Generated files:', summary].join('\n');
const output = cleanCodeToolOutput(input);
expect(output).toBe(input);
});

View file

@ -478,10 +478,10 @@ describe('recordCollectedUsage', () => {
collectedUsage,
});
expect(mockSpendTokens).toHaveBeenCalledWith(
expect.anything(),
{ promptTokens: 100, completionTokens: 50 },
);
expect(mockSpendTokens).toHaveBeenCalledWith(expect.anything(), {
promptTokens: 100,
completionTokens: 50,
});
expect(result?.output_tokens).toBe(50);
});
});

View file

@ -151,9 +151,7 @@ export async function updateInterfacePermissions({
} else if (isMemoryDisabled) {
logger.debug(`Role '${roleName}': Disabling memories as memory.disabled is true`);
} else if (isMemoryReenabling) {
logger.debug(
`Role '${roleName}': Re-enabling memories due to memory configuration`,
);
logger.debug(`Role '${roleName}': Re-enabling memories due to memory configuration`);
}
} else {
logger.debug(`Role '${roleName}': Preserving existing permissions for '${permType}'`);

View file

@ -71,12 +71,14 @@ describe('setupGracefulShutdown', () => {
});
it('closes the server and exits 0 on SIGTERM', async () => {
const closeSpy = jest.spyOn(server, 'close').mockImplementation((cb?: (err?: Error) => void) => {
if (cb) {
setImmediate(() => cb());
}
return server;
});
const closeSpy = jest
.spyOn(server, 'close')
.mockImplementation((cb?: (err?: Error) => void) => {
if (cb) {
setImmediate(() => cb());
}
return server;
});
setupGracefulShutdown(server);
triggerSignal('SIGTERM');
await flush();
@ -86,12 +88,14 @@ describe('setupGracefulShutdown', () => {
});
it('closes the server and exits 0 on SIGINT', async () => {
const closeSpy = jest.spyOn(server, 'close').mockImplementation((cb?: (err?: Error) => void) => {
if (cb) {
setImmediate(() => cb());
}
return server;
});
const closeSpy = jest
.spyOn(server, 'close')
.mockImplementation((cb?: (err?: Error) => void) => {
if (cb) {
setImmediate(() => cb());
}
return server;
});
setupGracefulShutdown(server);
triggerSignal('SIGINT');
await flush();

View file

@ -22,10 +22,7 @@ let httpServer: Server | null = null;
* listeners in registration order and any one of them can call
* `process.exit` before the HTTP server has finished closing.
*/
export function registerShutdownTask(
name: string,
fn: () => void | Promise<void>,
): void {
export function registerShutdownTask(name: string, fn: () => void | Promise<void>): void {
tasks.push({ name, fn });
}

View file

@ -1,21 +1,21 @@
jest.mock('@librechat/data-schemas', () => ({
logger: {
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
logger: {
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('~/crypto/jwt', () => ({
generateShortLivedToken: jest.fn().mockReturnValue('mock-jwt-token'),
generateShortLivedToken: jest.fn().mockReturnValue('mock-jwt-token'),
}));
jest.mock('axios', () => ({
delete: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
delete: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
}));
import axios from 'axios';
@ -26,125 +26,125 @@ import { generateShortLivedToken } from '~/crypto/jwt';
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedLogger = logger as jest.Mocked<typeof logger>;
const mockedGenerateShortLivedToken = generateShortLivedToken as jest.MockedFunction<
typeof generateShortLivedToken
typeof generateShortLivedToken
>;
describe('deleteRagFile', () => {
const originalEnv = process.env;
const originalEnv = process.env;
beforeEach(() => {
jest.clearAllMocks();
process.env = { ...originalEnv };
process.env.RAG_API_URL = 'http://localhost:8000';
});
beforeEach(() => {
jest.clearAllMocks();
process.env = { ...originalEnv };
process.env.RAG_API_URL = 'http://localhost:8000';
});
afterEach(() => {
process.env = originalEnv;
});
afterEach(() => {
process.env = originalEnv;
});
describe('when file is embedded and RAG_API_URL is configured', () => {
it('should delete the document from RAG API successfully', async () => {
const file = { file_id: 'file-123', embedded: true };
mockedAxios.delete.mockResolvedValueOnce({ status: 200 });
describe('when file is embedded and RAG_API_URL is configured', () => {
it('should delete the document from RAG API successfully', async () => {
const file = { file_id: 'file-123', embedded: true };
mockedAxios.delete.mockResolvedValueOnce({ status: 200 });
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(true);
expect(mockedGenerateShortLivedToken).toHaveBeenCalledWith('user123');
expect(mockedAxios.delete).toHaveBeenCalledWith('http://localhost:8000/documents', {
headers: {
Authorization: 'Bearer mock-jwt-token',
'Content-Type': 'application/json',
accept: 'application/json',
},
data: ['file-123'],
});
expect(mockedLogger.debug).toHaveBeenCalledWith(
'[deleteRagFile] Successfully deleted document file-123 from RAG API',
);
});
expect(result).toBe(true);
expect(mockedGenerateShortLivedToken).toHaveBeenCalledWith('user123');
expect(mockedAxios.delete).toHaveBeenCalledWith('http://localhost:8000/documents', {
headers: {
Authorization: 'Bearer mock-jwt-token',
'Content-Type': 'application/json',
accept: 'application/json',
},
data: ['file-123'],
});
expect(mockedLogger.debug).toHaveBeenCalledWith(
'[deleteRagFile] Successfully deleted document file-123 from RAG API',
);
});
it('should return true and log warning when document is not found (404)', async () => {
const file = { file_id: 'file-not-found', embedded: true };
const error = new Error('Not Found') as Error & { response?: { status?: number } };
error.response = { status: 404 };
mockedAxios.delete.mockRejectedValueOnce(error);
it('should return true and log warning when document is not found (404)', async () => {
const file = { file_id: 'file-not-found', embedded: true };
const error = new Error('Not Found') as Error & { response?: { status?: number } };
error.response = { status: 404 };
mockedAxios.delete.mockRejectedValueOnce(error);
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(true);
expect(mockedLogger.warn).toHaveBeenCalledWith(
'[deleteRagFile] Document file-not-found not found in RAG API, may have been deleted already',
);
});
expect(result).toBe(true);
expect(mockedLogger.warn).toHaveBeenCalledWith(
'[deleteRagFile] Document file-not-found not found in RAG API, may have been deleted already',
);
});
it('should return false and log error on other errors', async () => {
const file = { file_id: 'file-error', embedded: true };
const error = new Error('Server Error') as Error & { response?: { status?: number } };
error.response = { status: 500 };
mockedAxios.delete.mockRejectedValueOnce(error);
it('should return false and log error on other errors', async () => {
const file = { file_id: 'file-error', embedded: true };
const error = new Error('Server Error') as Error & { response?: { status?: number } };
error.response = { status: 500 };
mockedAxios.delete.mockRejectedValueOnce(error);
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith(
'[deleteRagFile] Error deleting document from RAG API:',
'Server Error',
);
});
});
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith(
'[deleteRagFile] Error deleting document from RAG API:',
'Server Error',
);
});
});
describe('when file is not embedded', () => {
it('should skip RAG deletion and return true', async () => {
const file = { file_id: 'file-123', embedded: false };
describe('when file is not embedded', () => {
it('should skip RAG deletion and return true', async () => {
const file = { file_id: 'file-123', embedded: false };
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
expect(mockedGenerateShortLivedToken).not.toHaveBeenCalled();
});
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
expect(mockedGenerateShortLivedToken).not.toHaveBeenCalled();
});
it('should skip RAG deletion when embedded is undefined', async () => {
const file = { file_id: 'file-123' };
it('should skip RAG deletion when embedded is undefined', async () => {
const file = { file_id: 'file-123' };
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
});
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
});
describe('when RAG_API_URL is not configured', () => {
it('should skip RAG deletion and return true', async () => {
delete process.env.RAG_API_URL;
const file = { file_id: 'file-123', embedded: true };
describe('when RAG_API_URL is not configured', () => {
it('should skip RAG deletion and return true', async () => {
delete process.env.RAG_API_URL;
const file = { file_id: 'file-123', embedded: true };
const result = await deleteRagFile({ userId: 'user123', file });
const result = await deleteRagFile({ userId: 'user123', file });
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
});
expect(result).toBe(true);
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
});
describe('userId handling', () => {
it('should return false when no userId is provided', async () => {
const file = { file_id: 'file-123', embedded: true };
describe('userId handling', () => {
it('should return false when no userId is provided', async () => {
const file = { file_id: 'file-123', embedded: true };
const result = await deleteRagFile({ userId: '', file });
const result = await deleteRagFile({ userId: '', file });
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith('[deleteRagFile] No user ID provided');
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith('[deleteRagFile] No user ID provided');
expect(mockedAxios.delete).not.toHaveBeenCalled();
});
it('should return false when userId is undefined', async () => {
const file = { file_id: 'file-123', embedded: true };
it('should return false when userId is undefined', async () => {
const file = { file_id: 'file-123', embedded: true };
const result = await deleteRagFile({ userId: undefined as unknown as string, file });
const result = await deleteRagFile({ userId: undefined as unknown as string, file });
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith('[deleteRagFile] No user ID provided');
});
});
expect(result).toBe(false);
expect(mockedLogger.error).toHaveBeenCalledWith('[deleteRagFile] No user ID provided');
});
});
});

View file

@ -3,13 +3,13 @@ import { logger } from '@librechat/data-schemas';
import { generateShortLivedToken } from '~/crypto/jwt';
interface DeleteRagFileParams {
/** The user ID. Required for authentication. If not provided, the function returns false and logs an error. */
userId: string;
/** The file object. Must have `embedded` and `file_id` properties. */
file: {
file_id: string;
embedded?: boolean;
};
/** The user ID. Required for authentication. If not provided, the function returns false and logs an error. */
userId: string;
/** The file object. Must have `embedded` and `file_id` properties. */
file: {
file_id: string;
embedded?: boolean;
};
}
/**
@ -23,38 +23,38 @@ interface DeleteRagFileParams {
* @returns Returns true if deletion was successful or skipped, false if there was an error.
*/
export async function deleteRagFile({ userId, file }: DeleteRagFileParams): Promise<boolean> {
if (!file.embedded || !process.env.RAG_API_URL) {
return true;
}
if (!file.embedded || !process.env.RAG_API_URL) {
return true;
}
if (!userId) {
logger.error('[deleteRagFile] No user ID provided');
return false;
}
if (!userId) {
logger.error('[deleteRagFile] No user ID provided');
return false;
}
const jwtToken = generateShortLivedToken(userId);
const jwtToken = generateShortLivedToken(userId);
try {
await axios.delete(`${process.env.RAG_API_URL}/documents`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
accept: 'application/json',
},
data: [file.file_id],
});
logger.debug(`[deleteRagFile] Successfully deleted document ${file.file_id} from RAG API`);
return true;
} catch (error) {
const axiosError = error as { response?: { status?: number }; message?: string };
if (axiosError.response?.status === 404) {
logger.warn(
`[deleteRagFile] Document ${file.file_id} not found in RAG API, may have been deleted already`,
);
return true;
} else {
logger.error('[deleteRagFile] Error deleting document from RAG API:', axiosError.message);
return false;
}
}
try {
await axios.delete(`${process.env.RAG_API_URL}/documents`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
accept: 'application/json',
},
data: [file.file_id],
});
logger.debug(`[deleteRagFile] Successfully deleted document ${file.file_id} from RAG API`);
return true;
} catch (error) {
const axiosError = error as { response?: { status?: number }; message?: string };
if (axiosError.response?.status === 404) {
logger.warn(
`[deleteRagFile] Document ${file.file_id} not found in RAG API, may have been deleted already`,
);
return true;
} else {
logger.error('[deleteRagFile] Error deleting document from RAG API:', axiosError.message);
return false;
}
}
}

View file

@ -260,11 +260,7 @@ export function restoreTenantContextFromReq(
logger.warn('[restoreTenantContextFromReq] Rejected system tenant for request route', {
path: req.path,
});
return rejectRequestWithUploadCleanup(
req,
res,
SYSTEM_TENANT_REJECTION_MESSAGE,
);
return rejectRequestWithUploadCleanup(req, res, SYSTEM_TENANT_REJECTION_MESSAGE);
}
const currentContext = tenantStorage.getStore();

View file

@ -512,9 +512,8 @@ describe('telemetry SDK lifecycle', () => {
expect(taskFn).toBeDefined();
await taskFn?.();
expect(emitWarningSpy).toHaveBeenCalledWith(
'OpenTelemetry shutdown failed: flush failed',
{ code: 'LIBRECHAT_OTEL' },
);
expect(emitWarningSpy).toHaveBeenCalledWith('OpenTelemetry shutdown failed: flush failed', {
code: 'LIBRECHAT_OTEL',
});
});
});

View file

@ -72,16 +72,20 @@ describe('createSseStreamTelemetry', () => {
res.writableEnded = true;
res.emit('finish');
expect(startSpan).toHaveBeenCalledWith('librechat.sse.stream', {
kind: SpanKind.INTERNAL,
attributes: expect.objectContaining({
'http.request.method': 'GET',
'http.route': '/api/agents/chat/stream/:streamId',
'librechat.stream.id': 'stream-1',
'librechat.stream.resume': false,
'librechat.stream.route': '/api/agents/chat/stream/:streamId',
}),
}, context.active());
expect(startSpan).toHaveBeenCalledWith(
'librechat.sse.stream',
{
kind: SpanKind.INTERNAL,
attributes: expect.objectContaining({
'http.request.method': 'GET',
'http.route': '/api/agents/chat/stream/:streamId',
'librechat.stream.id': 'stream-1',
'librechat.stream.resume': false,
'librechat.stream.route': '/api/agents/chat/stream/:streamId',
}),
},
context.active(),
);
expect(span.addEvent).toHaveBeenCalledWith('headers_flushed');
expect(span.addEvent).toHaveBeenCalledWith('first_chunk');
expect(span.addEvent).toHaveBeenCalledWith('final_event_emitted');

View file

@ -37,16 +37,20 @@ class SseStreamSpanTelemetry implements SseStreamTelemetry {
private plannedEndReason: StreamEndReason | undefined;
constructor({ isResume, req, res, streamId }: SseStreamTelemetryOptions) {
this.span = trace.getTracer('librechat.telemetry').startSpan(STREAM_SPAN_NAME, {
kind: SpanKind.INTERNAL,
attributes: {
'http.request.method': req.method,
'http.route': STREAM_ROUTE,
'librechat.stream.id': streamId,
'librechat.stream.resume': isResume,
'librechat.stream.route': STREAM_ROUTE,
this.span = trace.getTracer('librechat.telemetry').startSpan(
STREAM_SPAN_NAME,
{
kind: SpanKind.INTERNAL,
attributes: {
'http.request.method': req.method,
'http.route': STREAM_ROUTE,
'librechat.stream.id': streamId,
'librechat.stream.resume': isResume,
'librechat.stream.route': STREAM_ROUTE,
},
},
}, context.active());
context.active(),
);
res.once('finish', () => {
this.end(this.plannedEndReason ?? (this.errorEventEmitted ? 'server_error' : 'done'));
@ -138,8 +142,6 @@ class SseStreamSpanTelemetry implements SseStreamTelemetry {
}
}
export function createSseStreamTelemetry(
options: SseStreamTelemetryOptions,
): SseStreamTelemetry {
export function createSseStreamTelemetry(options: SseStreamTelemetryOptions): SseStreamTelemetry {
return new SseStreamSpanTelemetry(options);
}

View file

@ -11,10 +11,7 @@ import {
* Pre-computed regex for matching the Graph token placeholder.
* Escapes curly braces in the placeholder string for safe regex use.
*/
const GRAPH_TOKEN_REGEX = new RegExp(
GRAPH_TOKEN_PLACEHOLDER.replace(/[{}]/g, '\\$&'),
'g',
);
const GRAPH_TOKEN_REGEX = new RegExp(GRAPH_TOKEN_PLACEHOLDER.replace(/[{}]/g, '\\$&'), 'g');
/**
* Response from a Graph API token exchange.
@ -143,7 +140,9 @@ export async function resolveGraphTokenPlaceholder(
return value.replace(GRAPH_TOKEN_REGEX, graphTokenResponse.access_token);
}
logger.warn('[resolveGraphTokenPlaceholder] Graph token exchange did not return an access token');
logger.warn(
'[resolveGraphTokenPlaceholder] Graph token exchange did not return an access token',
);
return value;
} catch (error) {
logger.error('[resolveGraphTokenPlaceholder] Failed to exchange token for Graph API:', error);
@ -185,14 +184,13 @@ export async function resolveGraphTokensInRecord(
* @param graphOptions - Options for Graph token resolution
* @returns The options with Graph token placeholders resolved
*/
export async function preProcessGraphTokens<T extends {
headers?: Record<string, string>;
env?: Record<string, string>;
url?: string;
}>(
options: T,
graphOptions: GraphTokenOptions,
): Promise<T> {
export async function preProcessGraphTokens<
T extends {
headers?: Record<string, string>;
env?: Record<string, string>;
url?: string;
},
>(options: T, graphOptions: GraphTokenOptions): Promise<T> {
if (!mcpOptionsContainGraphTokenPlaceholder(options)) {
return options;
}

View file

@ -1,4 +1,12 @@
import React, { createContext, useContext, useEffect, useMemo, useCallback, useState, useRef } from 'react';
import React, {
createContext,
useContext,
useEffect,
useMemo,
useCallback,
useState,
useRef,
} from 'react';
import { IThemeRGB } from '../types';
import applyTheme from '../utils/applyTheme';

View file

@ -1,4 +1,10 @@
import { tenantStorage, getUserId, getRequestId, runAsSystem, scopedCacheKey } from './tenantContext';
import {
tenantStorage,
getUserId,
getRequestId,
runAsSystem,
scopedCacheKey,
} from './tenantContext';
describe('scopedCacheKey', () => {
it('returns base key when no ALS context is set', () => {
@ -32,12 +38,15 @@ describe('scopedCacheKey', () => {
});
it('preserves user and request context inside system tenant operations', async () => {
await tenantStorage.run({ tenantId: 'acme', userId: 'user-1', requestId: 'req-1' }, async () => {
await runAsSystem(async () => {
expect(getUserId()).toBe('user-1');
expect(getRequestId()).toBe('req-1');
expect(scopedCacheKey('KEY')).toBe('KEY');
});
});
await tenantStorage.run(
{ tenantId: 'acme', userId: 'user-1', requestId: 'req-1' },
async () => {
await runAsSystem(async () => {
expect(getUserId()).toBe('user-1');
expect(getRequestId()).toBe('req-1');
expect(scopedCacheKey('KEY')).toBe('KEY');
});
},
);
});
});

View file

@ -73,14 +73,16 @@ function collectRecords(value: unknown, predicate: (record: Record<string, unkno
}
function hasIndexedIdPlan(explain: unknown): boolean {
return collectRecords(explain, (record) => {
const stage = record.stage;
if (stage === 'IDHACK' || stage === 'EXPRESS_IXSCAN') return true;
if (stage !== 'IXSCAN') return false;
return (
collectRecords(explain, (record) => {
const stage = record.stage;
if (stage === 'IDHACK' || stage === 'EXPRESS_IXSCAN') return true;
if (stage !== 'IXSCAN') return false;
const keyPattern = record.keyPattern;
return isRecord(keyPattern) && keyPattern._id === 1;
}).length > 0;
const keyPattern = record.keyPattern;
return isRecord(keyPattern) && keyPattern._id === 1;
}).length > 0
);
}
beforeAll(async () => {

View file

@ -716,10 +716,7 @@ describe('UserGroup Methods - Detailed Tests', () => {
memberIds: ['m1', 'm2', 'm3'],
});
const updated = await methods.removeMemberById(
group._id as mongoose.Types.ObjectId,
'm2',
);
const updated = await methods.removeMemberById(group._id as mongoose.Types.ObjectId, 'm2');
expect(updated).toBeDefined();
expect(updated?.memberIds).toEqual(['m1', 'm3']);

View file

@ -15,8 +15,7 @@ export const activeExpirationFilter = <
export const legacyPermanentExpirationFilter = <
T extends RetentionFilterDocument = RetentionFilterDocument,
>(): FilterQuery<T> =>
({ expiredAt: null }) as FilterQuery<T>;
>(): FilterQuery<T> => ({ expiredAt: null }) as FilterQuery<T>;
export const buildRetentionVisibilityFilter = <
T extends RetentionFilterDocument = RetentionFilterDocument,