mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-26 10:15:05 +00:00
🧱 fix: Apply the endpoint file policy to persistent agent context files
A saved agent's persistent context files are read inside primeResources, after the caller has already applied the endpoint policy to the request's own files and to the deferred provisioning candidates. Nothing filtered them, so once the agent's provider or file configuration changed, a file the current endpoint disables or refuses by size or MIME type could still be queued and sent to the Code API or RAG on the next tool call. The caller now passes its endpoint policy down, since it owns the endpoint resolution and primeResources owns the read. Both the no-attachment turn and the turn that also carries new attachments go through it.
This commit is contained in:
parent
25eea4832d
commit
0115f85591
3 changed files with 90 additions and 0 deletions
|
|
@ -1310,6 +1310,12 @@ export async function initializeAgent(
|
|||
loadCodeApiKey: db.loadCodeApiKey,
|
||||
provisionCandidates: deferredProvisionFiles as unknown as TFile[],
|
||||
legacyFileUploadUX,
|
||||
filterByEndpointPolicy: (files) =>
|
||||
filterFilesByEndpointRuntimeConfig(appConfig, {
|
||||
files: files as unknown as IMongoFile[],
|
||||
endpoint: agent.endpoint ?? '',
|
||||
endpointType: endpointFileType,
|
||||
}) as unknown as TFile[],
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -97,6 +97,80 @@ describe('primeResources', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when the endpoint policy rejects a persistent context file', () => {
|
||||
it('keeps it out of provisioning and out of attachments', async () => {
|
||||
/* These files are read inside primeResources, so the caller never sees them to
|
||||
* filter. A provider or policy change since they were attached must still stop
|
||||
* their bytes reaching the Code API or RAG. */
|
||||
const rejected: TFile[] = [
|
||||
{
|
||||
user: 'user1',
|
||||
file_id: 'stale-context-file',
|
||||
filename: 'legacy.csv',
|
||||
filepath: '/uploads/legacy.csv',
|
||||
object: 'file' as const,
|
||||
type: 'text/csv',
|
||||
bytes: 1024,
|
||||
embedded: false,
|
||||
usage: 0,
|
||||
source: FileSources.local,
|
||||
},
|
||||
];
|
||||
mockGetFiles.mockResolvedValue(rejected);
|
||||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
filterFiles: mockFilterFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
tool_resources: { [EToolResources.context]: { file_ids: ['stale-context-file'] } },
|
||||
agentId: 'agent_test',
|
||||
enabledToolResources: new Set([EToolResources.execute_code, EToolResources.file_search]),
|
||||
filterByEndpointPolicy: () => [],
|
||||
});
|
||||
|
||||
expect(result.provisionState).toBeUndefined();
|
||||
expect(result.attachments).toBeUndefined();
|
||||
});
|
||||
|
||||
it('still provisions a persistent context file the policy allows', async () => {
|
||||
const allowed: TFile[] = [
|
||||
{
|
||||
user: 'user1',
|
||||
file_id: 'live-context-file',
|
||||
filename: 'data.csv',
|
||||
filepath: '/uploads/data.csv',
|
||||
object: 'file' as const,
|
||||
type: 'text/csv',
|
||||
bytes: 1024,
|
||||
embedded: false,
|
||||
usage: 0,
|
||||
source: FileSources.local,
|
||||
},
|
||||
];
|
||||
mockGetFiles.mockResolvedValue(allowed);
|
||||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
filterFiles: mockFilterFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
tool_resources: { [EToolResources.context]: { file_ids: ['live-context-file'] } },
|
||||
agentId: 'agent_test',
|
||||
enabledToolResources: new Set([EToolResources.execute_code, EToolResources.file_search]),
|
||||
filterByEndpointPolicy: (files) => files,
|
||||
});
|
||||
|
||||
expect(result.provisionState?.codeEnvFiles.map((f) => f.file_id)).toEqual([
|
||||
'live-context-file',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `context` capability is disabled', () => {
|
||||
it('should not fetch context files even if tool_resources has context file_ids', async () => {
|
||||
(mockAppConfig.endpoints![EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
|
||||
|
|
|
|||
|
|
@ -480,6 +480,7 @@ export const primeResources = async ({
|
|||
loadCodeApiKey,
|
||||
provisionCandidates,
|
||||
legacyFileUploadUX,
|
||||
filterByEndpointPolicy,
|
||||
}: {
|
||||
req?: ServerRequest;
|
||||
principal?: Pick<IUser, 'id' | 'role'>;
|
||||
|
|
@ -502,6 +503,11 @@ export const primeResources = async ({
|
|||
provisionCandidates?: Array<TFile>;
|
||||
/** True when this endpoint still shows the explicit upload-destination chooser. */
|
||||
legacyFileUploadUX?: boolean;
|
||||
/** Applies the current endpoint's file policy. Persistent agent files are read here
|
||||
* rather than by the caller, so the caller has no chance to filter them itself and
|
||||
* a provider or policy change since they were attached would otherwise let their
|
||||
* bytes reach the Code API or RAG. */
|
||||
filterByEndpointPolicy?: (files: Array<TFile>) => Array<TFile>;
|
||||
}): Promise<{
|
||||
attachments: Array<TFile | undefined> | undefined;
|
||||
requestAttachments: Array<TFile | undefined> | undefined;
|
||||
|
|
@ -610,6 +616,10 @@ export const primeResources = async ({
|
|||
agentId,
|
||||
});
|
||||
}
|
||||
|
||||
if (filterByEndpointPolicy) {
|
||||
persistedResourceFiles = filterByEndpointPolicy(persistedResourceFiles);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of persistedResourceFiles) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue