🐛 fix: Correct scope + tool visibility for lazily provisioned files

Addresses Codex P1 findings on the lazy provisioning path:
- Scope per file like the direct upload path: current-message chat attachments
  (context=message_attachment) provision to the user's code sandbox / unscoped
  vector index (entity_id undefined); only agent setup files use entity_id=agentId.
  Previously every lazily provisioned file was agent-scoped, so a user's chat
  attachment landed in the agent sandbox and file_search queries (unscoped for
  fromAgent=false) missed the agent-scoped embedding.
- Surface provisioned files to the tool loaded immediately after by adding them to
  ctx.tool_resources.<resource>.files, which primeCodeFiles/primeFiles read. Before,
  a freshly provisioned unified upload was invisible to the first code/file_search
  call even though provisioning had completed.
This commit is contained in:
Danny Avila 2026-07-09 11:29:08 -04:00
parent 900778f044
commit b1a56a143b

View file

@ -17,9 +17,11 @@ const {
buildAgentContextAttachmentsByAgentId,
} = require('@librechat/api');
const {
FileContext,
Permissions,
ResourceType,
EModelEndpoint,
EToolResources,
PermissionBits,
PermissionTypes,
MAX_SUBAGENT_DEPTH,
@ -275,6 +277,29 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
return;
}
/** Current-message chat attachments stay in the user's sandbox / unscoped vector
* index (matching the direct message_file upload path); only agent setup files
* are scoped to the agent. */
const entityIdForFile = (file) =>
file.context === FileContext.message_attachment ? undefined : agentId;
/** Surface a just-provisioned file to the tool loaded immediately after: the code
* and file_search primers read `tool_resources.<resource>.files`. */
if (!ctx.tool_resources) {
ctx.tool_resources = {};
}
const addProvisionedFile = (file, resourceType) => {
if (!file.file_id) {
return;
}
const resource = ctx.tool_resources[resourceType] ?? {};
const files = resource.files ? [...resource.files] : [];
if (!files.some((existing) => existing.file_id === file.file_id)) {
files.push(file);
}
ctx.tool_resources[resourceType] = { ...resource, files };
};
/** @type {import('@librechat/api').TFileUpdate[]} */
const pendingUpdates = [];
@ -284,9 +309,10 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
const { codeEnvRef, fileUpdate } = await provisionToCodeEnv({
req,
file,
entity_id: agentId,
entity_id: entityIdForFile(file),
});
file.metadata = { ...file.metadata, codeEnvRef };
addProvisionedFile(file, EToolResources.execute_code);
pendingUpdates.push(fileUpdate);
}),
);
@ -301,9 +327,14 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
if (needsSearch && provisionState.vectorDBFiles.length > 0) {
const results = await Promise.allSettled(
provisionState.vectorDBFiles.map(async (file) => {
const result = await provisionToVectorDB({ req, file, entity_id: agentId });
const result = await provisionToVectorDB({
req,
file,
entity_id: entityIdForFile(file),
});
if (result.embedded) {
file.embedded = true;
addProvisionedFile(file, EToolResources.file_search);
if (result.fileUpdate) {
pendingUpdates.push(result.fileUpdate);
}