mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-26 17:31:27 +00:00
📎 fix: Scope Attachment Usage to Request Owner (#13557)
* fix: harden attachment usage handling * fix: sort file method imports * fix: clarify file usage scope
This commit is contained in:
parent
3571dfcf22
commit
75bbefb1c8
6 changed files with 184 additions and 12 deletions
|
|
@ -33,7 +33,8 @@ jest.mock('@librechat/agents', () => ({
|
|||
}));
|
||||
|
||||
import { Providers } from '@librechat/agents';
|
||||
import { EModelEndpoint, Tools } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, EToolResources, Tools } from 'librechat-data-provider';
|
||||
import type { IMongoFile } from '@librechat/data-schemas';
|
||||
import type { Agent } from 'librechat-data-provider';
|
||||
import type { ServerRequest, InitializeResultBase, EndpointTokenConfig } from '~/types';
|
||||
import type { InitializeAgentDbMethods } from '../initialize';
|
||||
|
|
@ -750,6 +751,44 @@ describe('initializeAgent — attachment scoping', () => {
|
|||
expect(result.requestAttachments).toEqual([requestFile]);
|
||||
expect(result.agentContextAttachments).toEqual([agentContextFile]);
|
||||
});
|
||||
|
||||
it('owner-scopes request file usage updates while preserving trusted tool files', async () => {
|
||||
const requestFile = { file_id: 'request-file', filename: 'request.txt' } as IMongoFile;
|
||||
const toolFile = { file_id: 'tool-file', filename: 'tool.txt' } as IMongoFile;
|
||||
const { agent, req, res, loadTools, db } = createMocks();
|
||||
|
||||
agent.tools = [EToolResources.file_search];
|
||||
mockExtractLibreChatParams.mockReturnValueOnce({
|
||||
resendFiles: true,
|
||||
maxContextTokens: undefined,
|
||||
modelOptions: { model: agent.model },
|
||||
});
|
||||
(db.getConvoFiles as jest.Mock).mockResolvedValueOnce([toolFile.file_id]);
|
||||
(db.getToolFilesByIds as jest.Mock).mockResolvedValueOnce([toolFile]);
|
||||
(db.updateFilesUsage as jest.Mock)
|
||||
.mockResolvedValueOnce([requestFile])
|
||||
.mockResolvedValueOnce([toolFile]);
|
||||
|
||||
await initializeAgent(
|
||||
{
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
loadTools,
|
||||
requestFiles: [requestFile],
|
||||
conversationId: 'conversation-1',
|
||||
endpointOption: { endpoint: EModelEndpoint.agents },
|
||||
allowedProviders: new Set([Providers.OPENAI]),
|
||||
isInitialAgent: true,
|
||||
},
|
||||
db,
|
||||
);
|
||||
|
||||
expect(db.updateFilesUsage).toHaveBeenNthCalledWith(1, [requestFile], undefined, {
|
||||
user: 'user-1',
|
||||
});
|
||||
expect(db.updateFilesUsage).toHaveBeenNthCalledWith(2, [toolFile]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeAgent — maxContextTokens', () => {
|
||||
|
|
|
|||
|
|
@ -389,7 +389,11 @@ export interface InitializeAgentParams {
|
|||
*/
|
||||
export interface InitializeAgentDbMethods extends EndpointDbMethods {
|
||||
/** Update usage tracking for multiple files */
|
||||
updateFilesUsage: (files: Array<{ file_id: string }>, fileIds?: string[]) => Promise<unknown[]>;
|
||||
updateFilesUsage: (
|
||||
files: Array<{ file_id: string }>,
|
||||
fileIds?: string[],
|
||||
options?: { user?: string },
|
||||
) => Promise<unknown[]>;
|
||||
/** Get files from database */
|
||||
getFiles: (filter: unknown, sort: unknown, select: unknown) => Promise<unknown[]>;
|
||||
/** Filter files by agent access permissions (ownership or agent attachment) */
|
||||
|
|
@ -539,6 +543,7 @@ export async function initializeAgent(
|
|||
allowedProviders,
|
||||
isInitialAgent = false,
|
||||
} = params;
|
||||
const requestFileOwnerId = req.user?.id;
|
||||
|
||||
if (!db) {
|
||||
throw new Error('initializeAgent requires db methods to be passed');
|
||||
|
|
@ -640,10 +645,27 @@ export async function initializeAgent(
|
|||
|
||||
const allToolFiles = toolFiles.concat(codeGeneratedFiles, userCodeFiles);
|
||||
if (requestFiles.length || allToolFiles.length) {
|
||||
currentFiles = (await db.updateFilesUsage(requestFiles.concat(allToolFiles))) as IMongoFile[];
|
||||
const requestUsageFiles =
|
||||
requestFiles.length && requestFileOwnerId
|
||||
? ((await db.updateFilesUsage(requestFiles, undefined, {
|
||||
user: requestFileOwnerId,
|
||||
})) as IMongoFile[])
|
||||
: [];
|
||||
const requestUsageFileIds = new Set(requestUsageFiles.map((file) => file.file_id));
|
||||
const trustedToolFiles = allToolFiles.filter(
|
||||
(file) => !requestUsageFileIds.has(file.file_id),
|
||||
);
|
||||
const toolUsageFiles = trustedToolFiles.length
|
||||
? ((await db.updateFilesUsage(trustedToolFiles)) as IMongoFile[])
|
||||
: [];
|
||||
currentFiles = requestUsageFiles.concat(toolUsageFiles);
|
||||
}
|
||||
} else if (requestFiles.length) {
|
||||
currentFiles = (await db.updateFilesUsage(requestFiles)) as IMongoFile[];
|
||||
currentFiles = requestFileOwnerId
|
||||
? ((await db.updateFilesUsage(requestFiles, undefined, {
|
||||
user: requestFileOwnerId,
|
||||
})) as IMongoFile[])
|
||||
: [];
|
||||
}
|
||||
|
||||
if (currentFiles && currentFiles.length) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue