diff --git a/api/server/routes/files/files.js b/api/server/routes/files/files.js index d2914825b0..bb2ae0bbe5 100644 --- a/api/server/routes/files/files.js +++ b/api/server/routes/files/files.js @@ -283,6 +283,10 @@ router.post('/', async (req, res) => { message += ': ' + error.message; } + if (error.message?.includes('Invalid file format')) { + message = error.message; + } + // TODO: delete remote file if it exists try { await fs.unlink(req.file.path); diff --git a/api/server/services/Files/MistralOCR/crud.js b/api/server/services/Files/MistralOCR/crud.js index 1bd48479af..cc01d803b0 100644 --- a/api/server/services/Files/MistralOCR/crud.js +++ b/api/server/services/Files/MistralOCR/crud.js @@ -47,7 +47,6 @@ async function uploadDocumentToMistral({ }) .then((res) => res.data) .catch((error) => { - logger.error('Error uploading document to Mistral:', error.message); throw error; }); } @@ -217,8 +216,16 @@ const uploadMistralOCR = async ({ req, file, file_id, entity_id }) => { images, }; } catch (error) { - const message = 'Error uploading document to Mistral OCR API'; - throw new Error(logAxiosError({ error, message })); + let message = 'Error uploading document to Mistral OCR API'; + const detail = error?.response?.data?.detail; + if (detail && detail !== '') { + message = detail; + } + + const responseMessage = error?.response?.data?.message; + throw new Error( + `${logAxiosError({ error, message })}${responseMessage && responseMessage !== '' ? ` - ${responseMessage}` : ''}`, + ); } }; diff --git a/api/server/services/Files/MistralOCR/crud.spec.js b/api/server/services/Files/MistralOCR/crud.spec.js index c3d2f46c40..8cc63cade2 100644 --- a/api/server/services/Files/MistralOCR/crud.spec.js +++ b/api/server/services/Files/MistralOCR/crud.spec.js @@ -124,13 +124,7 @@ describe('MistralOCR Service', () => { fileName: 'test.pdf', apiKey: 'test-api-key', }), - ).rejects.toThrow(); - - const { logger } = require('~/config'); - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining('Error uploading document to Mistral:'), - expect.any(String), - ); + ).rejects.toThrow(errorMessage); }); }); diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index 60b0128f7d..8dd2fbf865 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -5,6 +5,7 @@ const { Calculator } = require('@langchain/community/tools/calculator'); const { tool: toolFn, Tool, DynamicStructuredTool } = require('@langchain/core/tools'); const { Tools, + Constants, ErrorTypes, ContentTypes, imageGenTools, @@ -14,6 +15,7 @@ const { ImageVisionTool, openapiToFunction, AgentCapabilities, + defaultAgentCapabilities, validateAndParseOpenAPISpec, } = require('librechat-data-provider'); const { @@ -501,8 +503,22 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey }) } const endpointsConfig = await getEndpointsConfig(req); - const enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); - const checkCapability = (capability) => enabledCapabilities.has(capability); + let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); + /** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */ + if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) { + enabledCapabilities = new Set( + req.app?.locals?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities, + ); + } + const checkCapability = (capability) => { + const enabled = enabledCapabilities.has(capability); + if (!enabled) { + logger.warn( + `Capability "${capability}" disabled${capability === AgentCapabilities.tools ? '.' : ' despite configured tool.'} User: ${req.user.id} | Agent: ${agent.id}`, + ); + } + return enabled; + }; const areToolsEnabled = checkCapability(AgentCapabilities.tools); let includesWebSearch = false; diff --git a/api/server/utils/handleText.js b/api/server/utils/handleText.js index 125f27c6be..86c17f1dda 100644 --- a/api/server/utils/handleText.js +++ b/api/server/utils/handleText.js @@ -4,10 +4,10 @@ const { Capabilities, EModelEndpoint, isAgentsEndpoint, - AgentCapabilities, isAssistantsEndpoint, defaultRetrievalModels, defaultAssistantsVersion, + defaultAgentCapabilities, } = require('librechat-data-provider'); const { Providers } = require('@librechat/agents'); const partialRight = require('lodash/partialRight'); @@ -197,16 +197,7 @@ function generateConfig(key, baseURL, endpoint) { } if (agents) { - config.capabilities = [ - AgentCapabilities.execute_code, - AgentCapabilities.file_search, - AgentCapabilities.web_search, - AgentCapabilities.artifacts, - AgentCapabilities.actions, - AgentCapabilities.tools, - AgentCapabilities.chain, - AgentCapabilities.ocr, - ]; + config.capabilities = defaultAgentCapabilities; } if (assistants && endpoint === EModelEndpoint.azureAssistants) { diff --git a/api/utils/axios.js b/api/utils/axios.js index 2beff55e1f..91c1fbb223 100644 --- a/api/utils/axios.js +++ b/api/utils/axios.js @@ -29,7 +29,7 @@ const logAxiosError = ({ message, error }) => { requestInfo: { method, url }, stack, }); - } else if (error?.message?.includes('Cannot read properties of undefined (reading \'status\')')) { + } else if (error?.message?.includes("Cannot read properties of undefined (reading 'status')")) { logMessage = `${message} It appears the request timed out or was unsuccessful: ${error.message}`; logger.error(logMessage, { stack }); } else { diff --git a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx index 6fdd86c2b2..85df07f24f 100644 --- a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx @@ -1,7 +1,7 @@ import * as Ariakit from '@ariakit/react'; import React, { useRef, useState, useMemo } from 'react'; -import { EToolResources, EModelEndpoint } from 'librechat-data-provider'; import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react'; +import { EToolResources, EModelEndpoint, defaultAgentCapabilities } from 'librechat-data-provider'; import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components'; import { useGetEndpointsQuery } from '~/data-provider'; import { useLocalize, useFileHandling } from '~/hooks'; @@ -22,6 +22,10 @@ const AttachFile = ({ disabled }: AttachFileProps) => { overrideEndpoint: EModelEndpoint.agents, }); + /** TODO: Ephemeral Agent Capabilities + * Allow defining agent capabilities on a per-endpoint basis + * Use definition for agents endpoint for ephemeral agents + * */ const capabilities = useMemo( () => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [], [endpointsConfig], diff --git a/client/src/components/Web/Sources.tsx b/client/src/components/Web/Sources.tsx index ea631a1fab..46c781afb3 100644 --- a/client/src/components/Web/Sources.tsx +++ b/client/src/components/Web/Sources.tsx @@ -190,12 +190,8 @@ function SourcesGroup({ sources, limit = 3 }: { sources: ValidSource[]; limit?: const remainingSources = sources.slice(limit); const hasMoreSources = remainingSources.length > 0; - /** Calculate grid columns based on number of items (including the +X sources button if present) */ - const totalItems = hasMoreSources ? visibleSources.length + 1 : visibleSources.length; - const gridCols = `grid-cols-${Math.min(totalItems, 4)}`; - return ( -