fix: show tool upload destinations in the + menu

They were only reachable by dropping a file on the composer. The menu and
the drop router now resolve the same allowances, so an ordinary chat can
upload to file search or the code environment.
This commit is contained in:
Marco Beretta 2026-07-27 00:51:03 +02:00
parent c56bdd5f09
commit 049cdd8c8d
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
3 changed files with 36 additions and 15 deletions

View file

@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import {
Tools,
Constants,
mergeFileConfig,
getEndpointFileConfig,
@ -9,13 +8,12 @@ import {
} from 'librechat-data-provider';
import type { EToolResources } from 'librechat-data-provider';
import useAgentToolPermissions from '~/hooks/Agents/useAgentToolPermissions';
import { getViableUploadOptions, getUploadToolAllowances } from '~/utils';
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
import useGetAgentsConfig from '~/hooks/Agents/useGetAgentsConfig';
import { useGetFileConfig } from '~/data-provider';
import { ephemeralAgentByConvoId } from '~/store';
import { getViableUploadOptions } from '~/utils';
import { useDragDropContext } from '~/Providers';
import { isEphemeralAgent } from '~/common';
/**
* Resolves which upload destinations a file set can be routed to, plus whether uploads are
@ -34,13 +32,7 @@ export default function useUploadOptions() {
select: (data) => mergeFileConfig(data),
});
/**
* Tools are offerable unless a saved agent omits them; in direct/ephemeral chats selecting
* one enables the ephemeral capability, matching the original drag-and-drop behavior.
*/
const isSavedAgent = agentId != null && agentId !== '' && !isEphemeralAgent(agentId);
const fileSearchAllowedByAgent = !isSavedAgent || (tools?.includes(Tools.file_search) ?? false);
const codeAllowedByAgent = !isSavedAgent || (tools?.includes(Tools.execute_code) ?? false);
const { fileSearchAllowedByAgent, codeAllowedByAgent } = getUploadToolAllowances(agentId, tools);
const endpointFileConfig = getEndpointFileConfig({ fileConfig, endpoint, endpointType });
const uploadsDisabled = endpointFileConfig.disabled === true;

View file

@ -1,6 +1,6 @@
import React, { useRef, useMemo, useState, useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { SharePointIcon } from '@librechat/client';
import { useRecoilState, useRecoilValue } from 'recoil';
import {
FileSearch,
ImageUpIcon,
@ -28,6 +28,7 @@ import { useSharePointFileHandlingNoChatContext } from '~/hooks/Files/useSharePo
import { useAgentToolPermissions, useAgentCapabilities, useGetAgentsConfig } from '~/hooks';
import { useFileHandlingNoChatContext } from '~/hooks/Files';
import { useGetStartupConfig } from '~/data-provider';
import { getUploadToolAllowances } from '~/utils';
import { ephemeralAgentByConvoId } from '~/store';
import useLocalize from '~/hooks/useLocalize';
@ -113,6 +114,7 @@ export default function useAttachItems({
{ files, setFiles, setFilesLoading, conversation },
);
const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(conversationId));
const { agentsConfig } = useGetAgentsConfig();
const { data: startupConfig } = useGetStartupConfig();
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
@ -122,10 +124,12 @@ export default function useAttachItems({
* Use definition for agents endpoint for ephemeral agents
* */
const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
const { fileSearchAllowedByAgent, codeAllowedByAgent, provider } = useAgentToolPermissions(
agentId,
undefined,
);
const { tools, provider } = useAgentToolPermissions(agentId, ephemeralAgent);
/* The same allowances the drag-and-drop router resolves, so a file reaches
the same destinations whether it is dropped on the composer or picked
through this menu. Passing no ephemeral agent here used to leave both tool
destinations permanently hidden in an ordinary chat. */
const { fileSearchAllowedByAgent, codeAllowedByAgent } = getUploadToolAllowances(agentId, tools);
const handleUploadClick = useCallback(
(fileType?: FileUploadType) => {

View file

@ -7,6 +7,7 @@ import {
SheetPaths,
} from '@librechat/client';
import {
Tools,
megabyte,
Providers,
QueryKeys,
@ -15,6 +16,7 @@ import {
EToolResources,
EModelEndpoint,
retrievalMimeTypes,
isEphemeralAgentId,
isBedrockDocumentType,
isPermissiveMimeConfig,
codeInterpreterMimeTypes,
@ -392,6 +394,29 @@ const isContextType = (type: string, fileConfig: FileConfig | null): boolean =>
* Each option requires every file to be valid for it, so the caller can decide between
* auto-routing (one option), prompting (multiple), or rejecting (none).
*/
/**
* Which tool destinations an upload may be routed to, before the files
* themselves are considered.
*
* A saved agent's tool list is the authority: it can only receive uploads for
* the tools it was built with. Everywhere else the destination is offered
* whether or not the tool is currently switched on, because choosing it is what
* switches it on.
*
* Shared by the `+` menu and the drag-and-drop router so a file has the same
* destinations however it arrives.
*/
export const getUploadToolAllowances = (
agentId: string | null | undefined,
tools: string[] | undefined,
): { fileSearchAllowedByAgent: boolean; codeAllowedByAgent: boolean } => {
const isSavedAgent = agentId != null && agentId !== '' && !isEphemeralAgentId(agentId);
return {
fileSearchAllowedByAgent: !isSavedAgent || (tools?.includes(Tools.file_search) ?? false),
codeAllowedByAgent: !isSavedAgent || (tools?.includes(Tools.execute_code) ?? false),
};
};
export const getViableUploadOptions = (
fileList: File[],
ctx: UploadOptionContext,