From 049cdd8c8d94d4a546ba88d3972bb62d83e93f5f Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:51:03 +0200 Subject: [PATCH] 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. --- client/src/hooks/Files/useUploadOptions.ts | 12 ++--------- client/src/hooks/Input/useAttachItems.tsx | 14 +++++++----- client/src/utils/files.ts | 25 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/client/src/hooks/Files/useUploadOptions.ts b/client/src/hooks/Files/useUploadOptions.ts index c82176f531..2dcaf2aef5 100644 --- a/client/src/hooks/Files/useUploadOptions.ts +++ b/client/src/hooks/Files/useUploadOptions.ts @@ -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; diff --git a/client/src/hooks/Input/useAttachItems.tsx b/client/src/hooks/Input/useAttachItems.tsx index f7481e5237..78dfc787f3 100644 --- a/client/src/hooks/Input/useAttachItems.tsx +++ b/client/src/hooks/Input/useAttachItems.tsx @@ -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) => { diff --git a/client/src/utils/files.ts b/client/src/utils/files.ts index 05ebfaffba..fe887fa9a7 100644 --- a/client/src/utils/files.ts +++ b/client/src/utils/files.ts @@ -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,