🤖 refactor: Side Panel Agent UI To Account For Ephemeral Agents (#9763)

* refactor: Remove unused imports and consolidate ephemeral agent logic

* refactor: Side Panel agent handling to account for ephemeral agents for UI

* refactor: Replace Constants.EPHEMERAL_AGENT_ID checks with isEphemeralAgent utility for consistency

* ci: AgentPanel tests with additional mock configurations and utility functions
This commit is contained in:
Danny Avila 2025-09-22 09:48:05 -04:00 committed by GitHub
parent a6bf2b6ce3
commit 8a60e8990f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 87 additions and 77 deletions

View file

@ -9,7 +9,7 @@ import {
useMCPToolsQuery, useMCPToolsQuery,
} from '~/data-provider'; } from '~/data-provider';
import { useLocalize, useGetAgentsConfig, useMCPConnectionStatus } from '~/hooks'; import { useLocalize, useGetAgentsConfig, useMCPConnectionStatus } from '~/hooks';
import { Panel } from '~/common'; import { Panel, isEphemeralAgent } from '~/common';
const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined); const AgentPanelContext = createContext<AgentPanelContextType | undefined>(undefined);
@ -32,15 +32,15 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
const { data: startupConfig } = useGetStartupConfig(); const { data: startupConfig } = useGetStartupConfig();
const { data: actions } = useGetActionsQuery(EModelEndpoint.agents, { const { data: actions } = useGetActionsQuery(EModelEndpoint.agents, {
enabled: !!agent_id, enabled: !isEphemeralAgent(agent_id),
}); });
const { data: regularTools } = useAvailableToolsQuery(EModelEndpoint.agents, { const { data: regularTools } = useAvailableToolsQuery(EModelEndpoint.agents, {
enabled: !!agent_id, enabled: !isEphemeralAgent(agent_id),
}); });
const { data: mcpData } = useMCPToolsQuery({ const { data: mcpData } = useMCPToolsQuery({
enabled: !!agent_id && startupConfig?.mcpServers != null, enabled: !isEphemeralAgent(agent_id) && startupConfig?.mcpServers != null,
}); });
const { agentsConfig, endpointsConfig } = useGetAgentsConfig(); const { agentsConfig, endpointsConfig } = useGetAgentsConfig();
@ -50,7 +50,7 @@ export function AgentPanelProvider({ children }: { children: React.ReactNode })
); );
const { connectionStatus } = useMCPConnectionStatus({ const { connectionStatus } = useMCPConnectionStatus({
enabled: !!agent_id && mcpServerNames.length > 0, enabled: !isEphemeralAgent(agent_id) && mcpServerNames.length > 0,
}); });
const mcpServersMap = useMemo(() => { const mcpServersMap = useMemo(() => {

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { TModelSpec, TStartupConfig } from 'librechat-data-provider'; import { TStartupConfig } from 'librechat-data-provider';
export interface Endpoint { export interface Endpoint {
value: string; value: string;

View file

@ -1,5 +1,5 @@
import { RefObject } from 'react'; import { RefObject } from 'react';
import { FileSources, EModelEndpoint } from 'librechat-data-provider'; import { Constants, FileSources, EModelEndpoint } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query'; import type { UseMutationResult } from '@tanstack/react-query';
import type * as InputNumberPrimitive from 'rc-input-number'; import type * as InputNumberPrimitive from 'rc-input-number';
import type { SetterOrUpdater, RecoilState } from 'recoil'; import type { SetterOrUpdater, RecoilState } from 'recoil';
@ -8,6 +8,10 @@ import type * as t from 'librechat-data-provider';
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
import type { TranslationKeys } from '~/hooks'; import type { TranslationKeys } from '~/hooks';
export function isEphemeralAgent(agentId: string | null | undefined): boolean {
return agentId == null || agentId === '' || agentId === Constants.EPHEMERAL_AGENT_ID;
}
export interface ConfigFieldDetail { export interface ConfigFieldDetail {
title: string; title: string;
description: string; description: string;

View file

@ -1,27 +1,26 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ChevronLeft } from 'lucide-react'; import { ChevronLeft } from 'lucide-react';
import { useForm, FormProvider } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form';
import { import {
AuthTypeEnum, AuthTypeEnum,
AuthorizationTypeEnum, AuthorizationTypeEnum,
TokenExchangeMethodEnum, TokenExchangeMethodEnum,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { import {
OGDialogTemplate,
TrashIcon,
OGDialog,
OGDialogTrigger,
Label, Label,
OGDialog,
TrashIcon,
OGDialogTrigger,
useToastContext, useToastContext,
OGDialogTemplate,
} from '@librechat/client'; } from '@librechat/client';
import type { ActionAuthForm } from '~/common';
import ActionsAuth from '~/components/SidePanel/Builder/ActionsAuth'; import ActionsAuth from '~/components/SidePanel/Builder/ActionsAuth';
import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
import { useDeleteAgentAction } from '~/data-provider'; import { useDeleteAgentAction } from '~/data-provider';
import type { ActionAuthForm } from '~/common'; import { Panel, isEphemeralAgent } from '~/common';
import ActionsInput from './ActionsInput'; import ActionsInput from './ActionsInput';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { Panel } from '~/common';
export default function ActionsPanel() { export default function ActionsPanel() {
const localize = useLocalize(); const localize = useLocalize();
@ -109,7 +108,7 @@ export default function ActionsPanel() {
<div className="absolute right-0 top-6"> <div className="absolute right-0 top-6">
<button <button
type="button" type="button"
disabled={!agent_id || !action.action_id} disabled={isEphemeralAgent(agent_id) || !action.action_id}
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium" className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
> >
<TrashIcon className="text-red-500" /> <TrashIcon className="text-red-500" />
@ -127,7 +126,7 @@ export default function ActionsPanel() {
} }
selection={{ selection={{
selectHandler: () => { selectHandler: () => {
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
return showToast({ return showToast({
message: localize('com_agents_no_agent_id_error'), message: localize('com_agents_no_agent_id_error'),
status: 'error', status: 'error',
@ -135,7 +134,7 @@ export default function ActionsPanel() {
} }
deleteAgentAction.mutate({ deleteAgentAction.mutate({
action_id: action.action_id, action_id: action.action_id,
agent_id, agent_id: agent_id || '',
}); });
}, },
selectClasses: selectClasses:

View file

@ -18,6 +18,7 @@ import { useFileMapContext, useAgentPanelContext } from '~/Providers';
import AgentCategorySelector from './AgentCategorySelector'; import AgentCategorySelector from './AgentCategorySelector';
import Action from '~/components/SidePanel/Builder/Action'; import Action from '~/components/SidePanel/Builder/Action';
import { useLocalize, useVisibleTools } from '~/hooks'; import { useLocalize, useVisibleTools } from '~/hooks';
import { Panel, isEphemeralAgent } from '~/common';
import { useGetAgentFiles } from '~/data-provider'; import { useGetAgentFiles } from '~/data-provider';
import { icons } from '~/hooks/Endpoint/Icons'; import { icons } from '~/hooks/Endpoint/Icons';
import Instructions from './Instructions'; import Instructions from './Instructions';
@ -29,7 +30,6 @@ import Artifacts from './Artifacts';
import AgentTool from './AgentTool'; import AgentTool from './AgentTool';
import CodeForm from './Code/Form'; import CodeForm from './Code/Form';
import MCPTools from './MCPTools'; import MCPTools from './MCPTools';
import { Panel } from '~/common';
const labelClass = 'mb-2 text-token-text-primary block font-medium'; const labelClass = 'mb-2 text-token-text-primary block font-medium';
const inputClass = cn( const inputClass = cn(
@ -149,7 +149,7 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
}, [agent, agent_id, mergedFileMap]); }, [agent, agent_id, mergedFileMap]);
const handleAddActions = useCallback(() => { const handleAddActions = useCallback(() => {
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
showToast({ showToast({
message: localize('com_assistants_actions_disabled'), message: localize('com_assistants_actions_disabled'),
status: 'warning', status: 'warning',
@ -370,7 +370,7 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
{(actionsEnabled ?? false) && ( {(actionsEnabled ?? false) && (
<button <button
type="button" type="button"
disabled={!agent_id} disabled={isEphemeralAgent(agent_id)}
onClick={handleAddActions} onClick={handleAddActions}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium" className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
aria-haspopup="dialog" aria-haspopup="dialog"

View file

@ -37,22 +37,28 @@ jest.mock('librechat-data-provider', () => {
dataService: { dataService: {
updateAgent: jest.fn(), updateAgent: jest.fn(),
}, },
Tools: { Tools: actualModule.Tools || {
execute_code: 'execute_code', execute_code: 'execute_code',
file_search: 'file_search', file_search: 'file_search',
web_search: 'web_search', web_search: 'web_search',
}, },
Constants: { Constants: actualModule.Constants || {
EPHEMERAL_AGENT_ID: 'ephemeral', EPHEMERAL_AGENT_ID: 'ephemeral',
}, },
SystemRoles: { SystemRoles: actualModule.SystemRoles || {
ADMIN: 'ADMIN', ADMIN: 'ADMIN',
}, },
EModelEndpoint: { EModelEndpoint: actualModule.EModelEndpoint || {
agents: 'agents', agents: 'agents',
chatGPTBrowser: 'chatGPTBrowser', chatGPTBrowser: 'chatGPTBrowser',
gptPlugins: 'gptPlugins', gptPlugins: 'gptPlugins',
}, },
ResourceType: actualModule.ResourceType || {
AGENT: 'agent',
},
PermissionBits: actualModule.PermissionBits || {
EDIT: 2,
},
isAssistantsEndpoint: jest.fn(() => false), isAssistantsEndpoint: jest.fn(() => false),
}; };
}); });
@ -97,6 +103,13 @@ jest.mock('~/hooks', () => ({
useAuthContext: () => ({ user: { id: 'user-123', role: 'USER' } }), useAuthContext: () => ({ user: { id: 'user-123', role: 'USER' } }),
})); }));
jest.mock('~/hooks/useResourcePermissions', () => ({
useResourcePermissions: () => ({
hasPermission: jest.fn(() => true),
isLoading: false,
}),
}));
jest.mock('~/Providers/AgentPanelContext', () => ({ jest.mock('~/Providers/AgentPanelContext', () => ({
useAgentPanelContext: () => ({ useAgentPanelContext: () => ({
activePanel: 'builder', activePanel: 'builder',
@ -109,6 +122,9 @@ jest.mock('~/Providers/AgentPanelContext', () => ({
})); }));
jest.mock('~/common', () => ({ jest.mock('~/common', () => ({
isEphemeralAgent: (agentId: string | null | undefined): boolean => {
return agentId == null || agentId === '' || agentId === 'ephemeral';
},
Panel: { Panel: {
model: 'model', model: 'model',
builder: 'builder', builder: 'builder',
@ -199,6 +215,10 @@ jest.mock('~/data-provider', () => {
return { return {
...actual, ...actual,
useGetAgentByIdQuery: jest.fn(), useGetAgentByIdQuery: jest.fn(),
useGetExpandedAgentByIdQuery: jest.fn(() => ({
data: null,
isInitialLoading: false,
})),
useUpdateAgentMutation: actual.useUpdateAgentMutation, useUpdateAgentMutation: actual.useUpdateAgentMutation,
}; };
}); });

View file

@ -5,7 +5,6 @@ import { useWatch, useForm, FormProvider } from 'react-hook-form';
import { useGetModelsQuery } from 'librechat-data-provider/react-query'; import { useGetModelsQuery } from 'librechat-data-provider/react-query';
import { import {
Tools, Tools,
Constants,
SystemRoles, SystemRoles,
ResourceType, ResourceType,
EModelEndpoint, EModelEndpoint,
@ -25,11 +24,11 @@ import { useSelectAgent, useLocalize, useAuthContext } from '~/hooks';
import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
import AgentPanelSkeleton from './AgentPanelSkeleton'; import AgentPanelSkeleton from './AgentPanelSkeleton';
import AdvancedPanel from './Advanced/AdvancedPanel'; import AdvancedPanel from './Advanced/AdvancedPanel';
import { Panel, isEphemeralAgent } from '~/common';
import AgentConfig from './AgentConfig'; import AgentConfig from './AgentConfig';
import AgentSelect from './AgentSelect'; import AgentSelect from './AgentSelect';
import AgentFooter from './AgentFooter'; import AgentFooter from './AgentFooter';
import ModelPanel from './ModelPanel'; import ModelPanel from './ModelPanel';
import { Panel } from '~/common';
export default function AgentPanel() { export default function AgentPanel() {
const localize = useLocalize(); const localize = useLocalize();
@ -57,11 +56,7 @@ export default function AgentPanel() {
const canEdit = hasPermission(PermissionBits.EDIT); const canEdit = hasPermission(PermissionBits.EDIT);
const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', { const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', {
enabled: enabled: !isEphemeralAgent(current_agent_id) && canEdit && !permissionsLoading,
!!(current_agent_id ?? '') &&
current_agent_id !== Constants.EPHEMERAL_AGENT_ID &&
canEdit &&
!permissionsLoading,
}); });
const agentQuery = canEdit && expandedAgentQuery.data ? expandedAgentQuery : basicAgentQuery; const agentQuery = canEdit && expandedAgentQuery.data ? expandedAgentQuery : basicAgentQuery;
@ -298,7 +293,7 @@ export default function AgentPanel() {
</Button> </Button>
<Button <Button
variant="submit" variant="submit"
disabled={!agent_id || agentQuery.isInitialLoading} disabled={isEphemeralAgent(agent_id) || agentQuery.isInitialLoading}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
handleSelectAgent(); handleSelectAgent();

View file

@ -1,11 +1,11 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { AgentPanelProvider, useAgentPanelContext } from '~/Providers/AgentPanelContext';
import { Panel, isEphemeralAgent } from '~/common';
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';
import AgentPanel from './AgentPanel'; import AgentPanel from './AgentPanel';
import MCPPanel from './MCPPanel'; import MCPPanel from './MCPPanel';
import { Panel } from '~/common';
export default function AgentPanelSwitch() { export default function AgentPanelSwitch() {
return ( return (
@ -21,7 +21,7 @@ function AgentPanelSwitchWithContext() {
useEffect(() => { useEffect(() => {
const agent_id = conversation?.agent_id ?? ''; const agent_id = conversation?.agent_id ?? '';
if (agent_id) { if (!isEphemeralAgent(agent_id)) {
setCurrentAgentId(agent_id); setCurrentAgentId(agent_id);
} }
}, [setCurrentAgentId, conversation?.agent_id]); }, [setCurrentAgentId, conversation?.agent_id]);

View file

@ -14,6 +14,7 @@ import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import FileRow from '~/components/Chat/Input/Files/FileRow'; import FileRow from '~/components/Chat/Input/Files/FileRow';
import { useGetFileConfig } from '~/data-provider'; import { useGetFileConfig } from '~/data-provider';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import { isEphemeralAgent } from '~/common';
const tool_resource = EToolResources.execute_code; const tool_resource = EToolResources.execute_code;
@ -85,7 +86,7 @@ export default function Files({
<div> <div>
<button <button
type="button" type="button"
disabled={!agent_id || codeChecked === false} disabled={isEphemeralAgent(agent_id) || codeChecked === false}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium" className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleButtonClick} onClick={handleButtonClick}
> >
@ -96,7 +97,7 @@ export default function Files({
style={{ display: 'none' }} style={{ display: 'none' }}
tabIndex={-1} tabIndex={-1}
ref={fileInputRef} ref={fileInputRef}
disabled={!agent_id || codeChecked === false} disabled={isEphemeralAgent(agent_id) || codeChecked === false}
onChange={handleFileChange} onChange={handleFileChange}
/> />
<AttachmentIcon className="text-token-text-primary h-4 w-4" /> <AttachmentIcon className="text-token-text-primary h-4 w-4" />

View file

@ -14,6 +14,7 @@ import { logger, getDefaultAgentFormValues } from '~/utils';
import { useLocalize, useSetIndexOptions } from '~/hooks'; import { useLocalize, useSetIndexOptions } from '~/hooks';
import { useDeleteAgentMutation } from '~/data-provider'; import { useDeleteAgentMutation } from '~/data-provider';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import { isEphemeralAgent } from '~/common';
export default function DeleteButton({ export default function DeleteButton({
agent_id, agent_id,
@ -76,7 +77,7 @@ export default function DeleteButton({
}, },
}); });
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
return null; return null;
} }

View file

@ -1,6 +1,7 @@
import { CopyIcon } from 'lucide-react'; import { CopyIcon } from 'lucide-react';
import { useToastContext, Button } from '@librechat/client'; import { useToastContext, Button } from '@librechat/client';
import { useDuplicateAgentMutation } from '~/data-provider'; import { useDuplicateAgentMutation } from '~/data-provider';
import { isEphemeralAgent } from '~/common';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
export default function DuplicateAgent({ agent_id }: { agent_id: string }) { export default function DuplicateAgent({ agent_id }: { agent_id: string }) {
@ -23,7 +24,7 @@ export default function DuplicateAgent({ agent_id }: { agent_id: string }) {
}, },
}); });
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
return null; return null;
} }

View file

@ -22,8 +22,8 @@ import { useFileHandling, useLocalize, useLazyEffect, useSharePointFileHandling
import { useGetFileConfig, useGetStartupConfig } from '~/data-provider'; import { useGetFileConfig, useGetStartupConfig } from '~/data-provider';
import { SharePointPickerDialog } from '~/components/SharePoint'; import { SharePointPickerDialog } from '~/components/SharePoint';
import FileRow from '~/components/Chat/Input/Files/FileRow'; import FileRow from '~/components/Chat/Input/Files/FileRow';
import { ESide, isEphemeralAgent } from '~/common';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import { ESide } from '~/common';
export default function FileContext({ export default function FileContext({
agent_id, agent_id,
@ -156,7 +156,7 @@ export default function FileContext({
) : ( ) : (
<button <button
type="button" type="button"
disabled={!agent_id} disabled={isEphemeralAgent(agent_id)}
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium" className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
onClick={handleLocalFileClick} onClick={handleLocalFileClick}
> >
@ -173,7 +173,7 @@ export default function FileContext({
style={{ display: 'none' }} style={{ display: 'none' }}
tabIndex={-1} tabIndex={-1}
ref={fileInputRef} ref={fileInputRef}
disabled={!agent_id} disabled={isEphemeralAgent(agent_id)}
onChange={handleFileChange} onChange={handleFileChange}
/> />
</div> </div>

View file

@ -18,6 +18,7 @@ import { SharePointPickerDialog } from '~/components/SharePoint';
import FileRow from '~/components/Chat/Input/Files/FileRow'; import FileRow from '~/components/Chat/Input/Files/FileRow';
import FileSearchCheckbox from './FileSearchCheckbox'; import FileSearchCheckbox from './FileSearchCheckbox';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import { isEphemeralAgent } from '~/common';
export default function FileSearch({ export default function FileSearch({
agent_id, agent_id,
@ -69,7 +70,7 @@ export default function FileSearch({
const isUploadDisabled = endpointFileConfig.disabled ?? false; const isUploadDisabled = endpointFileConfig.disabled ?? false;
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled; const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
const disabledUploadButton = !agent_id || fileSearchChecked === false; const disabledUploadButton = isEphemeralAgent(agent_id) || fileSearchChecked === false;
const handleSharePointFilesSelected = async (sharePointFiles: any[]) => { const handleSharePointFilesSelected = async (sharePointFiles: any[]) => {
try { try {

View file

@ -7,21 +7,19 @@ import {
TokenExchangeMethodEnum, TokenExchangeMethodEnum,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { import {
OGDialog,
OGDialogTrigger,
Label, Label,
OGDialogTemplate, OGDialog,
TrashIcon, TrashIcon,
OGDialogTrigger,
useToastContext, useToastContext,
OGDialogTemplate,
} from '@librechat/client'; } from '@librechat/client';
import type { MCPForm } from '~/common';
import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
import { defaultMCPFormValues } from '~/common/mcp'; import { defaultMCPFormValues } from '~/common/mcp';
import type { MCPForm } from '~/common'; import { Panel, isEphemeralAgent } from '~/common';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import MCPInput from './MCPInput'; import MCPInput from './MCPInput';
import { Panel } from '~/common';
// TODO: Add MCP delete (for now mocked for ui)
// import { useDeleteAgentMCP } from '~/data-provider';
function useDeleteAgentMCP({ function useDeleteAgentMCP({
onSuccess, onSuccess,
@ -127,7 +125,7 @@ export default function MCPPanel() {
<div className="absolute right-0 top-6"> <div className="absolute right-0 top-6">
<button <button
type="button" type="button"
disabled={!agent_id || !mcp.mcp_id} disabled={isEphemeralAgent(agent_id) || !mcp.mcp_id}
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium" className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
> >
<TrashIcon className="text-red-500" /> <TrashIcon className="text-red-500" />
@ -145,7 +143,7 @@ export default function MCPPanel() {
} }
selection={{ selection={{
selectHandler: () => { selectHandler: () => {
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
return showToast({ return showToast({
message: localize('com_agents_no_agent_id_error'), message: localize('com_agents_no_agent_id_error'),
status: 'error', status: 'error',
@ -153,7 +151,7 @@ export default function MCPPanel() {
} }
deleteAgentMCP.mutate({ deleteAgentMCP.mutate({
mcp_id: mcp.mcp_id, mcp_id: mcp.mcp_id,
agent_id, agent_id: agent_id || '',
}); });
}, },
selectClasses: selectClasses:

View file

@ -3,15 +3,15 @@ import { useLocalize } from '~/hooks';
import { useToastContext } from '@librechat/client'; import { useToastContext } from '@librechat/client';
import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
import MCP from '~/components/SidePanel/Builder/MCP'; import MCP from '~/components/SidePanel/Builder/MCP';
import { Panel } from '~/common'; import { Panel, isEphemeralAgent } from '~/common';
export default function MCPSection() { export default function MCPSection() {
const { showToast } = useToastContext();
const localize = useLocalize(); const localize = useLocalize();
const { showToast } = useToastContext();
const { mcps = [], agent_id, setMcp, setActivePanel } = useAgentPanelContext(); const { mcps = [], agent_id, setMcp, setActivePanel } = useAgentPanelContext();
const handleAddMCP = useCallback(() => { const handleAddMCP = useCallback(() => {
if (!agent_id) { if (isEphemeralAgent(agent_id)) {
showToast({ showToast({
message: localize('com_agents_mcps_disabled'), message: localize('com_agents_mcps_disabled'),
status: 'warning', status: 'warning',

View file

@ -1,17 +1,12 @@
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { import { QueryKeys, dataService, EModelEndpoint, PermissionBits } from 'librechat-data-provider';
Constants,
QueryKeys,
dataService,
EModelEndpoint,
PermissionBits,
} from 'librechat-data-provider';
import type { import type {
QueryObserverResult, QueryObserverResult,
UseQueryOptions, UseQueryOptions,
UseInfiniteQueryOptions, UseInfiniteQueryOptions,
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type t from 'librechat-data-provider'; import type t from 'librechat-data-provider';
import { isEphemeralAgent } from '~/common';
/** /**
* AGENTS * AGENTS
@ -73,11 +68,7 @@ export const useGetAgentByIdQuery = (
agent_id: string | null | undefined, agent_id: string | null | undefined,
config?: UseQueryOptions<t.Agent>, config?: UseQueryOptions<t.Agent>,
): QueryObserverResult<t.Agent> => { ): QueryObserverResult<t.Agent> => {
const isValidAgentId = !!( const isValidAgentId = !!agent_id && !isEphemeralAgent(agent_id);
agent_id &&
agent_id !== '' &&
agent_id !== Constants.EPHEMERAL_AGENT_ID
);
return useQuery<t.Agent>( return useQuery<t.Agent>(
[QueryKeys.agent, agent_id], [QueryKeys.agent, agent_id],

View file

@ -3,6 +3,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { QueryKeys, DynamicQueryKeys, dataService } from 'librechat-data-provider'; import { QueryKeys, DynamicQueryKeys, dataService } from 'librechat-data-provider';
import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query'; import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query';
import type t from 'librechat-data-provider'; import type t from 'librechat-data-provider';
import { isEphemeralAgent } from '~/common';
import { addFileToCache } from '~/utils'; import { addFileToCache } from '~/utils';
import store from '~/store'; import store from '~/store';
@ -32,7 +33,7 @@ export const useGetAgentFiles = <TData = t.TFile[]>(
refetchOnReconnect: false, refetchOnReconnect: false,
refetchOnMount: false, refetchOnMount: false,
...config, ...config,
enabled: (config?.enabled ?? true) === true && queriesEnabled && !!agentId, enabled: (config?.enabled ?? true) === true && queriesEnabled && !isEphemeralAgent(agentId),
}, },
); );
}; };

View file

@ -1,8 +1,9 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Tools, Constants, EToolResources } from 'librechat-data-provider'; import { Tools, EToolResources } from 'librechat-data-provider';
import type { TEphemeralAgent } from 'librechat-data-provider'; import type { TEphemeralAgent } from 'librechat-data-provider';
import { useGetAgentByIdQuery } from '~/data-provider'; import { useGetAgentByIdQuery } from '~/data-provider';
import { useAgentsMapContext } from '~/Providers'; import { useAgentsMapContext } from '~/Providers';
import { isEphemeralAgent } from '~/common';
interface AgentToolPermissionsResult { interface AgentToolPermissionsResult {
fileSearchAllowedByAgent: boolean; fileSearchAllowedByAgent: boolean;
@ -10,10 +11,6 @@ interface AgentToolPermissionsResult {
tools: string[] | undefined; tools: string[] | undefined;
} }
function isEphemeralAgent(agentId: string | null | undefined): boolean {
return agentId == null || agentId === '' || agentId === Constants.EPHEMERAL_AGENT_ID;
}
/** /**
* Hook to determine whether specific tools are allowed for a given agent. * Hook to determine whether specific tools are allowed for a given agent.
* *

View file

@ -17,6 +17,7 @@ import type { DropTargetMonitor } from 'react-dnd';
import type * as t from 'librechat-data-provider'; import type * as t from 'librechat-data-provider';
import store, { ephemeralAgentByConvoId } from '~/store'; import store, { ephemeralAgentByConvoId } from '~/store';
import useFileHandling from './useFileHandling'; import useFileHandling from './useFileHandling';
import { isEphemeralAgent } from '~/common';
export default function useDragHelpers() { export default function useDragHelpers() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -78,7 +79,7 @@ export default function useDragHelpers() {
let fileSearchAllowedByAgent = true; let fileSearchAllowedByAgent = true;
let codeAllowedByAgent = true; let codeAllowedByAgent = true;
if (agentId && agentId !== Constants.EPHEMERAL_AGENT_ID) { if (agentId && !isEphemeralAgent(agentId)) {
/** Agent data from cache */ /** Agent data from cache */
const agent = queryClient.getQueryData<t.Agent>([QueryKeys.agent, agentId]); const agent = queryClient.getQueryData<t.Agent>([QueryKeys.agent, agentId]);
if (agent) { if (agent) {

View file

@ -1,10 +1,10 @@
import { import {
Constants,
isAgentsEndpoint, isAgentsEndpoint,
tQueryParamsSchema, tQueryParamsSchema,
isAssistantsEndpoint, isAssistantsEndpoint,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { TConversation, TPreset } from 'librechat-data-provider'; import type { TConversation, TPreset } from 'librechat-data-provider';
import { isEphemeralAgent } from '~/common';
const allowedParams = Object.keys(tQueryParamsSchema.shape); const allowedParams = Object.keys(tQueryParamsSchema.shape);
export default function createChatSearchParams( export default function createChatSearchParams(
@ -33,7 +33,7 @@ export default function createChatSearchParams(
if ( if (
isAgentsEndpoint(endpoint) && isAgentsEndpoint(endpoint) &&
conversation.agent_id && conversation.agent_id &&
conversation.agent_id !== Constants.EPHEMERAL_AGENT_ID !isEphemeralAgent(conversation.agent_id)
) { ) {
return new URLSearchParams({ agent_id: String(conversation.agent_id) }); return new URLSearchParams({ agent_id: String(conversation.agent_id) });
} else if (isAssistantsEndpoint(endpoint) && conversation.assistant_id) { } else if (isAssistantsEndpoint(endpoint) && conversation.assistant_id) {
@ -53,7 +53,7 @@ export default function createChatSearchParams(
const paramMap: Record<string, any> = {}; const paramMap: Record<string, any> = {};
allowedParams.forEach((key) => { allowedParams.forEach((key) => {
if (key === 'agent_id' && conversation.agent_id === Constants.EPHEMERAL_AGENT_ID) { if (key === 'agent_id' && isEphemeralAgent(conversation.agent_id)) {
return; return;
} }
if (key !== 'endpoint' && key !== 'model') { if (key !== 'endpoint' && key !== 'model') {