diff --git a/client/src/components/Chat/Input/Skills.tsx b/client/src/components/Chat/Input/Skills.tsx index 2a5706eb5f..ed61263167 100644 --- a/client/src/components/Chat/Input/Skills.tsx +++ b/client/src/components/Chat/Input/Skills.tsx @@ -1,8 +1,8 @@ import React, { memo } from 'react'; import { ScrollText } from 'lucide-react'; import { CheckboxButton } from '@librechat/client'; -import { PermissionTypes, Permissions } from 'librechat-data-provider'; -import { useLocalize, useHasAccess } from '~/hooks'; +import { Permissions, PermissionTypes, defaultAgentCapabilities } from 'librechat-data-provider'; +import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks'; import { useBadgeRowContext } from '~/Providers'; function Skills() { @@ -15,7 +15,11 @@ function Skills() { permission: Permissions.USE, }); - if (!canUseSkills) { + const { skillsEnabled } = useAgentCapabilities( + context?.agentsConfig?.capabilities ?? defaultAgentCapabilities, + ); + + if (!canUseSkills || !skillsEnabled) { return null; } diff --git a/client/src/hooks/Input/useHandleKeyUp.spec.ts b/client/src/hooks/Input/useHandleKeyUp.spec.ts index c9ea83ea90..8148340e08 100644 --- a/client/src/hooks/Input/useHandleKeyUp.spec.ts +++ b/client/src/hooks/Input/useHandleKeyUp.spec.ts @@ -5,6 +5,7 @@ const mockSetShowSkillsPopover = jest.fn(); const mockHasPromptsAccess = { current: true }; const mockHasMultiConvoAccess = { current: true }; const mockHasSkillsAccess = { current: true }; +const mockSkillsEnabled = { current: true }; const mockEndpoint = { current: 'openAI' as string | null }; const mockCommandToggles = { at: true, plus: true, slash: true, dollar: true }; @@ -76,6 +77,16 @@ jest.mock('~/hooks/Roles/useHasAccess', () => }), ); +jest.mock('~/hooks/Agents/useGetAgentsConfig', () => + jest.fn(() => ({ agentsConfig: { capabilities: [] } })), +); + +jest.mock('~/hooks/Agents/useAgentCapabilities', () => + jest.fn(() => ({ + skillsEnabled: mockSkillsEnabled.current, + })), +); + import React from 'react'; import { renderHook, act } from '@testing-library/react'; import useHandleKeyUp from './useHandleKeyUp'; @@ -118,6 +129,7 @@ beforeEach(() => { mockHasPromptsAccess.current = true; mockHasMultiConvoAccess.current = true; mockHasSkillsAccess.current = true; + mockSkillsEnabled.current = true; mockEndpoint.current = 'openAI'; mockCommandToggles.at = true; mockCommandToggles.plus = true; @@ -424,6 +436,16 @@ describe('useHandleKeyUp', () => { expect(setShowSkillsPopover).not.toHaveBeenCalled(); }); + + it('does NOT trigger $ skill command when skills capability is disabled on agents endpoint', () => { + mockSkillsEnabled.current = false; + const ref = makeTextAreaRef('$', 1); + const { handleKeyUp, setShowSkillsPopover } = renderUseHandleKeyUp(ref); + + act(() => handleKeyUp(makeKeyEvent('$'))); + + expect(setShowSkillsPopover).not.toHaveBeenCalled(); + }); }); describe('endpoint gating', () => { diff --git a/client/src/hooks/Input/useHandleKeyUp.ts b/client/src/hooks/Input/useHandleKeyUp.ts index fca2ee248a..e9af8b4a95 100644 --- a/client/src/hooks/Input/useHandleKeyUp.ts +++ b/client/src/hooks/Input/useHandleKeyUp.ts @@ -1,6 +1,8 @@ import { useCallback, useEffect, useMemo } from 'react'; import { useSetRecoilState, useRecoilValue } from 'recoil'; import { PermissionTypes, Permissions, isAssistantsEndpoint } from 'librechat-data-provider'; +import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities'; +import useGetAgentsConfig from '~/hooks/Agents/useGetAgentsConfig'; import useHasAccess from '~/hooks/Roles/useHasAccess'; import store from '~/store'; @@ -65,6 +67,8 @@ const useHandleKeyUp = ({ permissionType: PermissionTypes.SKILLS, permission: Permissions.USE, }); + const { agentsConfig } = useGetAgentsConfig(); + const { skillsEnabled } = useAgentCapabilities(agentsConfig?.capabilities); const latestMessage = useRecoilValue(store.latestMessageFamily(index)); const endpoint = useRecoilValue(store.effectiveEndpointByIndex(index)); const setShowMentionPopover = useSetRecoilState(store.showMentionPopoverFamily(index)); @@ -109,13 +113,25 @@ const useHandleKeyUp = ({ }, [textAreaRef, hasPromptsAccess, setShowPromptsPopover, slashCommandEnabled]); const handleSkillsCommand = useCallback(() => { - if (!hasSkillsAccess || !dollarCommandEnabled || isAssistantsEndpoint(endpoint)) { + if ( + !hasSkillsAccess || + !skillsEnabled || + !dollarCommandEnabled || + isAssistantsEndpoint(endpoint) + ) { return; } if (shouldTriggerCommand(textAreaRef, '$')) { setShowSkillsPopover(true); } - }, [textAreaRef, hasSkillsAccess, setShowSkillsPopover, dollarCommandEnabled, endpoint]); + }, [ + textAreaRef, + hasSkillsAccess, + skillsEnabled, + setShowSkillsPopover, + dollarCommandEnabled, + endpoint, + ]); const commandHandlers = useMemo( () => ({ diff --git a/client/src/hooks/Nav/useSideNavLinks.ts b/client/src/hooks/Nav/useSideNavLinks.ts index ee0212e5e8..04cf146f7a 100644 --- a/client/src/hooks/Nav/useSideNavLinks.ts +++ b/client/src/hooks/Nav/useSideNavLinks.ts @@ -19,6 +19,12 @@ import { } from 'librechat-data-provider'; import type { TInterfaceConfig, TEndpointsConfig } from 'librechat-data-provider'; import type { NavLink } from '~/common'; +import { + useAgentCapabilities, + useMCPServerManager, + useGetAgentsConfig, + useHasAccess, +} from '~/hooks'; import MCPBuilderPanel from '~/components/SidePanel/MCPBuilder/MCPBuilderPanel'; import AgentPanelSwitch from '~/components/SidePanel/Agents/AgentPanelSwitch'; import BookmarkPanel from '~/components/SidePanel/Bookmarks/BookmarkPanel'; @@ -26,7 +32,6 @@ import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch'; import Parameters from '~/components/SidePanel/Parameters/Panel'; import { MemoryPanel } from '~/components/SidePanel/Memories'; import FilesPanel from '~/components/SidePanel/Files/Panel'; -import { useHasAccess, useMCPServerManager } from '~/hooks'; import { PromptsAccordion } from '~/components/Prompts'; import { SkillsAccordion } from '~/components/Skills'; @@ -85,6 +90,9 @@ export default function useSideNavLinks({ }); const { availableMCPServers } = useMCPServerManager(); + const { agentsConfig } = useGetAgentsConfig({ endpointsConfig }); + const { skillsEnabled } = useAgentCapabilities(agentsConfig?.capabilities); + const Links = useMemo(() => { const links: NavLink[] = []; @@ -122,7 +130,7 @@ export default function useSideNavLinks({ }); } - if (hasAccessToSkills) { + if (hasAccessToSkills && skillsEnabled) { links.push({ title: 'com_ui_skills', label: '', @@ -217,6 +225,7 @@ export default function useSideNavLinks({ hasAccessToCreateAgents, hasAccessToPrompts, hasAccessToSkills, + skillsEnabled, hasAccessToMemories, hasAccessToReadMemories, interfaceConfig.parameters,