🔁 refactor: Capabilities for Tools/File handling for Direct Endpoints (#8253)

* feat: add useAgentCapabilities hook to manage agent capabilities

* refactor: move  agents and endpoints configuration to AgentPanel context provider

* refactor: implement useGetAgentsConfig hook for consolidated agents and endpoints management

* refactor: enhance ToolsDropdown to utilize agent capabilities and streamline dropdown item rendering

* chore: reorder return values in useAgentCapabilities for improved clarity

* refactor: enhance agent capabilities handling in AttachFileMenu and update file handling logic to allow capabilities to be used for non-agents endpoints
This commit is contained in:
Danny Avila 2025-07-04 14:51:26 -04:00 committed by GitHub
parent a288ad1d9c
commit f5511e4a4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 308 additions and 250 deletions

View file

@ -1,4 +1,10 @@
const { CacheKeys, EModelEndpoint, orderEndpointsConfig } = require('librechat-data-provider'); const {
CacheKeys,
EModelEndpoint,
isAgentsEndpoint,
orderEndpointsConfig,
defaultAgentCapabilities,
} = require('librechat-data-provider');
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig'); const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
const loadConfigEndpoints = require('./loadConfigEndpoints'); const loadConfigEndpoints = require('./loadConfigEndpoints');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
@ -80,8 +86,12 @@ async function getEndpointsConfig(req) {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
const checkCapability = async (req, capability) => { const checkCapability = async (req, capability) => {
const isAgents = isAgentsEndpoint(req.body?.original_endpoint || req.body?.endpoint);
const endpointsConfig = await getEndpointsConfig(req); const endpointsConfig = await getEndpointsConfig(req);
const capabilities = endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []; const capabilities =
isAgents || endpointsConfig?.[EModelEndpoint.agents]?.capabilities != null
? (endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [])
: defaultAgentCapabilities;
return capabilities.includes(capability); return capabilities.includes(capability);
}; };

View file

@ -1,9 +1,9 @@
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { Constants, EModelEndpoint } from 'librechat-data-provider'; import { Constants, EModelEndpoint } from 'librechat-data-provider';
import type { TPlugin, AgentToolType, Action, MCP } from 'librechat-data-provider'; import type { MCP, Action, TPlugin, AgentToolType } from 'librechat-data-provider';
import type { AgentPanelContextType } from '~/common'; import type { AgentPanelContextType } from '~/common';
import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider'; import { useAvailableToolsQuery, useGetActionsQuery } from '~/data-provider';
import { useLocalize } from '~/hooks'; import { useLocalize, useGetAgentsConfig } from '~/hooks';
import { Panel } from '~/common'; import { Panel } from '~/common';
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined); const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
@ -75,21 +75,25 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
{} as Record<string, AgentToolType & { tools?: AgentToolType[] }>, {} as Record<string, AgentToolType & { tools?: AgentToolType[] }>,
); );
const value = { const { agentsConfig, endpointsConfig } = useGetAgentsConfig();
action,
setAction, const value: AgentPanelContextType = {
mcp, mcp,
setMcp,
mcps, mcps,
setMcps,
activePanel,
setActivePanel,
setCurrentAgentId,
agent_id,
groupedTools,
/** Query data for actions and tools */ /** Query data for actions and tools */
actions,
tools, tools,
action,
setMcp,
actions,
setMcps,
agent_id,
setAction,
activePanel,
groupedTools,
agentsConfig,
setActivePanel,
endpointsConfig,
setCurrentAgentId,
}; };
return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>; return <AgentPanelContext.Provider value={value}>{children}</AgentPanelContext.Provider>;

View file

@ -1,17 +1,25 @@
import React, { createContext, useContext, useEffect, useRef } from 'react'; import React, { createContext, useContext, useEffect, useRef } from 'react';
import { Tools, LocalStorageKeys, AgentCapabilities, Constants } from 'librechat-data-provider';
import { useMCPSelect, useToolToggle, useCodeApiKeyForm, useSearchApiKeyForm } from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { Tools, Constants, LocalStorageKeys, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint } from 'librechat-data-provider';
import {
useSearchApiKeyForm,
useGetAgentsConfig,
useCodeApiKeyForm,
useToolToggle,
useMCPSelect,
} from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
import { ephemeralAgentByConvoId } from '~/store'; import { ephemeralAgentByConvoId } from '~/store';
interface BadgeRowContextType { interface BadgeRowContextType {
conversationId?: string | null; conversationId?: string | null;
agentsConfig?: TAgentsEndpoint | null;
mcpSelect: ReturnType<typeof useMCPSelect>; mcpSelect: ReturnType<typeof useMCPSelect>;
webSearch: ReturnType<typeof useToolToggle>; webSearch: ReturnType<typeof useToolToggle>;
codeInterpreter: ReturnType<typeof useToolToggle>;
fileSearch: ReturnType<typeof useToolToggle>;
artifacts: ReturnType<typeof useToolToggle>; artifacts: ReturnType<typeof useToolToggle>;
fileSearch: ReturnType<typeof useToolToggle>;
codeInterpreter: ReturnType<typeof useToolToggle>;
codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>; codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>;
searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>; searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>;
startupConfig: ReturnType<typeof useGetStartupConfig>['data']; startupConfig: ReturnType<typeof useGetStartupConfig>['data'];
@ -40,6 +48,7 @@ export default function BadgeRowProvider({
}: BadgeRowProviderProps) { }: BadgeRowProviderProps) {
const hasInitializedRef = useRef(false); const hasInitializedRef = useRef(false);
const lastKeyRef = useRef<string>(''); const lastKeyRef = useRef<string>('');
const { agentsConfig } = useGetAgentsConfig();
const key = conversationId ?? Constants.NEW_CONVO; const key = conversationId ?? Constants.NEW_CONVO;
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key)); const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
@ -165,8 +174,9 @@ export default function BadgeRowProvider({
const value: BadgeRowContextType = { const value: BadgeRowContextType = {
mcpSelect, mcpSelect,
webSearch, webSearch,
fileSearch,
artifacts, artifacts,
fileSearch,
agentsConfig,
startupConfig, startupConfig,
conversationId, conversationId,
codeApiKeyForm, codeApiKeyForm,

View file

@ -206,9 +206,7 @@ export type AgentPanelProps = {
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>; setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>; setMcp: React.Dispatch<React.SetStateAction<t.MCP | undefined>>;
setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>; setAction: React.Dispatch<React.SetStateAction<t.Action | undefined>>;
endpointsConfig?: t.TEndpointsConfig;
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>; setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
agentsConfig?: t.TAgentsEndpoint | null;
}; };
export type AgentPanelContextType = { export type AgentPanelContextType = {
@ -225,6 +223,8 @@ export type AgentPanelContextType = {
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>; setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
groupedTools?: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>; groupedTools?: Record<string, t.AgentToolType & { tools?: t.AgentToolType[] }>;
agent_id?: string; agent_id?: string;
agentsConfig?: t.TAgentsEndpoint | null;
endpointsConfig?: t.TEndpointsConfig | null;
}; };
export type AgentModelPanelProps = { export type AgentModelPanelProps = {

View file

@ -2,11 +2,10 @@ import { useSetRecoilState } from 'recoil';
import * as Ariakit from '@ariakit/react'; import * as Ariakit from '@ariakit/react';
import React, { useRef, useState, useMemo } from 'react'; import React, { useRef, useState, useMemo } from 'react';
import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react'; import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react';
import { EToolResources, EModelEndpoint, defaultAgentCapabilities } from 'librechat-data-provider';
import type { EndpointFileConfig } from 'librechat-data-provider'; import type { EndpointFileConfig } from 'librechat-data-provider';
import { useLocalize, useGetAgentsConfig, useFileHandling, useAgentCapabilities } from '~/hooks';
import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components'; import { FileUpload, TooltipAnchor, DropdownPopup, AttachmentIcon } from '~/components';
import { EToolResources, EModelEndpoint } from 'librechat-data-provider';
import { useGetEndpointsQuery } from '~/data-provider';
import { useLocalize, useFileHandling } from '~/hooks';
import { ephemeralAgentByConvoId } from '~/store'; import { ephemeralAgentByConvoId } from '~/store';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -23,20 +22,17 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
const [isPopoverActive, setIsPopoverActive] = useState(false); const [isPopoverActive, setIsPopoverActive] = useState(false);
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(conversationId)); const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(conversationId));
const [toolResource, setToolResource] = useState<EToolResources | undefined>(); const [toolResource, setToolResource] = useState<EToolResources | undefined>();
const { data: endpointsConfig } = useGetEndpointsQuery();
const { handleFileChange } = useFileHandling({ const { handleFileChange } = useFileHandling({
overrideEndpoint: EModelEndpoint.agents, overrideEndpoint: EModelEndpoint.agents,
overrideEndpointFileConfig: endpointFileConfig, overrideEndpointFileConfig: endpointFileConfig,
}); });
const { agentsConfig } = useGetAgentsConfig();
/** TODO: Ephemeral Agent Capabilities /** TODO: Ephemeral Agent Capabilities
* Allow defining agent capabilities on a per-endpoint basis * Allow defining agent capabilities on a per-endpoint basis
* Use definition for agents endpoint for ephemeral agents * Use definition for agents endpoint for ephemeral agents
* */ * */
const capabilities = useMemo( const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
() => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [],
[endpointsConfig],
);
const handleUploadClick = (isImage?: boolean) => { const handleUploadClick = (isImage?: boolean) => {
if (!inputRef.current) { if (!inputRef.current) {
@ -60,7 +56,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
}, },
]; ];
if (capabilities.includes(EToolResources.ocr)) { if (capabilities.ocrEnabled) {
items.push({ items.push({
label: localize('com_ui_upload_ocr_text'), label: localize('com_ui_upload_ocr_text'),
onClick: () => { onClick: () => {
@ -71,7 +67,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
}); });
} }
if (capabilities.includes(EToolResources.file_search)) { if (capabilities.fileSearchEnabled) {
items.push({ items.push({
label: localize('com_ui_upload_file_search'), label: localize('com_ui_upload_file_search'),
onClick: () => { onClick: () => {
@ -83,7 +79,7 @@ const AttachFileMenu = ({ disabled, conversationId, endpointFileConfig }: Attach
}); });
} }
if (capabilities.includes(EToolResources.execute_code)) { if (capabilities.codeEnabled) {
items.push({ items.push({
label: localize('com_ui_upload_code_files'), label: localize('com_ui_upload_code_files'),
onClick: () => { onClick: () => {

View file

@ -2,12 +2,18 @@ import React, { useState, useMemo, useCallback } from 'react';
import * as Ariakit from '@ariakit/react'; import * as Ariakit from '@ariakit/react';
import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react'; import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react';
import type { MenuItemProps } from '~/common'; import type { MenuItemProps } from '~/common';
import { Permissions, PermissionTypes, AuthType, ArtifactModes } from 'librechat-data-provider'; import {
AuthType,
Permissions,
ArtifactModes,
PermissionTypes,
defaultAgentCapabilities,
} from 'librechat-data-provider';
import { TooltipAnchor, DropdownPopup } from '~/components'; import { TooltipAnchor, DropdownPopup } from '~/components';
import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks';
import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu'; import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu';
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu'; import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
import { PinIcon, VectorIcon } from '~/components/svg'; import { PinIcon, VectorIcon } from '~/components/svg';
import { useLocalize, useHasAccess } from '~/hooks';
import { useBadgeRowContext } from '~/Providers'; import { useBadgeRowContext } from '~/Providers';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -24,11 +30,15 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
mcpSelect, mcpSelect,
artifacts, artifacts,
fileSearch, fileSearch,
agentsConfig,
startupConfig, startupConfig,
codeApiKeyForm, codeApiKeyForm,
codeInterpreter, codeInterpreter,
searchApiKeyForm, searchApiKeyForm,
} = useBadgeRowContext(); } = useBadgeRowContext();
const { codeEnabled, webSearchEnabled, artifactsEnabled, fileSearchEnabled } =
useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
const { setIsDialogOpen: setIsCodeDialogOpen, menuTriggerRef: codeMenuTriggerRef } = const { setIsDialogOpen: setIsCodeDialogOpen, menuTriggerRef: codeMenuTriggerRef } =
codeApiKeyForm; codeApiKeyForm;
const { setIsDialogOpen: setIsSearchDialogOpen, menuTriggerRef: searchMenuTriggerRef } = const { setIsDialogOpen: setIsSearchDialogOpen, menuTriggerRef: searchMenuTriggerRef } =
@ -128,9 +138,10 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
const mcpPlaceholder = startupConfig?.interface?.mcpServers?.placeholder; const mcpPlaceholder = startupConfig?.interface?.mcpServers?.placeholder;
const dropdownItems = useMemo(() => { const dropdownItems: MenuItemProps[] = [];
const items: MenuItemProps[] = [];
items.push({ if (fileSearchEnabled) {
dropdownItems.push({
onClick: handleFileSearchToggle, onClick: handleFileSearchToggle,
hideOnClick: false, hideOnClick: false,
render: (props) => ( render: (props) => (
@ -159,9 +170,10 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
</div> </div>
), ),
}); });
}
if (canUseWebSearch) { if (canUseWebSearch && webSearchEnabled) {
items.push({ dropdownItems.push({
onClick: handleWebSearchToggle, onClick: handleWebSearchToggle,
hideOnClick: false, hideOnClick: false,
render: (props) => ( render: (props) => (
@ -214,8 +226,8 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
}); });
} }
if (canRunCode) { if (canRunCode && codeEnabled) {
items.push({ dropdownItems.push({
onClick: handleCodeInterpreterToggle, onClick: handleCodeInterpreterToggle,
hideOnClick: false, hideOnClick: false,
render: (props) => ( render: (props) => (
@ -268,8 +280,8 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
}); });
} }
// Add Artifacts option if (artifactsEnabled) {
items.push({ dropdownItems.push({
hideOnClick: false, hideOnClick: false,
render: (props) => ( render: (props) => (
<ArtifactsSubMenu <ArtifactsSubMenu
@ -283,9 +295,10 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
/> />
), ),
}); });
}
if (mcpServerNames && mcpServerNames.length > 0) { if (mcpServerNames && mcpServerNames.length > 0) {
items.push({ dropdownItems.push({
hideOnClick: false, hideOnClick: false,
render: (props) => ( render: (props) => (
<MCPSubMenu <MCPSubMenu
@ -301,40 +314,6 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
}); });
} }
return items;
}, [
localize,
mcpValues,
canRunCode,
isMCPPinned,
isCodePinned,
mcpPlaceholder,
mcpServerNames,
isSearchPinned,
setIsMCPPinned,
canUseWebSearch,
setIsCodePinned,
handleMCPToggle,
showCodeSettings,
setIsSearchPinned,
handleShadcnToggle,
handleCustomToggle,
isFileSearchPinned,
isArtifactsPinned,
codeMenuTriggerRef,
setIsCodeDialogOpen,
searchMenuTriggerRef,
showWebSearchSettings,
setIsFileSearchPinned,
artifacts.toggleState,
setIsArtifactsPinned,
handleWebSearchToggle,
setIsSearchDialogOpen,
handleFileSearchToggle,
handleArtifactsToggle,
handleCodeInterpreterToggle,
]);
const menuTrigger = ( const menuTrigger = (
<TooltipAnchor <TooltipAnchor
render={ render={

View file

@ -2,20 +2,20 @@ import { useMemo } from 'react';
import { ChevronLeft } from 'lucide-react'; import { ChevronLeft } from 'lucide-react';
import { AgentCapabilities } from 'librechat-data-provider'; import { AgentCapabilities } from 'librechat-data-provider';
import { useFormContext, Controller } from 'react-hook-form'; import { useFormContext, Controller } from 'react-hook-form';
import type { AgentForm, AgentPanelProps } from '~/common'; import type { AgentForm } from '~/common';
import { useAgentPanelContext } from '~/Providers';
import MaxAgentSteps from './MaxAgentSteps'; import MaxAgentSteps from './MaxAgentSteps';
import AgentChain from './AgentChain';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import AgentChain from './AgentChain';
import { Panel } from '~/common'; import { Panel } from '~/common';
export default function AdvancedPanel({ export default function AdvancedPanel() {
agentsConfig,
setActivePanel,
}: Pick<AgentPanelProps, 'setActivePanel' | 'agentsConfig'>) {
const localize = useLocalize(); const localize = useLocalize();
const methods = useFormContext<AgentForm>(); const methods = useFormContext<AgentForm>();
const { control, watch } = methods; const { control, watch } = methods;
const currentAgentId = watch('id'); const currentAgentId = watch('id');
const { agentsConfig, setActivePanel } = useAgentPanelContext();
const chainEnabled = useMemo( const chainEnabled = useMemo(
() => agentsConfig?.capabilities.includes(AgentCapabilities.chain) ?? false, () => agentsConfig?.capabilities.includes(AgentCapabilities.chain) ?? false,
[agentsConfig], [agentsConfig],

View file

@ -1,9 +1,10 @@
import React, { useState, useMemo, useCallback } from 'react'; import React, { useState, useMemo, useCallback } from 'react';
import { EModelEndpoint } from 'librechat-data-provider';
import { Controller, useWatch, useFormContext } from 'react-hook-form'; import { Controller, useWatch, useFormContext } from 'react-hook-form';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common'; import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils'; import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers'; import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Providers';
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
import Action from '~/components/SidePanel/Builder/Action'; import Action from '~/components/SidePanel/Builder/Action';
import { ToolSelectDialog } from '~/components/Tools'; import { ToolSelectDialog } from '~/components/Tools';
import { icons } from '~/hooks/Endpoint/Icons'; import { icons } from '~/hooks/Endpoint/Icons';
@ -26,17 +27,20 @@ const inputClass = cn(
removeFocusOutlines, removeFocusOutlines,
); );
export default function AgentConfig({ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'createMutation'>) {
agentsConfig,
createMutation,
endpointsConfig,
}: Pick<AgentPanelProps, 'agentsConfig' | 'createMutation' | 'endpointsConfig'>) {
const localize = useLocalize(); const localize = useLocalize();
const fileMap = useFileMapContext(); const fileMap = useFileMapContext();
const { showToast } = useToastContext(); const { showToast } = useToastContext();
const methods = useFormContext<AgentForm>(); const methods = useFormContext<AgentForm>();
const [showToolDialog, setShowToolDialog] = useState(false); const [showToolDialog, setShowToolDialog] = useState(false);
const { actions, setAction, groupedTools: allTools, setActivePanel } = useAgentPanelContext(); const {
actions,
setAction,
agentsConfig,
setActivePanel,
endpointsConfig,
groupedTools: allTools,
} = useAgentPanelContext();
const { control } = methods; const { control } = methods;
const provider = useWatch({ control, name: 'provider' }); const provider = useWatch({ control, name: 'provider' });
@ -45,34 +49,15 @@ export default function AgentConfig({
const tools = useWatch({ control, name: 'tools' }); const tools = useWatch({ control, name: 'tools' });
const agent_id = useWatch({ control, name: 'id' }); const agent_id = useWatch({ control, name: 'id' });
const toolsEnabled = useMemo( const {
() => agentsConfig?.capabilities?.includes(AgentCapabilities.tools) ?? false, ocrEnabled,
[agentsConfig], codeEnabled,
); toolsEnabled,
const actionsEnabled = useMemo( actionsEnabled,
() => agentsConfig?.capabilities?.includes(AgentCapabilities.actions) ?? false, artifactsEnabled,
[agentsConfig], webSearchEnabled,
); fileSearchEnabled,
const artifactsEnabled = useMemo( } = useAgentCapabilities(agentsConfig?.capabilities);
() => agentsConfig?.capabilities?.includes(AgentCapabilities.artifacts) ?? false,
[agentsConfig],
);
const ocrEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.ocr) ?? false,
[agentsConfig],
);
const fileSearchEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.file_search) ?? false,
[agentsConfig],
);
const webSearchEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.web_search) ?? false,
[agentsConfig],
);
const codeEnabled = useMemo(
() => agentsConfig?.capabilities?.includes(AgentCapabilities.execute_code) ?? false,
[agentsConfig],
);
const context_files = useMemo(() => { const context_files = useMemo(() => {
if (typeof agent === 'string') { if (typeof agent === 'string') {

View file

@ -7,8 +7,6 @@ import {
Constants, Constants,
SystemRoles, SystemRoles,
EModelEndpoint, EModelEndpoint,
TAgentsEndpoint,
TEndpointsConfig,
isAssistantsEndpoint, isAssistantsEndpoint,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { AgentForm, StringOption } from '~/common'; import type { AgentForm, StringOption } from '~/common';
@ -30,19 +28,15 @@ import { Button } from '~/components';
import ModelPanel from './ModelPanel'; import ModelPanel from './ModelPanel';
import { Panel } from '~/common'; import { Panel } from '~/common';
export default function AgentPanel({ export default function AgentPanel() {
agentsConfig,
endpointsConfig,
}: {
agentsConfig: TAgentsEndpoint | null;
endpointsConfig: TEndpointsConfig;
}) {
const localize = useLocalize(); const localize = useLocalize();
const { user } = useAuthContext(); const { user } = useAuthContext();
const { showToast } = useToastContext(); const { showToast } = useToastContext();
const { const {
activePanel, activePanel,
agentsConfig,
setActivePanel, setActivePanel,
endpointsConfig,
setCurrentAgentId, setCurrentAgentId,
agent_id: current_agent_id, agent_id: current_agent_id,
} = useAgentPanelContext(); } = useAgentPanelContext();
@ -323,14 +317,10 @@ export default function AgentPanel({
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} /> <ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
)} )}
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && ( {canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
<AgentConfig <AgentConfig createMutation={create} />
createMutation={create}
agentsConfig={agentsConfig}
endpointsConfig={endpointsConfig}
/>
)} )}
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && ( {canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (
<AdvancedPanel setActivePanel={setActivePanel} agentsConfig={agentsConfig} /> <AdvancedPanel />
)} )}
{canEditAgent && !agentQuery.isInitialLoading && ( {canEditAgent && !agentQuery.isInitialLoading && (
<AgentFooter <AgentFooter

View file

@ -1,8 +1,5 @@
import { useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { TConfig, TEndpointsConfig, TAgentsEndpoint } from 'librechat-data-provider';
import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext';
import { useGetEndpointsQuery } from '~/data-provider';
import VersionPanel from './Version/VersionPanel'; import VersionPanel from './Version/VersionPanel';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import ActionsPanel from './ActionsPanel'; import ActionsPanel from './ActionsPanel';
@ -22,21 +19,6 @@ function AgentPanelSwitchWithContext() {
const { conversation } = useChatContext(); const { conversation } = useChatContext();
const { activePanel, setCurrentAgentId } = useAgentPanelContext(); const { activePanel, setCurrentAgentId } = useAgentPanelContext();
// TODO: Implement MCP endpoint
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
if (!config) return null;
return {
...(config as TConfig),
capabilities: Array.isArray(config.capabilities)
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
: ([] as AgentCapabilities[]),
} as TAgentsEndpoint;
}, [endpointsConfig]);
useEffect(() => { useEffect(() => {
const agent_id = conversation?.agent_id ?? ''; const agent_id = conversation?.agent_id ?? '';
if (agent_id) { if (agent_id) {
@ -57,5 +39,5 @@ function AgentPanelSwitchWithContext() {
if (activePanel === Panel.mcp) { if (activePanel === Panel.mcp) {
return <MCPPanel />; return <MCPPanel />;
} }
return <AgentPanel agentsConfig={agentsConfig} endpointsConfig={endpointsConfig} />; return <AgentPanel />;
} }

View file

@ -1,2 +1,4 @@
export { default as useAgentsMap } from './useAgentsMap'; export { default as useAgentsMap } from './useAgentsMap';
export { default as useSelectAgent } from './useSelectAgent'; export { default as useSelectAgent } from './useSelectAgent';
export { default as useAgentCapabilities } from './useAgentCapabilities';
export { default as useGetAgentsConfig } from './useGetAgentsConfig';

View file

@ -0,0 +1,61 @@
import { useMemo } from 'react';
import { AgentCapabilities } from 'librechat-data-provider';
interface AgentCapabilitiesResult {
toolsEnabled: boolean;
actionsEnabled: boolean;
artifactsEnabled: boolean;
ocrEnabled: boolean;
fileSearchEnabled: boolean;
webSearchEnabled: boolean;
codeEnabled: boolean;
}
export default function useAgentCapabilities(
capabilities: AgentCapabilities[] | undefined,
): AgentCapabilitiesResult {
const toolsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.tools) ?? false,
[capabilities],
);
const actionsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.actions) ?? false,
[capabilities],
);
const artifactsEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.artifacts) ?? false,
[capabilities],
);
const ocrEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.ocr) ?? false,
[capabilities],
);
const fileSearchEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.file_search) ?? false,
[capabilities],
);
const webSearchEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.web_search) ?? false,
[capabilities],
);
const codeEnabled = useMemo(
() => capabilities?.includes(AgentCapabilities.execute_code) ?? false,
[capabilities],
);
return {
ocrEnabled,
codeEnabled,
toolsEnabled,
actionsEnabled,
artifactsEnabled,
webSearchEnabled,
fileSearchEnabled,
};
}

View file

@ -0,0 +1,35 @@
import { useMemo } from 'react';
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint, TEndpointsConfig, TConfig } from 'librechat-data-provider';
import { useGetEndpointsQuery } from '~/data-provider';
interface UseGetAgentsConfigOptions {
endpointsConfig?: TEndpointsConfig;
}
export default function useGetAgentsConfig(options?: UseGetAgentsConfigOptions): {
agentsConfig?: TAgentsEndpoint | null;
endpointsConfig?: TEndpointsConfig | null;
} {
const { endpointsConfig: providedConfig } = options || {};
const { data: queriedConfig } = useGetEndpointsQuery({
enabled: !providedConfig,
});
const endpointsConfig = providedConfig || queriedConfig;
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
if (!config) return null;
return {
...(config as TConfig),
capabilities: Array.isArray(config.capabilities)
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
: ([] as AgentCapabilities[]),
} as TAgentsEndpoint;
}, [endpointsConfig]);
return { agentsConfig, endpointsConfig };
}

View file

@ -25,10 +25,10 @@ import useUpdateFiles from './useUpdateFiles';
type UseFileHandling = { type UseFileHandling = {
fileSetter?: FileSetter; fileSetter?: FileSetter;
fileFilter?: (file: File) => boolean;
additionalMetadata?: Record<string, string | undefined>;
overrideEndpoint?: EModelEndpoint; overrideEndpoint?: EModelEndpoint;
fileFilter?: (file: File) => boolean;
overrideEndpointFileConfig?: EndpointFileConfig; overrideEndpointFileConfig?: EndpointFileConfig;
additionalMetadata?: Record<string, string | undefined>;
}; };
const useFileHandling = (params?: UseFileHandling) => { const useFileHandling = (params?: UseFileHandling) => {
@ -151,6 +151,10 @@ const useFileHandling = (params?: UseFileHandling) => {
const formData = new FormData(); const formData = new FormData();
formData.append('endpoint', endpoint); formData.append('endpoint', endpoint);
formData.append(
'original_endpoint',
conversation?.endpointType || conversation?.endpoint || '',
);
formData.append('file', extendedFile.file as File, encodeURIComponent(filename)); formData.append('file', extendedFile.file as File, encodeURIComponent(filename));
formData.append('file_id', extendedFile.file_id); formData.append('file_id', extendedFile.file_id);

View file

@ -59,7 +59,7 @@ export const getAvailableEndpoints = (
/** Get the specified field from the endpoint config */ /** Get the specified field from the endpoint config */
export function getEndpointField<K extends keyof t.TConfig>( export function getEndpointField<K extends keyof t.TConfig>(
endpointsConfig: t.TEndpointsConfig | undefined, endpointsConfig: t.TEndpointsConfig | undefined | null,
endpoint: EModelEndpoint | string | null | undefined, endpoint: EModelEndpoint | string | null | undefined,
property: K, property: K,
): t.TConfig[K] | undefined { ): t.TConfig[K] | undefined {
@ -246,7 +246,7 @@ export function getIconKey({
endpointIconURL: iconURL, endpointIconURL: iconURL,
}: { }: {
endpoint?: string | null; endpoint?: string | null;
endpointsConfig?: t.TEndpointsConfig; endpointsConfig?: t.TEndpointsConfig | null;
endpointType?: string | null; endpointType?: string | null;
endpointIconURL?: string; endpointIconURL?: string;
}): keyof IconsRecord { }): keyof IconsRecord {