🎉 feat: Code Interpreter API and Agents Release (#4860)

* feat: Code Interpreter API & File Search Agent Uploads

chore: add back code files

wip: first pass, abstract key dialog

refactor: influence checkbox on key changes

refactor: update localization keys for 'execute code' to 'run code'

wip: run code button

refactor: add throwError parameter to loadAuthValues and getUserPluginAuthValue functions

feat: first pass, API tool calling

fix: handle missing toolId in callTool function and return 404 for non-existent tools

feat: show code outputs

fix: improve error handling in callTool function and log errors

fix: handle potential null value for filepath in attachment destructuring

fix: normalize language before rendering and prevent null return

fix: add loading indicator in RunCode component while executing code

feat: add support for conditional code execution in Markdown components

feat: attachments

refactor: remove bash

fix: pass abort signal to graph/run

refactor: debounce and rate limit tool call

refactor: increase debounce delay for execute function

feat: set code output attachments

feat: image attachments

refactor: apply message context

refactor: pass `partIndex`

feat: toolCall schema/model/methods

feat: block indexing

feat: get tool calls

chore: imports

chore: typing

chore: condense type imports

feat: get tool calls

fix: block indexing

chore: typing

refactor: update tool calls mapping to support multiple results

fix: add unique key to nav link for rendering

wip: first pass, tool call results

refactor: update query cache from successful tool call mutation

style: improve result switcher styling

chore: note on using \`.toObject()\`

feat: add agent_id field to conversation schema

chore: typing

refactor: rename agentMap to agentsMap for consistency

feat: Agent Name as chat input placeholder

chore: bump agents

📦 chore: update @langchain dependencies to latest versions to match agents package

📦 chore: update @librechat/agents dependency to version 1.8.0

fix: Aborting agent stream removes sender; fix(bedrock): completion removes preset name label

refactor: remove direct file parameter to use req.file, add `processAgentFileUpload` for image uploads

feat: upload menu

feat: prime message_file resources

feat: implement conversation access validation in chat route

refactor: remove file parameter from processFileUpload and use req.file instead

feat: add savedMessageIds set to track saved message IDs in BaseClient, to prevent unnecessary double-write to db

feat: prevent duplicate message saves by checking savedMessageIds in AgentController

refactor: skip legacy RAG API handling for agents

feat: add files field to convoSchema

refactor: update request type annotations from Express.Request to ServerRequest in file processing functions

feat: track conversation files

fix: resendFiles, addPreviousAttachments handling

feat: add ID validation for session_id and file_id in download route

feat: entity_id for code file uploads/downloads

fix: code file edge cases

feat: delete related tool calls

feat: add stream rate handling for LLM configuration

feat: enhance system content with attached file information

fix: improve error logging in resource priming function

* WIP: PoC, sequential agents

WIP: PoC Sequential Agents, first pass content data + bump agents package

fix: package-lock

WIP: PoC, o1 support, refactor bufferString

feat: convertJsonSchemaToZod

fix: form issues and schema defining erroneous model

fix: max length issue on agent form instructions, limit conversation messages to sequential agents

feat: add abort signal support to createRun function and AgentClient

feat: PoC, hide prior sequential agent steps

fix: update parameter naming from config to metadata in event handlers for clarity, add model to usage data

refactor: use only last contentData, track model for usage data

chore: bump agents package

fix: content parts issue

refactor: filter contentParts to include tool calls and relevant indices

feat: show function calls

refactor: filter context messages to exclude tool calls when no tools are available to the agent

fix: ensure tool call content is not undefined in formatMessages

feat: add agent_id field to conversationPreset schema

feat: hide sequential agents

feat: increase upload toast duration to 10 seconds

* refactor: tool context handling & update Code API Key Dialog

feat: toolContextMap

chore: skipSpecs -> useSpecs

ci: fix handleTools tests

feat: API Key Dialog

* feat: Agent Permissions Admin Controls

feat: replace label with button for prompt permission toggle

feat: update agent permissions

feat: enable experimental agents and streamline capability configuration

feat: implement access control for agents and enhance endpoint menu items

feat: add welcome message for agent selection in localization

feat: add agents permission to access control and update version to 0.7.57

* fix: update types in useAssistantListMap and useMentions hooks for better null handling

* feat: mention agents

* fix: agent tool resource race conditions when deleting agent tool resource files

* feat: add error handling for code execution with user feedback

* refactor: rename AdminControls to AdminSettings for clarity

* style: add gap to button in AdminSettings for improved layout

* refactor: separate agent query hooks and check access to enable fetching

* fix: remove unused provider from agent initialization options, creates issue with custom endpoints

* refactor: remove redundant/deprecated modelOptions from AgentClient processes

* chore: update @librechat/agents to version 1.8.5 in package.json and package-lock.json

* fix: minor styling issues + agent panel uniformity

* fix: agent edge cases when set endpoint is no longer defined

* refactor: remove unused cleanup function call from AppService

* fix: update link in ApiKeyDialog to point to pricing page

* fix: improve type handling and layout calculations in SidePanel component

* fix: add missing localization string for agent selection in SidePanel

* chore: form styling and localizations for upload filesearch/code interpreter

* fix: model selection placeholder logic in AgentConfig component

* style: agent capabilities

* fix: add localization for provider selection and improve dropdown styling in ModelPanel

* refactor: use gpt-4o-mini > gpt-3.5-turbo

* fix: agents configuration for loadDefaultInterface and update related tests

* feat: DALLE Agents support
This commit is contained in:
Danny Avila 2024-12-04 15:48:13 -05:00 committed by GitHub
parent affcebd48c
commit 1a815f5e19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
189 changed files with 5056 additions and 1815 deletions

View file

@ -16,7 +16,7 @@ export default function useAssistantListMap<T = AssistantListItem[] | null>(
selector: (res: AssistantListResponse) => T = selectAssistantsResponse as (
res: AssistantListResponse,
) => T,
): Record<AssistantsEndpoint, T> {
): Record<AssistantsEndpoint, T | null> {
const { data: assistantsList = null } = useListAssistantsQuery(
EModelEndpoint.assistants,
undefined,

View file

@ -46,7 +46,7 @@ export default function usePresets() {
return;
}
if (presets && presets.length > 0 && user && presets[0].user !== user?.id) {
if (presets && presets.length > 0 && user && presets[0].user !== user.id) {
presetsQuery.refetch();
return;
}
@ -80,7 +80,7 @@ export default function usePresets() {
}
const previousPresets = presetsQuery.data ?? [];
if (previousPresets) {
setPresets(previousPresets.filter((p) => p.presetId !== preset?.presetId));
setPresets(previousPresets.filter((p) => p.presetId !== preset.presetId));
}
},
onSuccess: () => {
@ -99,12 +99,12 @@ export default function usePresets() {
const updatePreset = useUpdatePresetMutation({
onSuccess: (data, preset) => {
const toastTitle = data.title ? `"${data.title}"` : localize('com_endpoint_preset_title');
let message = `${toastTitle} ${localize('com_endpoint_preset_saved')}`;
let message = `${toastTitle} ${localize('com_ui_saved')}`;
if (data.defaultPreset && data.presetId !== _defaultPreset?.presetId) {
message = `${toastTitle} ${localize('com_endpoint_preset_default')}`;
setDefaultPreset(data);
newConversation({ preset: data });
} else if (preset?.defaultPreset === false) {
} else if (preset.defaultPreset === false) {
setDefaultPreset(null);
message = `${toastTitle} ${localize('com_endpoint_preset_default_removed')}`;
}
@ -233,7 +233,7 @@ export default function usePresets() {
if (!preset) {
return;
}
const fileName = filenamify(preset?.title || 'preset');
const fileName = filenamify(preset.title || 'preset');
exportFromJSON({
data: cleanupPreset({ preset }),
fileName,

View file

@ -25,7 +25,7 @@ export const useDelayedUploadToast = () => {
showToast({
message,
status: 'warning',
duration: 7000,
duration: 10000,
});
}, delay);

View file

@ -8,6 +8,7 @@ import {
EModelEndpoint,
codeTypeMapping,
mergeFileConfig,
isAgentsEndpoint,
isAssistantsEndpoint,
defaultAssistantsVersion,
fileConfig as defaultFileConfig,
@ -38,6 +39,7 @@ const useFileHandling = (params?: UseFileHandling) => {
const [errors, setErrors] = useState<string[]>([]);
const abortControllerRef = useRef<AbortController | null>(null);
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
const [toolResource, setToolResource] = useState<string | undefined>();
const { files, setFiles, setFilesLoading, conversation } = useChatContext();
const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]);
const { addFile, replaceFile, updateFileById, deleteFileById } = useUpdateFiles(
@ -147,6 +149,9 @@ const useFileHandling = (params?: UseFileHandling) => {
: error?.response?.data?.message ?? 'com_error_files_upload';
setError(errorMessage);
},
onMutate: () => {
setToolResource(undefined);
},
},
abortControllerRef.current?.signal,
);
@ -178,6 +183,18 @@ const useFileHandling = (params?: UseFileHandling) => {
}
}
if (isAgentsEndpoint(endpoint)) {
if (!agent_id) {
formData.append('message_file', 'true');
}
if (toolResource != null) {
formData.append('tool_resource', toolResource);
}
if (conversation?.agent_id != null && formData.get('agent_id') == null) {
formData.append('agent_id', conversation.agent_id);
}
}
if (!isAssistantsEndpoint(endpoint)) {
uploadFile.mutate(formData);
return;
@ -377,6 +394,7 @@ const useFileHandling = (params?: UseFileHandling) => {
return {
handleFileChange,
setToolResource,
handleFiles,
abortUpload,
setFiles,

View file

@ -5,17 +5,17 @@ import {
useGetEndpointsQuery,
} from 'librechat-data-provider/react-query';
import {
getConfigDefaults,
EModelEndpoint,
alternateName,
EModelEndpoint,
getConfigDefaults,
isAssistantsEndpoint,
} from 'librechat-data-provider';
import type { AssistantsEndpoint, TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
import type { MentionOption } from '~/common';
import useAssistantListMap from '~/hooks/Assistants/useAssistantListMap';
import { useGetPresetsQuery, useListAgentsQuery } from '~/data-provider';
import { mapEndpoints, getPresetTitle } from '~/utils';
import { EndpointIcon } from '~/components/Endpoints';
import { useGetPresetsQuery } from '~/data-provider';
const defaultInterface = getConfigDefaults().interface;
@ -25,7 +25,7 @@ const assistantMapFn =
assistantMap,
endpointsConfig,
}: {
endpoint: AssistantsEndpoint;
endpoint: EModelEndpoint | string;
assistantMap: TAssistantsMap;
endpointsConfig: TEndpointsConfig;
}) =>
@ -65,6 +65,27 @@ export default function useMentions({
description,
})),
);
const { data: agentsList = null } = useListAgentsQuery(undefined, {
select: (res) => {
const { data } = res;
return data.map(({ id, name, avatar }) => ({
value: id,
label: name ?? '',
type: EModelEndpoint.agents,
icon: EndpointIcon({
conversation: {
agent_id: id,
endpoint: EModelEndpoint.agents,
iconURL: avatar?.filepath,
},
containerClassName: 'shadow-stroke overflow-hidden rounded-full',
endpointsConfig: endpointsConfig,
context: 'menu-item',
size: 20,
}),
}));
},
});
const assistantListMap = useMemo(
() => ({
[EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants]
@ -101,7 +122,7 @@ export default function useMentions({
validEndpoints = endpoints.filter((endpoint) => !isAssistantsEndpoint(endpoint));
}
const mentions = [
...(modelSpecs?.length > 0 ? modelSpecs : []).map((modelSpec) => ({
...(modelSpecs.length > 0 ? modelSpecs : []).map((modelSpec) => ({
value: modelSpec.name,
label: modelSpec.label,
description: modelSpec.description,
@ -116,9 +137,9 @@ export default function useMentions({
}),
type: 'modelSpec' as const,
})),
...(interfaceConfig.endpointsMenu ? validEndpoints : []).map((endpoint) => ({
...(interfaceConfig.endpointsMenu === true ? validEndpoints : []).map((endpoint) => ({
value: endpoint,
label: alternateName[endpoint] ?? endpoint ?? '',
label: alternateName[endpoint as string] ?? endpoint ?? '',
type: 'endpoint' as const,
icon: EndpointIcon({
conversation: { endpoint },
@ -127,13 +148,14 @@ export default function useMentions({
size: 20,
}),
})),
...(agentsList ?? []),
...(endpointsConfig?.[EModelEndpoint.assistants] && includeAssistants
? assistantListMap[EModelEndpoint.assistants] || []
: []),
...(endpointsConfig?.[EModelEndpoint.azureAssistants] && includeAssistants
? assistantListMap[EModelEndpoint.azureAssistants] || []
: []),
...((interfaceConfig.presets ? presets : [])?.map((preset, index) => ({
...((interfaceConfig.presets === true ? presets : [])?.map((preset, index) => ({
value: preset.presetId ?? `preset-${index}`,
label: preset.title ?? preset.modelLabel ?? preset.chatGptLabel ?? '',
description: getPresetTitle(preset, true),
@ -154,6 +176,7 @@ export default function useMentions({
presets,
endpoints,
modelSpecs,
agentsList,
assistantMap,
endpointsConfig,
assistantListMap,
@ -166,6 +189,7 @@ export default function useMentions({
options,
presets,
modelSpecs,
agentsList,
modelsConfig,
endpointsConfig,
assistantListMap,

View file

@ -1,6 +1,6 @@
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type {
TPreset,
TModelSpec,
@ -64,7 +64,11 @@ export default function useSelectMention({
preset.endpointType = newEndpointType;
}
if (isAssistantsEndpoint(newEndpoint) && preset.assistant_id != null && !(preset.model ?? '')) {
if (
isAssistantsEndpoint(newEndpoint) &&
preset.assistant_id != null &&
!(preset.model ?? '')
) {
preset.model = assistantMap?.[newEndpoint]?.[preset.assistant_id]?.model;
}
@ -94,11 +98,19 @@ export default function useSelectMention({
keepAddedConvos: isModular,
});
},
[conversation, getDefaultConversation, modularChat, newConversation, endpointsConfig, assistantMap],
[
conversation,
getDefaultConversation,
modularChat,
newConversation,
endpointsConfig,
assistantMap,
],
);
type Kwargs = {
model?: string;
agent_id?: string;
assistant_id?: string;
};
@ -228,6 +240,10 @@ export default function useSelectMention({
assistant_id: key,
model: assistantMap?.[option.type]?.[key]?.model ?? '',
});
} else if (isAgentsEndpoint(option.type)) {
onSelectEndpoint(option.type, {
agent_id: key,
});
}
},
[modelSpecs, onSelectEndpoint, onSelectPreset, onSelectSpec, presets, assistantMap],

View file

@ -1,11 +1,12 @@
import debounce from 'lodash/debounce';
import { useEffect, useRef, useCallback } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import { isAssistantsEndpoint } from 'librechat-data-provider';
import { Constants } from 'librechat-data-provider';
import type { TEndpointOption } from 'librechat-data-provider';
import type { KeyboardEvent } from 'react';
import { forceResize, insertTextAtCursor, getAssistantName } from '~/utils';
import { forceResize, insertTextAtCursor, getEntityName, getEntity } from '~/utils';
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
import useGetSender from '~/hooks/Conversations/useGetSender';
import useFileHandling from '~/hooks/Files/useFileHandling';
import { useInteractionHealthCheck } from '~/data-provider';
@ -28,6 +29,7 @@ export default function useTextarea({
const localize = useLocalize();
const getSender = useGetSender();
const isComposing = useRef(false);
const agentsMap = useAgentsMapContext();
const { handleFiles } = useFileHandling();
const assistantMap = useAssistantsMapContext();
const checkHealth = useInteractionHealthCheck();
@ -44,19 +46,25 @@ export default function useTextarea({
} = useChatContext();
const [activePrompt, setActivePrompt] = useRecoilState(store.activePromptByIndex(index));
const { conversationId, jailbreak, endpoint = '', assistant_id } = conversation || {};
const { conversationId, jailbreak = false, endpoint = '' } = conversation || {};
const { entity, isAgent, isAssistant } = getEntity({
endpoint,
agentsMap,
assistantMap,
agent_id: conversation?.agent_id,
assistant_id: conversation?.assistant_id,
});
const entityName = entity?.name ?? '';
const isNotAppendable =
((latestMessage?.unfinished && !isSubmitting) || latestMessage?.error) &&
!isAssistantsEndpoint(endpoint);
(((latestMessage?.unfinished ?? false) && !isSubmitting) || (latestMessage?.error ?? false)) &&
!isAssistant;
// && (conversationId?.length ?? 0) > 6; // also ensures that we don't show the wrong placeholder
const assistant =
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint ?? '']?.[assistant_id ?? ''];
const assistantName = (assistant && assistant.name) || '';
useEffect(() => {
if (activePrompt && textAreaRef.current) {
insertTextAtCursor(textAreaRef.current, activePrompt);
const prompt = activePrompt ?? '';
if (prompt && textAreaRef.current) {
insertTextAtCursor(textAreaRef.current, prompt);
forceResize(textAreaRef.current);
setActivePrompt(undefined);
}
@ -64,16 +72,17 @@ export default function useTextarea({
// auto focus to input, when enter a conversation.
useEffect(() => {
if (!conversationId) {
const convoId = conversationId ?? '';
if (!convoId) {
return;
}
// Prevents Settings from not showing on new conversation, also prevents showing toneStyle change without jailbreak
if (conversationId === 'new' || !jailbreak) {
if (convoId === Constants.NEW_CONVO || !jailbreak) {
setShowBingToneSetting(false);
}
if (conversationId !== 'search') {
if (convoId !== Constants.SEARCH) {
textAreaRef.current?.focus();
}
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
@ -89,7 +98,8 @@ export default function useTextarea({
}, [isSubmitting, textAreaRef]);
useEffect(() => {
if (textAreaRef.current?.value) {
const currentValue = textAreaRef.current?.value ?? '';
if (currentValue) {
return;
}
@ -98,10 +108,13 @@ export default function useTextarea({
return localize('com_endpoint_config_placeholder');
}
const currentEndpoint = conversation?.endpoint ?? '';
const currentAgentId = conversation?.agent_id ?? '';
const currentAssistantId = conversation?.assistant_id ?? '';
if (
isAssistantsEndpoint(currentEndpoint) &&
(!currentAssistantId || !assistantMap?.[currentEndpoint]?.[currentAssistantId ?? ''])
if (isAgent && (!currentAgentId || !agentsMap?.[currentAgentId])) {
return localize('com_endpoint_agent_placeholder');
} else if (
isAssistant &&
(!currentAssistantId || !assistantMap?.[currentEndpoint]?.[currentAssistantId])
) {
return localize('com_endpoint_assistant_placeholder');
}
@ -110,9 +123,10 @@ export default function useTextarea({
return localize('com_endpoint_message_not_appendable');
}
const sender = isAssistantsEndpoint(currentEndpoint)
? getAssistantName({ name: assistantName, localize })
: getSender(conversation as TEndpointOption);
const sender =
isAssistant || isAgent
? getEntityName({ name: entityName, isAgent, localize })
: getSender(conversation as TEndpointOption);
return `${localize('com_endpoint_message')} ${sender ? sender : 'AI'}`;
};
@ -137,15 +151,18 @@ export default function useTextarea({
return () => debouncedSetPlaceholder.cancel();
}, [
conversation,
isAgent,
localize,
disabled,
getSender,
agentsMap,
entityName,
textAreaRef,
isAssistant,
assistantMap,
conversation,
latestMessage,
isNotAppendable,
localize,
getSender,
assistantName,
textAreaRef,
assistantMap,
]);
const handleKeyDown = useCallback(
@ -181,7 +198,7 @@ export default function useTextarea({
}
if ((isNonShiftEnter || isCtrlEnter) && !isComposing.current) {
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement;
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement | undefined;
if (globalAudio) {
console.log('Unmuting global audio');
globalAudio.muted = false;
@ -207,14 +224,15 @@ export default function useTextarea({
return;
}
if (!e.clipboardData) {
const clipboardData = e.clipboardData as DataTransfer | undefined;
if (!clipboardData) {
return;
}
if (e.clipboardData.files.length > 0) {
if (clipboardData.files.length > 0) {
setFilesLoading(true);
const timestampedFiles: File[] = [];
for (const file of e.clipboardData.files) {
for (const file of clipboardData.files) {
const newFile = new File([file], `clipboard_${+new Date()}_${file.name}`, {
type: file.type,
});

View file

@ -41,7 +41,7 @@ export default function useMessageActions(props: TMessageActions) {
[isMultiMessage, addedConvo, rootConvo],
);
const agentMap = useAgentsMapContext();
const agentsMap = useAgentsMapContext();
const assistantMap = useAssistantsMapContext();
const { text, content, messageId = null, isCreatedByUser } = message ?? {};
@ -68,20 +68,20 @@ export default function useMessageActions(props: TMessageActions) {
return undefined;
}
if (!agentMap) {
if (!agentsMap) {
return undefined;
}
const modelKey = message?.model ?? '';
if (modelKey) {
return agentMap[modelKey];
return agentsMap[modelKey];
}
const agentId = conversation?.agent_id ?? '';
if (agentId) {
return agentMap[agentId];
return agentsMap[agentId];
}
}, [agentMap, conversation?.agent_id, conversation?.endpoint, message?.model]);
}, [agentsMap, conversation?.agent_id, conversation?.endpoint, message?.model]);
const isSubmitting = useMemo(
() => (isMultiMessage === true ? isSubmittingAdditional : isSubmittingRoot),

View file

@ -22,7 +22,7 @@ export default function useMessageHelpers(props: TMessageProps) {
setLatestMessage,
} = useChatContext();
const assistantMap = useAssistantsMapContext();
const agentMap = useAgentsMapContext();
const agentsMap = useAgentsMapContext();
const { text, content, children, messageId = null, isCreatedByUser } = message ?? {};
const edit = messageId === currentEditId;
@ -102,8 +102,8 @@ export default function useMessageHelpers(props: TMessageProps) {
const modelKey = message?.model ?? '';
return agentMap ? agentMap[modelKey] : undefined;
}, [agentMap, conversation?.endpoint, message?.model]);
return agentsMap ? agentsMap[modelKey] : undefined;
}, [agentsMap, conversation?.endpoint, message?.model]);
const regenerateMessage = () => {
if ((isSubmitting && isCreatedByUser === true) || !message) {

View file

@ -10,9 +10,9 @@ import {
} from 'librechat-data-provider';
import type { TConfig, TInterfaceConfig } from 'librechat-data-provider';
import type { NavLink } from '~/common';
import AgentPanelSwitch from '~/components/SidePanel/Agents/AgentPanelSwitch';
import BookmarkPanel from '~/components/SidePanel/Bookmarks/BookmarkPanel';
import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch';
import AgentPanelSwitch from '~/components/SidePanel/Agents/AgentPanelSwitch';
import PromptsAccordion from '~/components/Prompts/PromptsAccordion';
import Parameters from '~/components/SidePanel/Parameters/Panel';
import FilesPanel from '~/components/SidePanel/Files/Panel';
@ -44,6 +44,14 @@ export default function useSideNavLinks({
permissionType: PermissionTypes.BOOKMARKS,
permission: Permissions.USE,
});
const hasAccessToAgents = useHasAccess({
permissionType: PermissionTypes.AGENTS,
permission: Permissions.USE,
});
const hasAccessToCreateAgents = useHasAccess({
permissionType: PermissionTypes.AGENTS,
permission: Permissions.CREATE,
});
const Links = useMemo(() => {
const links: NavLink[] = [];
@ -64,6 +72,8 @@ export default function useSideNavLinks({
}
if (
hasAccessToAgents &&
hasAccessToCreateAgents &&
isAgentsEndpoint(endpoint) &&
agents &&
// agents.disableBuilder !== true &&
@ -137,8 +147,10 @@ export default function useSideNavLinks({
endpointType,
endpoint,
agents,
hasAccessToAgents,
hasAccessToPrompts,
hasAccessToBookmarks,
hasAccessToCreateAgents,
hidePanel,
]);

View file

@ -1,3 +1,4 @@
export { default as useAuthCodeTool } from './useAuthCodeTool';
export { default as usePluginInstall } from './usePluginInstall';
export { default as useCodeApiKeyForm } from './useCodeApiKeyForm';
export { default as usePluginDialogHelpers } from './usePluginDialogHelpers';

View file

@ -0,0 +1,43 @@
// client/src/hooks/Plugins/useCodeApiKeyForm.ts
import { useState, useCallback } from 'react';
import { useForm } from 'react-hook-form';
import type { ApiKeyFormData } from '~/common';
import useAuthCodeTool from '~/hooks/Plugins/useAuthCodeTool';
export default function useCodeApiKeyForm({
onSubmit,
onRevoke,
}: {
onSubmit?: () => void;
onRevoke?: () => void;
}) {
const methods = useForm<ApiKeyFormData>();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const { installTool, removeTool } = useAuthCodeTool({ isEntityTool: true });
const { reset } = methods;
const onSubmitHandler = useCallback(
(data: { apiKey: string }) => {
reset();
installTool(data.apiKey);
setIsDialogOpen(false);
onSubmit?.();
},
[onSubmit, reset, installTool],
);
const handleRevokeApiKey = useCallback(() => {
reset();
removeTool();
setIsDialogOpen(false);
onRevoke?.();
}, [reset, onRevoke, removeTool]);
return {
methods,
isDialogOpen,
setIsDialogOpen,
handleRevokeApiKey,
onSubmit: onSubmitHandler,
};
}

View file

@ -0,0 +1,28 @@
import { ToolCallResult } from 'librechat-data-provider';
import { useMemo } from 'react';
import { useGetToolCalls } from '~/data-provider';
import { mapToolCalls, logger } from '~/utils';
type ToolCallsMap = {
[x: string]: ToolCallResult[] | undefined;
};
export default function useToolCallsMap({
conversationId,
}: {
conversationId: string;
}): ToolCallsMap | undefined {
const { data: toolCallsMap = null } = useGetToolCalls(
{ conversationId },
{
select: (res) => mapToolCalls(res),
},
);
const result = useMemo<ToolCallsMap | undefined>(() => {
return toolCallsMap !== null ? toolCallsMap : undefined;
}, [toolCallsMap]);
logger.log('tools', 'tool calls map:', result);
return result;
}

View file

@ -21,7 +21,14 @@ type TUseStepHandler = {
type TStepEvent = {
event: string;
data: Agents.MessageDeltaEvent | Agents.RunStep | Agents.ToolEndEvent;
data:
| Agents.MessageDeltaEvent
| Agents.RunStep
| Agents.ToolEndEvent
| {
runId?: string;
message: string;
};
};
type MessageDeltaUpdate = { type: ContentTypes.TEXT; text: string; tool_call_ids?: string[] };
@ -166,6 +173,30 @@ export default function useStepHandler({
}
});
}
} else if (event === 'on_agent_update') {
const { runId, message } = data as { runId?: string; message: string };
const responseMessageId = runId ?? '';
if (!responseMessageId) {
console.warn('No message id found in agent update event');
return;
}
const responseMessage = messages[messages.length - 1] as TMessage;
const response = {
...responseMessage,
parentMessageId: userMessage.messageId,
conversationId: userMessage.conversationId,
messageId: responseMessageId,
content: [
{
type: ContentTypes.TEXT,
text: message,
},
],
} as TMessage;
setMessages([...messages.slice(0, -1), response]);
} else if (event === 'on_message_delta') {
const messageDelta = data as Agents.MessageDeltaEvent;
const runStep = stepMap.current.get(messageDelta.id);