From 24c0433dcf3b84a82eafce055c9076de98efc57b Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 9 Apr 2025 16:11:16 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=96=A5=EF=B8=8F=20feat:=20Code=20Interpre?= =?UTF-8?q?ter=20API=20for=20Non-Agent=20Endpoints=20(#6803)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Prevent parsing 'undefined' string in useLocalStorage initialization * feat: first pass, code interpreter badge * feat: Integrate API key authentication and default checked value in Code Interpreter Badge * refactor: Rename showMCPServers to showEphemeralBadges and update related components, memoize values in useChatBadges * refactor: Enhance AttachFileChat to support ephemeral agents in file attachment logic * fix: Add baseURL configuration option to legacy function call * refactor: Update dependency array in useDragHelpers to include handleFiles * refactor: Update isEphemeralAgent function to accept optional endpoint parameter * refactor: Update file handling to support ephemeral agents in AttachFileMenu and useDragHelpers * fix: improve compatibility issues with OpenAI usage field handling in createRun function * refactor: usage field compatibility * fix: ensure mcp servers are no longer "selected" if mcp servers are now unavailable --- api/app/clients/llm/createLLM.js | 1 + api/models/Agent.js | 22 ++-- api/server/controllers/agents/run.js | 14 ++- client/src/components/Chat/Input/BadgeRow.tsx | 20 +++- client/src/components/Chat/Input/ChatForm.tsx | 2 +- .../components/Chat/Input/CodeInterpreter.tsx | 104 ++++++++++++++++++ .../Chat/Input/Files/AttachFileChat.tsx | 11 +- .../Chat/Input/Files/AttachFileMenu.tsx | 4 +- .../src/components/Chat/Input/MCPSelect.tsx | 28 ++++- client/src/components/ui/CheckboxButton.tsx | 62 +++++++++++ client/src/components/ui/MultiSelect.tsx | 5 +- client/src/hooks/Files/useDragHelpers.ts | 28 +++-- client/src/hooks/useChatBadges.ts | 28 +++-- client/src/hooks/useLocalStorageAlt.tsx | 2 +- client/src/utils/localStorage.ts | 1 + packages/data-provider/src/config.ts | 2 + packages/data-provider/src/createPayload.ts | 6 +- packages/data-provider/src/schemas.ts | 16 +++ packages/data-provider/src/types.ts | 3 +- 19 files changed, 311 insertions(+), 48 deletions(-) create mode 100644 client/src/components/Chat/Input/CodeInterpreter.tsx create mode 100644 client/src/components/ui/CheckboxButton.tsx diff --git a/api/app/clients/llm/createLLM.js b/api/app/clients/llm/createLLM.js index 7dc0d40ce..c8d6666bc 100644 --- a/api/app/clients/llm/createLLM.js +++ b/api/app/clients/llm/createLLM.js @@ -34,6 +34,7 @@ function createLLM({ let credentials = { openAIApiKey }; let configuration = { apiKey: openAIApiKey, + ...(configOptions.basePath && { baseURL: configOptions.basePath }), }; /** @type {AzureOptions} */ diff --git a/api/models/Agent.js b/api/models/Agent.js index 7c0a63080..dba0c40ee 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const { agentSchema } = require('@librechat/data-schemas'); -const { SystemRoles } = require('librechat-data-provider'); +const { SystemRoles, Tools } = require('librechat-data-provider'); const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } = require('librechat-data-provider').Constants; const { CONFIG_STORE, STARTUP_CONFIG } = require('librechat-data-provider').CacheKeys; @@ -51,16 +51,22 @@ const loadEphemeralAgent = ({ req, agent_id, endpoint, model_parameters: _m }) = const mcpServers = new Set(req.body.ephemeralAgent?.mcp); /** @type {string[]} */ const tools = []; + if (req.body.ephemeralAgent?.execute_code === true) { + tools.push(Tools.execute_code); + } - for (const toolName of Object.keys(availableTools)) { - if (!toolName.includes(mcp_delimiter)) { - continue; - } - const mcpServer = toolName.split(mcp_delimiter)?.[1]; - if (mcpServer && mcpServers.has(mcpServer)) { - tools.push(toolName); + if (mcpServers.size > 0) { + for (const toolName of Object.keys(availableTools)) { + if (!toolName.includes(mcp_delimiter)) { + continue; + } + const mcpServer = toolName.split(mcp_delimiter)?.[1]; + if (mcpServer && mcpServers.has(mcpServer)) { + tools.push(toolName); + } } } + const instructions = req.body.promptPrefix; return { id: agent_id, diff --git a/api/server/controllers/agents/run.js b/api/server/controllers/agents/run.js index 2efde5d06..2452e6623 100644 --- a/api/server/controllers/agents/run.js +++ b/api/server/controllers/agents/run.js @@ -11,6 +11,13 @@ const { providerEndpointMap, KnownEndpoints } = require('librechat-data-provider * @typedef {import('@librechat/agents').IState} IState */ +const customProviders = new Set([ + Providers.XAI, + Providers.OLLAMA, + Providers.DEEPSEEK, + Providers.OPENROUTER, +]); + /** * Creates a new Run instance with custom handlers and configuration. * @@ -43,8 +50,11 @@ async function createRun({ agent.model_parameters, ); - /** Resolves Mistral type strictness due to new OpenAI usage field */ - if (agent.endpoint?.toLowerCase().includes(KnownEndpoints.mistral)) { + /** Resolves issues with new OpenAI usage field */ + if ( + customProviders.has(agent.provider) || + (agent.provider === Providers.OPENAI && agent.endpoint !== agent.provider) + ) { llmConfig.streamUsage = false; llmConfig.usage = true; } diff --git a/client/src/components/Chat/Input/BadgeRow.tsx b/client/src/components/Chat/Input/BadgeRow.tsx index 0de28cb34..15e99bd42 100644 --- a/client/src/components/Chat/Input/BadgeRow.tsx +++ b/client/src/components/Chat/Input/BadgeRow.tsx @@ -10,6 +10,7 @@ import React, { } from 'react'; import { useRecoilValue, useRecoilCallback } from 'recoil'; import type { LucideIcon } from 'lucide-react'; +import CodeInterpreter from './CodeInterpreter'; import type { BadgeItem } from '~/common'; import { useChatBadges } from '~/hooks'; import { Badge } from '~/components/ui'; @@ -17,7 +18,7 @@ import MCPSelect from './MCPSelect'; import store from '~/store'; interface BadgeRowProps { - showMCPServers?: boolean; + showEphemeralBadges?: boolean; onChange: (badges: Pick[]) => void; onToggle?: (badgeId: string, currentActive: boolean) => void; conversationId?: string | null; @@ -131,7 +132,13 @@ const dragReducer = (state: DragState, action: DragAction): DragState => { } }; -function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat }: BadgeRowProps) { +function BadgeRow({ + showEphemeralBadges, + conversationId, + onChange, + onToggle, + isInChat, +}: BadgeRowProps) { const [orderedBadges, setOrderedBadges] = useState([]); const [dragState, dispatch] = useReducer(dragReducer, { draggedBadge: null, @@ -146,7 +153,7 @@ function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat const animationFrame = useRef(null); const containerRectRef = useRef(null); - const allBadges = useChatBadges() || []; + const allBadges = useChatBadges(); const isEditing = useRecoilValue(store.isEditingBadges); const badges = useMemo( @@ -345,7 +352,12 @@ function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat /> )} - {showMCPServers === true && } + {showEphemeralBadges === true && ( + <> + + + + )} {ghostBadge && (
{
{ + return ephemeralAgent?.execute_code ?? false; + }, [ephemeralAgent?.execute_code]); + + const { data } = useVerifyAgentToolAuth( + { toolId: Tools.execute_code }, + { + retry: 1, + }, + ); + const authType = useMemo(() => data?.message ?? false, [data?.message]); + const isAuthenticated = useMemo(() => data?.authenticated ?? false, [data?.authenticated]); + const { methods, onSubmit, isDialogOpen, setIsDialogOpen, handleRevokeApiKey } = + useCodeApiKeyForm({}); + + const setValue = useCallback( + (isChecked: boolean) => { + setEphemeralAgent((prev) => ({ + ...prev, + execute_code: isChecked, + })); + }, + [setEphemeralAgent], + ); + + const [runCode, setRunCode] = useLocalStorage( + `${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`, + isCodeToggleEnabled, + setValue, + ); + + const handleChange = useCallback( + (isChecked: boolean) => { + if (!isAuthenticated) { + setIsDialogOpen(true); + return; + } + setRunCode(isChecked); + }, + [setRunCode, setIsDialogOpen, isAuthenticated], + ); + + const debouncedChange = useMemo( + () => debounce(handleChange, 50, { leading: true }), + [handleChange], + ); + + if (!canRunCode) { + return null; + } + + return ( + <> + } + /> + + + ); +} + +export default memo(CodeInterpreter); diff --git a/client/src/components/Chat/Input/Files/AttachFileChat.tsx b/client/src/components/Chat/Input/Files/AttachFileChat.tsx index b4b0de1a6..11bca082f 100644 --- a/client/src/components/Chat/Input/Files/AttachFileChat.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileChat.tsx @@ -1,24 +1,31 @@ import { memo, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { + Constants, supportsFiles, mergeFileConfig, isAgentsEndpoint, + isEphemeralAgent, EndpointFileConfig, fileConfig as defaultFileConfig, } from 'librechat-data-provider'; import { useChatContext } from '~/Providers'; import { useGetFileConfig } from '~/data-provider'; +import { ephemeralAgentByConvoId } from '~/store'; import AttachFileMenu from './AttachFileMenu'; import AttachFile from './AttachFile'; -import store from '~/store'; function AttachFileChat({ disableInputs }: { disableInputs: boolean }) { const { conversation } = useChatContext(); const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null }; - const isAgents = useMemo(() => isAgentsEndpoint(_endpoint), [_endpoint]); + const key = conversation?.conversationId ?? Constants.NEW_CONVO; + const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(key)); + const isAgents = useMemo( + () => isAgentsEndpoint(_endpoint) || isEphemeralAgent(_endpoint, ephemeralAgent), + [_endpoint, ephemeralAgent], + ); const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ select: (data) => mergeFileConfig(data), diff --git a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx index 0f5f8e05a..f827d39f4 100644 --- a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx @@ -18,7 +18,9 @@ const AttachFile = ({ disabled }: AttachFileProps) => { const [isPopoverActive, setIsPopoverActive] = useState(false); const [toolResource, setToolResource] = useState(); const { data: endpointsConfig } = useGetEndpointsQuery(); - const { handleFileChange } = useFileHandling(); + const { handleFileChange } = useFileHandling({ + overrideEndpoint: EModelEndpoint.agents, + }); const capabilities = useMemo( () => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [], diff --git a/client/src/components/Chat/Input/MCPSelect.tsx b/client/src/components/Chat/Input/MCPSelect.tsx index adbdc2975..270228fe5 100644 --- a/client/src/components/Chat/Input/MCPSelect.tsx +++ b/client/src/components/Chat/Input/MCPSelect.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from 'react'; +import React, { memo, useRef, useMemo, useEffect, useCallback } from 'react'; import { useRecoilState } from 'recoil'; import { Constants, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider'; import { useAvailableToolsQuery } from '~/data-provider'; @@ -10,8 +10,12 @@ import { useLocalize } from '~/hooks'; function MCPSelect({ conversationId }: { conversationId?: string | null }) { const localize = useLocalize(); + const hasSetFetched = useRef(false); const key = conversationId ?? Constants.NEW_CONVO; const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key)); + const mcpState = useMemo(() => { + return ephemeralAgent?.mcp ?? []; + }, [ephemeralAgent?.mcp]); const setSelectedValues = useCallback( (values: string[] | null | undefined) => { if (!values) { @@ -29,10 +33,10 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { ); const [mcpValues, setMCPValues] = useLocalStorage( `${LocalStorageKeys.LAST_MCP_}${key}`, - ephemeralAgent?.mcp ?? [], + mcpState, setSelectedValues, ); - const { data: mcpServers } = useAvailableToolsQuery(EModelEndpoint.agents, { + const { data: mcpServers, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, { select: (data) => { const serverNames = new Set(); data.forEach((tool) => { @@ -45,6 +49,20 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { }, }); + useEffect(() => { + if (hasSetFetched.current) { + return; + } + if (!isFetched) { + return; + } + hasSetFetched.current = true; + if ((mcpServers?.length ?? 0) > 0) { + return; + } + setMCPValues([]); + }, [isFetched, setMCPValues, mcpServers?.length]); + const renderSelectedValues = useCallback( (values: string[], placeholder?: string) => { if (values.length === 0) { @@ -70,8 +88,8 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { defaultSelectedValues={mcpValues ?? []} renderSelectedValues={renderSelectedValues} placeholder={localize('com_ui_mcp_servers')} - popoverClassName="min-w-[200px]" - className="badge-icon h-full min-w-[150px]" + popoverClassName="min-w-fit" + className="badge-icon min-w-fit" selectIcon={} selectItemsClassName="border border-blue-600/50 bg-blue-500/10 hover:bg-blue-700/10" selectClassName="group relative inline-flex items-center justify-center md:justify-start gap-1.5 rounded-full border border-border-medium text-sm font-medium transition-shadow md:w-full size-9 p-2 md:p-3 bg-surface-chat shadow-sm hover:bg-surface-hover hover:shadow-md active:shadow-inner" diff --git a/client/src/components/ui/CheckboxButton.tsx b/client/src/components/ui/CheckboxButton.tsx new file mode 100644 index 000000000..988340c06 --- /dev/null +++ b/client/src/components/ui/CheckboxButton.tsx @@ -0,0 +1,62 @@ +import { useEffect } from 'react'; +import { Checkbox, useStoreState, useCheckboxStore } from '@ariakit/react'; +import { cn } from '~/utils'; + +export default function CheckboxButton({ + label, + icon, + setValue, + className, + defaultChecked, + isCheckedClassName, +}: { + label: string; + className?: string; + icon?: React.ReactNode; + defaultChecked?: boolean; + isCheckedClassName?: string; + setValue?: (isChecked: boolean) => void; +}) { + const checkbox = useCheckboxStore(); + const isChecked = useStoreState(checkbox, (state) => state?.value); + const onChange = (e: React.ChangeEvent) => { + e.stopPropagation(); + if (typeof isChecked !== 'boolean') { + return; + } + setValue?.(!isChecked); + }; + useEffect(() => { + if (defaultChecked) { + checkbox.setValue(defaultChecked); + } + }, [defaultChecked, checkbox]); + + return ( + } + > + {/* Icon if provided */} + {icon && {icon}} + + {/* Show the label on larger screens */} + {label} + + ); +} diff --git a/client/src/components/ui/MultiSelect.tsx b/client/src/components/ui/MultiSelect.tsx index 9de278072..ddbd5c90a 100644 --- a/client/src/components/ui/MultiSelect.tsx +++ b/client/src/components/ui/MultiSelect.tsx @@ -66,7 +66,7 @@ export default function MultiSelect({ }; return ( -
+
{label && ( @@ -82,9 +82,10 @@ export default function MultiSelect({ selectClassName, selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName, )} + onChange={(e) => e.stopPropagation()} > {selectIcon && selectIcon} - + {renderSelectedValues(selectedValues, placeholder)} diff --git a/client/src/hooks/Files/useDragHelpers.ts b/client/src/hooks/Files/useDragHelpers.ts index 32cd83f9b..af4530e62 100644 --- a/client/src/hooks/Files/useDragHelpers.ts +++ b/client/src/hooks/Files/useDragHelpers.ts @@ -4,22 +4,28 @@ import { useRecoilValue } from 'recoil'; import { NativeTypes } from 'react-dnd-html5-backend'; import { useQueryClient } from '@tanstack/react-query'; import { - isAgentsEndpoint, - EModelEndpoint, - AgentCapabilities, + Constants, QueryKeys, + EModelEndpoint, + isAgentsEndpoint, + isEphemeralAgent, + AgentCapabilities, } from 'librechat-data-provider'; import type * as t from 'librechat-data-provider'; import type { DropTargetMonitor } from 'react-dnd'; import useFileHandling from './useFileHandling'; -import store from '~/store'; +import store, { ephemeralAgentByConvoId } from '~/store'; export default function useDragHelpers() { const queryClient = useQueryClient(); - const { handleFiles } = useFileHandling(); const [showModal, setShowModal] = useState(false); const [draggedFiles, setDraggedFiles] = useState([]); const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; + const key = useMemo( + () => conversation?.conversationId ?? Constants.NEW_CONVO, + [conversation?.conversationId], + ); + const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(key)); const handleOptionSelect = (toolResource: string | undefined) => { handleFiles(draggedFiles, toolResource); @@ -28,10 +34,16 @@ export default function useDragHelpers() { }; const isAgents = useMemo( - () => isAgentsEndpoint(conversation?.endpoint), - [conversation?.endpoint], + () => + isAgentsEndpoint(conversation?.endpoint) || + isEphemeralAgent(conversation?.endpoint, ephemeralAgent), + [conversation?.endpoint, ephemeralAgent], ); + const { handleFiles } = useFileHandling({ + overrideEndpoint: isAgents ? EModelEndpoint.agents : undefined, + }); + const [{ canDrop, isOver }, drop] = useDrop( () => ({ accept: [NativeTypes.FILE], @@ -61,7 +73,7 @@ export default function useDragHelpers() { canDrop: monitor.canDrop(), }), }), - [], + [handleFiles], ); return { diff --git a/client/src/hooks/useChatBadges.ts b/client/src/hooks/useChatBadges.ts index 022f3f7a6..abc23ee30 100644 --- a/client/src/hooks/useChatBadges.ts +++ b/client/src/hooks/useChatBadges.ts @@ -1,8 +1,9 @@ +import { useMemo } from 'react'; import { useRecoilCallback } from 'recoil'; import { useRecoilValue } from 'recoil'; import { MessageCircleDashed, Box } from 'lucide-react'; import type { BadgeItem } from '~/common'; -import { useLocalize } from '~/hooks'; +import { useLocalize, TranslationKeys } from '~/hooks'; import store from '~/store'; interface ChatBadgeConfig { @@ -25,15 +26,22 @@ const badgeConfig: ReadonlyArray = [ export default function useChatBadges(): BadgeItem[] { const localize = useLocalize(); const activeBadges = useRecoilValue(store.chatBadges) as Array<{ id: string }>; - const activeBadgeIds = new Set(activeBadges.map((badge) => badge.id)); - - return badgeConfig.map((cfg) => ({ - id: cfg.id, - label: localize(cfg.label), - icon: cfg.icon, - atom: cfg.atom, - isAvailable: activeBadgeIds.has(cfg.id), - })); + const activeBadgeIds = useMemo( + () => new Set(activeBadges.map((badge) => badge.id)), + [activeBadges], + ); + const allBadges = useMemo(() => { + return ( + badgeConfig.map((cfg) => ({ + id: cfg.id, + label: localize(cfg.label as TranslationKeys), + icon: cfg.icon, + atom: cfg.atom, + isAvailable: activeBadgeIds.has(cfg.id), + })) || [] + ); + }, [activeBadgeIds, localize]); + return allBadges; } export function useResetChatBadges() { diff --git a/client/src/hooks/useLocalStorageAlt.tsx b/client/src/hooks/useLocalStorageAlt.tsx index 465d62c70..ef6319824 100644 --- a/client/src/hooks/useLocalStorageAlt.tsx +++ b/client/src/hooks/useLocalStorageAlt.tsx @@ -21,7 +21,7 @@ export default function useLocalStorage( localStorage.setItem(key, JSON.stringify(defaultValue)); } - const initialValue = item ? JSON.parse(item) : defaultValue; + const initialValue = item && item !== 'undefined' ? JSON.parse(item) : defaultValue; setValue(initialValue); if (globalSetState) { globalSetState(initialValue); diff --git a/client/src/utils/localStorage.ts b/client/src/utils/localStorage.ts index 35bd12232..cb1dee940 100644 --- a/client/src/utils/localStorage.ts +++ b/client/src/utils/localStorage.ts @@ -32,6 +32,7 @@ export function clearLocalStorage(skipFirst?: boolean) { } if ( key.startsWith(LocalStorageKeys.LAST_MCP_) || + key.startsWith(LocalStorageKeys.LAST_CODE_TOGGLE_) || key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) || key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) || key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP) || diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 1b8d815a7..6e6291952 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -1285,6 +1285,8 @@ export enum LocalStorageKeys { SHOW_ANALYSIS_CODE = 'showAnalysisCode', /** Last selected MCP values per conversation ID */ LAST_MCP_ = 'LAST_MCP_', + /** Last checked toggle for Code Interpreter API per conversation ID */ + LAST_CODE_TOGGLE_ = 'LAST_CODE_TOGGLE_', } export enum ForkOptions { diff --git a/packages/data-provider/src/createPayload.ts b/packages/data-provider/src/createPayload.ts index 19d94cec4..edd8f0e42 100644 --- a/packages/data-provider/src/createPayload.ts +++ b/packages/data-provider/src/createPayload.ts @@ -19,20 +19,20 @@ export default function createPayload(submission: t.TSubmission) { }; let server = EndpointURLs[endpointType ?? endpoint]; - const isEphemeralAgent = (ephemeralAgent?.mcp?.length ?? 0) > 0 && !s.isAgentsEndpoint(endpoint); + const isEphemeral = s.isEphemeralAgent(endpoint, ephemeralAgent); if (isEdited && s.isAssistantsEndpoint(endpoint)) { server += '/modify'; } else if (isEdited) { server = server.replace('/ask/', '/edit/'); - } else if (isEphemeralAgent) { + } else if (isEphemeral) { server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`; } const payload: t.TPayload = { ...userMessage, ...endpointOption, - ephemeralAgent: isEphemeralAgent ? ephemeralAgent : undefined, + ephemeralAgent: isEphemeral ? ephemeralAgent : undefined, isContinued: !!(isEdited && isContinued), conversationId, isTemporary, diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index b923a1a46..f3335a894 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { Tools } from './types/assistants'; import type { TMessageContentParts, FunctionTool, FunctionToolCall } from './types/assistants'; +import type { TEphemeralAgent } from './types'; import type { TFile } from './types/files'; export const isUUID = z.string().uuid(); @@ -88,6 +89,21 @@ export const isAgentsEndpoint = (_endpoint?: EModelEndpoint.agents | null | stri return endpoint === EModelEndpoint.agents; }; +export const isEphemeralAgent = ( + endpoint?: EModelEndpoint.agents | null | string, + ephemeralAgent?: TEphemeralAgent | null, +) => { + if (!ephemeralAgent) { + return false; + } + if (isAgentsEndpoint(endpoint)) { + return false; + } + const hasMCPSelected = (ephemeralAgent?.mcp?.length ?? 0) > 0; + const hasCodeSelected = (ephemeralAgent?.execute_code ?? false) === true; + return hasMCPSelected || hasCodeSelected; +}; + export const isParamEndpoint = ( endpoint: EModelEndpoint | string, endpointType?: EModelEndpoint | string, diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index dabe89cdb..1ac3d1f46 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -42,7 +42,8 @@ export type TEndpointOption = { }; export type TEphemeralAgent = { - mcp: string[]; + mcp?: string[]; + execute_code?: boolean; }; export type TPayload = Partial &