From 1909efd6ba5cd79c001b3f36453b19200001d342 Mon Sep 17 00:00:00 2001 From: Tim Manik <115643393+timmanik@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:57:33 -0400 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=93=9D=20docs:=20Added=20`RAG=5FUSE?= =?UTF-8?q?=5FFULL=5FCONTEXT`=20to=20`.env.example`=20(#4494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 75604e4447c..f77bb75af27 100644 --- a/.env.example +++ b/.env.example @@ -302,6 +302,7 @@ TTS_API_KEY= # RAG_OPENAI_BASEURL= # RAG_OPENAI_API_KEY= +# RAG_USE_FULL_CONTEXT= # EMBEDDINGS_PROVIDER=openai # EMBEDDINGS_MODEL=text-embedding-3-small From 95011ce349229cfab0379fdf320e452dcefc278c Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 1 Nov 2024 18:36:39 -0400 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=9A=A7=20WIP:=20Merge=20Dev=20Build?= =?UTF-8?q?=20(#4611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Agent CodeFiles, abortUpload WIP * feat: code environment file upload * refactor: useLazyEffect * refactor: - Add `watch` from `useFormContext` to check if code execution is enabled - Disable file upload button if `agent_id` is not selected or code execution is disabled * WIP: primeCodeFiles; refactor: rename sessionId to session_id for uniformity * Refactor: Rename session_id to sessionId for uniformity in AuthService.js * chore: bump @librechat/agents to version 1.7.1 * WIP: prime code files * refactor: Update code env file upload method to use read stream * feat: reupload code env file if no longer active * refactor: isAssistantTool -> isEntityTool + address type issues * feat: execute code tool hook * refactor: Rename isPluginAuthenticated to checkPluginAuth in PluginController.js * refactor: Update PluginController.js to use AuthType constant for comparison * feat: verify tool authentication (execute_code) * feat: enter librechat_code_api_key * refactor: Remove unused imports in BookmarkForm.tsx * feat: authenticate code tool * refactor: Update Action.tsx to conditionally render the key and revoke key buttons * refactor(Code/Action): prevent uncheck-able 'Run Code' capability when key is revoked * refactor(Code/Action): Update Action.tsx to conditionally render the key and revoke key buttons * fix: agent file upload edge cases * chore: bump @librechat/agents * fix: custom endpoint providerValue icon * feat: ollama meta modal token values + context * feat: ollama agents * refactor: Update token models for Ollama models * chore: Comment out CodeForm * refactor: Update token models for Ollama and Meta models --- api/app/clients/tools/util/handleTools.js | 7 +- api/models/schema/fileSchema.js | 5 + api/models/tx.js | 7 + api/package.json | 2 +- api/server/controllers/PluginController.js | 10 +- api/server/controllers/UserController.js | 4 +- api/server/controllers/agents/callbacks.js | 15 +- api/server/controllers/agents/client.js | 2 - api/server/controllers/agents/run.js | 3 +- api/server/controllers/tools.js | 53 + api/server/routes/agents/tools.js | 22 + api/server/routes/agents/v1.js | 5 +- api/server/routes/files/files.js | 41 +- .../services/Endpoints/agents/initialize.js | 8 +- .../services/Endpoints/custom/initialize.js | 18 +- api/server/services/Files/Code/crud.js | 52 +- api/server/services/Files/Code/process.js | 140 +- api/server/services/Files/Firebase/crud.js | 20 +- api/server/services/Files/Local/crud.js | 7 +- api/server/services/Files/OpenAI/crud.js | 4 +- api/server/services/Files/VectorDB/crud.js | 2 +- api/server/services/Files/process.js | 60 +- api/server/services/Files/strategies.js | 14 +- api/utils/tokens.js | 27 +- api/utils/tokens.spec.js | 107 +- .../src/components/Bookmarks/BookmarkForm.tsx | 2 +- .../Chat/Menus/Endpoints/UnknownIcon.tsx | 10 +- .../Plugins/Store/PluginAuthForm.tsx | 19 +- .../SidePanel/Agents/AgentConfig.tsx | 31 +- .../components/SidePanel/Agents/AgentTool.tsx | 4 +- .../SidePanel/Agents/CapabilitiesForm.tsx | 56 - .../src/components/SidePanel/Agents/Code.tsx | 70 - .../SidePanel/Agents/Code/Action.tsx | 151 +++ .../Agents/{CodeFiles.tsx => Code/Files.tsx} | 39 +- .../components/SidePanel/Agents/Code/Form.tsx | 33 + .../SidePanel/Agents/FileSearch.tsx | 23 +- .../SidePanel/Builder/AssistantTool.tsx | 6 +- .../src/components/Tools/ToolSelectDialog.tsx | 4 +- client/src/data-provider/Files/mutations.ts | 16 +- client/src/data-provider/Tools/index.ts | 2 + client/src/data-provider/Tools/queries.ts | 20 + client/src/data-provider/index.ts | 1 + client/src/hooks/Generic/index.ts | 1 + client/src/hooks/Generic/useLazyEffect.ts | 18 + client/src/hooks/Plugins/index.ts | 1 + client/src/hooks/Plugins/useAuthCodeTool.ts | 53 + client/src/hooks/index.ts | 1 + package-lock.json | 1154 +++++++---------- packages/data-provider/src/api-endpoints.ts | 4 +- packages/data-provider/src/config.ts | 1 + packages/data-provider/src/data-service.ts | 10 + packages/data-provider/src/keys.ts | 1 + .../src/react-query/react-query-service.ts | 14 +- packages/data-provider/src/schemas.ts | 4 +- packages/data-provider/src/types.ts | 5 +- packages/data-provider/src/types/files.ts | 1 + packages/data-provider/src/types/mutations.ts | 3 + packages/data-provider/src/types/queries.ts | 23 +- 58 files changed, 1416 insertions(+), 1000 deletions(-) create mode 100644 api/server/controllers/tools.js create mode 100644 api/server/routes/agents/tools.js delete mode 100644 client/src/components/SidePanel/Agents/CapabilitiesForm.tsx delete mode 100644 client/src/components/SidePanel/Agents/Code.tsx create mode 100644 client/src/components/SidePanel/Agents/Code/Action.tsx rename client/src/components/SidePanel/Agents/{CodeFiles.tsx => Code/Files.tsx} (76%) create mode 100644 client/src/components/SidePanel/Agents/Code/Form.tsx create mode 100644 client/src/data-provider/Tools/index.ts create mode 100644 client/src/data-provider/Tools/queries.ts create mode 100644 client/src/hooks/Generic/index.ts create mode 100644 client/src/hooks/Generic/useLazyEffect.ts create mode 100644 client/src/hooks/Plugins/useAuthCodeTool.ts diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 7a7be797d56..6f1acb549b5 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -24,6 +24,7 @@ const { StructuredWolfram, TavilySearchResults, } = require('../'); +const { primeFiles } = require('~/server/services/Files/Code/process'); const createFileSearchTool = require('./createFileSearchTool'); const { loadToolSuite } = require('./loadToolSuite'); const { loadSpecs } = require('./loadSpecs'); @@ -255,12 +256,14 @@ const loadTools = async ({ for (const tool of tools) { if (tool === Tools.execute_code) { const authValues = await loadAuthValues({ - userId: user.id, + userId: user, authFields: [EnvVar.CODE_API_KEY], }); + const files = await primeFiles(options, authValues[EnvVar.CODE_API_KEY]); requestedTools[tool] = () => createCodeExecutionTool({ - user_id: user.id, + user_id: user, + files, ...authValues, }); continue; diff --git a/api/models/schema/fileSchema.js b/api/models/schema/fileSchema.js index da3f31c4065..77c6ff94d4a 100644 --- a/api/models/schema/fileSchema.js +++ b/api/models/schema/fileSchema.js @@ -21,6 +21,8 @@ const mongoose = require('mongoose'); * @property {string} [source] - The source of the file (e.g., from FileSources) * @property {number} [width] - Optional width of the file * @property {number} [height] - Optional height of the file + * @property {Object} [metadata] - Metadata related to the file + * @property {string} [metadata.fileIdentifier] - Unique identifier for the file in metadata * @property {Date} [expiresAt] - Optional expiration date of the file * @property {Date} [createdAt] - Date when the file was created * @property {Date} [updatedAt] - Date when the file was updated @@ -91,6 +93,9 @@ const fileSchema = mongoose.Schema( }, width: Number, height: Number, + metadata: { + fileIdentifier: String, + }, expiresAt: { type: Date, expires: 3600, // 1 hour in seconds diff --git a/api/models/tx.js b/api/models/tx.js index e7960482c47..1b1501bd282 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -10,6 +10,13 @@ const bedrockValues = { 'llama3-1-8b': { prompt: 0.3, completion: 0.6 }, 'llama3-1-70b': { prompt: 2.65, completion: 3.5 }, 'llama3-1-405b': { prompt: 5.32, completion: 16.0 }, + 'llama2:13b': { prompt: 0.75, completion: 1.0 }, + 'llama2:70b': { prompt: 1.95, completion: 2.56 }, + 'llama3:8b': { prompt: 0.3, completion: 0.6 }, + 'llama3:70b': { prompt: 2.65, completion: 3.5 }, + 'llama3.1:8b': { prompt: 0.3, completion: 0.6 }, + 'llama3.1:70b': { prompt: 2.65, completion: 3.5 }, + 'llama3.1:405b': { prompt: 5.32, completion: 16.0 }, 'mistral-7b': { prompt: 0.15, completion: 0.2 }, 'mistral-small': { prompt: 0.15, completion: 0.2 }, 'mixtral-8x7b': { prompt: 0.45, completion: 0.7 }, diff --git a/api/package.json b/api/package.json index 68f6a3c823b..22677f6c241 100644 --- a/api/package.json +++ b/api/package.json @@ -43,7 +43,7 @@ "@langchain/core": "^0.2.18", "@langchain/google-genai": "^0.0.11", "@langchain/google-vertexai": "^0.0.17", - "@librechat/agents": "^1.6.9", + "@librechat/agents": "^1.7.7", "axios": "^1.7.7", "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index 5bb34671f8d..3c7085c2a0e 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -1,5 +1,5 @@ const { promises: fs } = require('fs'); -const { CacheKeys } = require('librechat-data-provider'); +const { CacheKeys, AuthType } = require('librechat-data-provider'); const { addOpenAPISpecs } = require('~/app/clients/tools/util/addOpenAPISpecs'); const { getLogStores } = require('~/cache'); @@ -25,7 +25,7 @@ const filterUniquePlugins = (plugins) => { * @param {TPlugin} plugin The plugin object containing the authentication configuration. * @returns {boolean} True if the plugin is authenticated for all required fields, false otherwise. */ -const isPluginAuthenticated = (plugin) => { +const checkPluginAuth = (plugin) => { if (!plugin.authConfig || plugin.authConfig.length === 0) { return false; } @@ -36,7 +36,7 @@ const isPluginAuthenticated = (plugin) => { for (const fieldOption of authFieldOptions) { const envValue = process.env[fieldOption]; - if (envValue && envValue.trim() !== '' && envValue !== 'user_provided') { + if (envValue && envValue.trim() !== '' && envValue !== AuthType.USER_PROVIDED) { isFieldAuthenticated = true; break; } @@ -64,7 +64,7 @@ const getAvailablePluginsController = async (req, res) => { let authenticatedPlugins = []; for (const plugin of uniquePlugins) { authenticatedPlugins.push( - isPluginAuthenticated(plugin) ? { ...plugin, authenticated: true } : plugin, + checkPluginAuth(plugin) ? { ...plugin, authenticated: true } : plugin, ); } @@ -111,7 +111,7 @@ const getAvailableTools = async (req, res) => { const uniquePlugins = filterUniquePlugins(jsonData); const authenticatedPlugins = uniquePlugins.map((plugin) => { - if (isPluginAuthenticated(plugin)) { + if (checkPluginAuth(plugin)) { return { ...plugin, authenticated: true }; } else { return plugin; diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index abe89d2b0a8..f9ed887b15a 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -61,10 +61,10 @@ const deleteUserFiles = async (req) => { const updateUserPluginsController = async (req, res) => { const { user } = req; - const { pluginKey, action, auth, isAssistantTool } = req.body; + const { pluginKey, action, auth, isEntityTool } = req.body; let authService; try { - if (!isAssistantTool) { + if (!isEntityTool) { const userPluginsService = await updateUserPluginsService(user, pluginKey, action); if (userPluginsService instanceof Error) { diff --git a/api/server/controllers/agents/callbacks.js b/api/server/controllers/agents/callbacks.js index 7bbb2212aa6..209de71714a 100644 --- a/api/server/controllers/agents/callbacks.js +++ b/api/server/controllers/agents/callbacks.js @@ -1,6 +1,12 @@ const { Tools } = require('librechat-data-provider'); -const { GraphEvents, ToolEndHandler, ChatModelStreamHandler } = require('@librechat/agents'); +const { + EnvVar, + GraphEvents, + ToolEndHandler, + ChatModelStreamHandler, +} = require('@librechat/agents'); const { processCodeOutput } = require('~/server/services/Files/Code/process'); +const { loadAuthValues } = require('~/app/clients/tools/util'); const { logger } = require('~/config'); /** @typedef {import('@librechat/agents').Graph} Graph */ @@ -158,13 +164,18 @@ function createToolEndCallback({ req, res, artifactPromises }) { const { id, name } = file; artifactPromises.push( (async () => { + const result = await loadAuthValues({ + userId: req.user.id, + authFields: [EnvVar.CODE_API_KEY], + }); const fileMetadata = await processCodeOutput({ req, id, name, + apiKey: result[EnvVar.CODE_API_KEY], toolCallId: tool_call_id, messageId: metadata.run_id, - sessionId: artifact.session_id, + session_id: artifact.session_id, conversationId: metadata.thread_id, }); if (!res.headersSent) { diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index f035d546f28..0534014bec7 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -15,7 +15,6 @@ const { EModelEndpoint, anthropicSchema, bedrockOutputParser, - providerEndpointMap, removeNullishValues, } = require('librechat-data-provider'); const { @@ -465,7 +464,6 @@ class AgentClient extends BaseClient { const config = { configurable: { - provider: providerEndpointMap[this.options.agent.provider], thread_id: this.conversationId, }, signal: abortController.signal, diff --git a/api/server/controllers/agents/run.js b/api/server/controllers/agents/run.js index 2377ce64f13..241d2247302 100644 --- a/api/server/controllers/agents/run.js +++ b/api/server/controllers/agents/run.js @@ -35,9 +35,10 @@ async function createRun({ streaming = true, streamUsage = true, }) { + const provider = providerEndpointMap[agent.provider] ?? agent.provider; const llmConfig = Object.assign( { - provider: providerEndpointMap[agent.provider], + provider, streaming, streamUsage, }, diff --git a/api/server/controllers/tools.js b/api/server/controllers/tools.js new file mode 100644 index 00000000000..9fd9cb2942b --- /dev/null +++ b/api/server/controllers/tools.js @@ -0,0 +1,53 @@ +const { EnvVar } = require('@librechat/agents'); +const { Tools, AuthType } = require('librechat-data-provider'); +const { loadAuthValues } = require('~/app/clients/tools/util'); + +const fieldsMap = { + [Tools.execute_code]: [EnvVar.CODE_API_KEY], +}; + +/** + * @param {ServerRequest} req - The request object, containing information about the HTTP request. + * @param {ServerResponse} res - The response object, used to send back the desired HTTP response. + * @returns {Promise} A promise that resolves when the function has completed. + */ +const verifyToolAuth = async (req, res) => { + try { + const { toolId } = req.params; + const authFields = fieldsMap[toolId]; + if (!authFields) { + res.status(404).json({ message: 'Tool not found' }); + return; + } + let result; + try { + result = await loadAuthValues({ + userId: req.user.id, + authFields, + }); + } catch (error) { + res.status(200).json({ authenticated: false, message: AuthType.USER_PROVIDED }); + return; + } + let isUserProvided = false; + for (const field of authFields) { + if (!result[field]) { + res.status(200).json({ authenticated: false, message: AuthType.USER_PROVIDED }); + return; + } + if (!isUserProvided && process.env[field] !== result[field]) { + isUserProvided = true; + } + } + res.status(200).json({ + authenticated: true, + message: isUserProvided ? AuthType.USER_PROVIDED : AuthType.SYSTEM_DEFINED, + }); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + +module.exports = { + verifyToolAuth, +}; diff --git a/api/server/routes/agents/tools.js b/api/server/routes/agents/tools.js new file mode 100644 index 00000000000..b58fc21d4fd --- /dev/null +++ b/api/server/routes/agents/tools.js @@ -0,0 +1,22 @@ +const express = require('express'); +const { getAvailableTools } = require('~/server/controllers/PluginController'); +const { verifyToolAuth } = require('~/server/controllers/tools'); + +const router = express.Router(); + +/** + * Get a list of available tools for agents. + * @route GET /agents/tools + * @returns {TPlugin[]} 200 - application/json + */ +router.get('/', getAvailableTools); + +/** + * Verify authentication for a specific tool + * @route GET /agents/tools/:toolId/auth + * @param {string} toolId - The ID of the tool to verify + * @returns {{ authenticated?: boolean; message?: string }} + */ +router.get('/:toolId/auth', verifyToolAuth); + +module.exports = router; diff --git a/api/server/routes/agents/v1.js b/api/server/routes/agents/v1.js index 8e347bbe2a1..a4fcde12412 100644 --- a/api/server/routes/agents/v1.js +++ b/api/server/routes/agents/v1.js @@ -2,9 +2,9 @@ const multer = require('multer'); const express = require('express'); const { PermissionTypes, Permissions } = require('librechat-data-provider'); const { requireJwtAuth, generateCheckAccess } = require('~/server/middleware'); -const { getAvailableTools } = require('~/server/controllers/PluginController'); const v1 = require('~/server/controllers/agents/v1'); const actions = require('./actions'); +const tools = require('./tools'); const upload = multer(); const router = express.Router(); @@ -35,9 +35,8 @@ router.use('/actions', actions); /** * Get a list of available tools for agents. * @route GET /agents/tools - * @returns {TPlugin[]} 200 - application/json */ -router.use('/tools', getAvailableTools); +router.use('/tools', tools); /** * Creates an agent. diff --git a/api/server/routes/files/files.js b/api/server/routes/files/files.js index 6d4ab7fefb7..df2c05efe70 100644 --- a/api/server/routes/files/files.js +++ b/api/server/routes/files/files.js @@ -17,6 +17,7 @@ const { const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); const { loadAuthValues } = require('~/app/clients/tools/util'); +const { getAgent } = require('~/models/Agent'); const { getFiles } = require('~/models/File'); const { logger } = require('~/config'); @@ -67,12 +68,31 @@ router.delete('/', async (req, res) => { } const fileIds = files.map((file) => file.file_id); - const userFiles = await getFiles({ file_id: { $in: fileIds }, user: req.user.id }); - if (userFiles.length !== files.length) { - return res.status(403).json({ message: 'You can only delete your own files' }); + const dbFiles = await getFiles({ file_id: { $in: fileIds } }); + const unauthorizedFiles = dbFiles.filter((file) => file.user.toString() !== req.user.id); + + if (unauthorizedFiles.length > 0) { + return res.status(403).json({ + message: 'You can only delete your own files', + unauthorizedFiles: unauthorizedFiles.map((f) => f.file_id), + }); } - await processDeleteRequest({ req, files: userFiles }); + /* Handle entity unlinking even if no valid files to delete */ + if (req.body.agent_id && req.body.tool_resource && dbFiles.length === 0) { + const agent = await getAgent({ + id: req.body.agent_id, + }); + + const toolResourceFiles = agent.tool_resources?.[req.body.tool_resource]?.file_ids ?? []; + const agentFiles = files.filter((f) => toolResourceFiles.includes(f.file_id)); + + await processDeleteRequest({ req, files: agentFiles }); + res.status(200).json({ message: 'File associations removed successfully' }); + return; + } + + await processDeleteRequest({ req, files: dbFiles }); logger.debug( `[/files] Files deleted successfully: ${files @@ -87,13 +107,13 @@ router.delete('/', async (req, res) => { } }); -router.get('/code/download/:sessionId/:fileId', async (req, res) => { +router.get('/code/download/:session_id/:fileId', async (req, res) => { try { - const { sessionId, fileId } = req.params; - const logPrefix = `Session ID: ${sessionId} | File ID: ${fileId} | Code output download requested by user `; + const { session_id, fileId } = req.params; + const logPrefix = `Session ID: ${session_id} | File ID: ${fileId} | Code output download requested by user `; logger.debug(logPrefix); - if (!sessionId || !fileId) { + if (!session_id || !fileId) { return res.status(400).send('Bad request'); } @@ -108,7 +128,10 @@ router.get('/code/download/:sessionId/:fileId', async (req, res) => { const result = await loadAuthValues({ userId: req.user.id, authFields: [EnvVar.CODE_API_KEY] }); /** @type {AxiosResponse | undefined} */ - const response = await getDownloadStream(`${sessionId}/${fileId}`, result[EnvVar.CODE_API_KEY]); + const response = await getDownloadStream( + `${session_id}/${fileId}`, + result[EnvVar.CODE_API_KEY], + ); res.set(response.headers); response.data.pipe(res); } catch (error) { diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index b756b781ee7..190972ac51f 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -11,7 +11,7 @@ const { z } = require('zod'); const { tool } = require('@langchain/core/tools'); -const { createContentAggregator } = require('@librechat/agents'); +const { createContentAggregator, Providers } = require('@librechat/agents'); const { EModelEndpoint, getResponseSender, @@ -22,8 +22,9 @@ const { createToolEndCallback, } = require('~/server/controllers/agents/callbacks'); const initAnthropic = require('~/server/services/Endpoints/anthropic/initialize'); -const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options'); +const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); +const initCustom = require('~/server/services/Endpoints/custom/initialize'); const { loadAgentTools } = require('~/server/services/ToolService'); const AgentClient = require('~/server/controllers/agents/client'); const { getModelMaxTokens } = require('~/utils'); @@ -53,6 +54,7 @@ const providerConfigMap = { [EModelEndpoint.azureOpenAI]: initOpenAI, [EModelEndpoint.anthropic]: initAnthropic, [EModelEndpoint.bedrock]: getBedrockOptions, + [Providers.OLLAMA]: initCustom, }; const initializeClient = async ({ req, res, endpointOption }) => { @@ -92,7 +94,7 @@ const initializeClient = async ({ req, res, endpointOption }) => { }); let modelOptions = { model: agent.model }; - const getOptions = providerConfigMap[agent.provider]; + let getOptions = providerConfigMap[agent.provider]; if (!getOptions) { throw new Error(`Provider ${agent.provider} not supported`); } diff --git a/api/server/services/Endpoints/custom/initialize.js b/api/server/services/Endpoints/custom/initialize.js index dbc7a769fb9..a2f410cdf1d 100644 --- a/api/server/services/Endpoints/custom/initialize.js +++ b/api/server/services/Endpoints/custom/initialize.js @@ -12,11 +12,14 @@ const { fetchModels } = require('~/server/services/ModelService'); const getLogStores = require('~/cache/getLogStores'); const { isUserProvided } = require('~/server/utils'); const { OpenAIClient } = require('~/app'); +const { Providers } = require('@librechat/agents'); const { PROXY } = process.env; -const initializeClient = async ({ req, res, endpointOption }) => { - const { key: expiresAt, endpoint } = req.body; +const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => { + const { key: expiresAt } = req.body; + const endpoint = overrideEndpoint ?? req.body.endpoint; + const customConfig = await getCustomConfig(); if (!customConfig) { throw new Error(`Config not found for the ${endpoint} custom endpoint.`); @@ -133,6 +136,17 @@ const initializeClient = async ({ req, res, endpointOption }) => { ...endpointOption, }; + if (optionsOnly) { + const modelOptions = endpointOption.model_parameters; + if (endpoint === Providers.OLLAMA && clientOptions.reverseProxyUrl) { + modelOptions.baseUrl = clientOptions.reverseProxyUrl.split('/v1')[0]; + delete clientOptions.reverseProxyUrl; + } + return { + llmConfig: modelOptions, + }; + } + const client = new OpenAIClient(apiKey, clientOptions); return { client, diff --git a/api/server/services/Files/Code/crud.js b/api/server/services/Files/Code/crud.js index 10aa130d2ea..82b999b9bb1 100644 --- a/api/server/services/Files/Code/crud.js +++ b/api/server/services/Files/Code/crud.js @@ -1,19 +1,20 @@ -// downloadStream.js - +// Code Files const axios = require('axios'); +const FormData = require('form-data'); const { getCodeBaseURL } = require('@librechat/agents'); -const baseURL = getCodeBaseURL(); +const MAX_FILE_SIZE = 25 * 1024 * 1024; /** * Retrieves a download stream for a specified file. - * @param {string} fileIdentifier - The identifier for the file (e.g., "sessionId/fileId"). + * @param {string} fileIdentifier - The identifier for the file (e.g., "session_id/fileId"). * @param {string} apiKey - The API key for authentication. * @returns {Promise} A promise that resolves to a readable stream of the file content. * @throws {Error} If there's an error during the download process. */ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) { try { + const baseURL = getCodeBaseURL(); const response = await axios({ method: 'get', url: `${baseURL}/download/${fileIdentifier}`, @@ -31,4 +32,45 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) { } } -module.exports = { getCodeOutputDownloadStream }; +/** + * Uploads a file to the Code Environment server. + * @param {Object} params - The params object. + * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` + * representing the user, and an `app.locals.paths` object with an `uploads` path. + * @param {import('fs').ReadStream | import('stream').Readable} params.stream - The read stream for the file. + * @param {string} params.filename - The name of the file. + * @param {string} params.apiKey - The API key for authentication. + * @returns {Promise} + * @throws {Error} If there's an error during the upload process. + */ +async function uploadCodeEnvFile({ req, stream, filename, apiKey }) { + try { + const form = new FormData(); + form.append('file', stream, filename); + + const baseURL = getCodeBaseURL(); + const response = await axios.post(`${baseURL}/upload`, form, { + headers: { + ...form.getHeaders(), + 'Content-Type': 'multipart/form-data', + 'User-Agent': 'LibreChat/1.0', + 'User-Id': req.user.id, + 'X-API-Key': apiKey, + }, + maxContentLength: MAX_FILE_SIZE, + maxBodyLength: MAX_FILE_SIZE, + }); + + /** @type {{ message: string; session_id: string; files: Array<{ fileId: string; filename: string }> }} */ + const result = response.data; + if (result.message !== 'success') { + throw new Error(`Error uploading file: ${result.message}`); + } + + return `${result.session_id}/${result.files[0].fileId}`; + } catch (error) { + throw new Error(`Error uploading file: ${error.message}`); + } +} + +module.exports = { getCodeOutputDownloadStream, uploadCodeEnvFile }; diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js index d8e8119367e..313b98f39b8 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -1,11 +1,16 @@ const path = require('path'); const { v4 } = require('uuid'); const axios = require('axios'); -const { getCodeBaseURL, EnvVar } = require('@librechat/agents'); -const { FileContext, imageExtRegex } = require('librechat-data-provider'); +const { getCodeBaseURL } = require('@librechat/agents'); +const { + EToolResources, + FileContext, + imageExtRegex, + FileSources, +} = require('librechat-data-provider'); +const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { convertImage } = require('~/server/services/Files/images/convert'); -const { loadAuthValues } = require('~/app/clients/tools/util'); -const { createFile } = require('~/models/File'); +const { createFile, getFiles, updateFile } = require('~/models/File'); const { logger } = require('~/config'); /** @@ -13,8 +18,9 @@ const { logger } = require('~/config'); * @param {ServerRequest} params.req - The Express request object. * @param {string} params.id - The file ID. * @param {string} params.name - The filename. + * @param {string} params.apiKey - The code execution API key. * @param {string} params.toolCallId - The tool call ID that generated the file. - * @param {string} params.sessionId - The code execution session ID. + * @param {string} params.session_id - The code execution session ID. * @param {string} params.conversationId - The current conversation ID. * @param {string} params.messageId - The current message ID. * @returns {Promise} The file metadata or undefined if an error occurs. @@ -23,10 +29,11 @@ const processCodeOutput = async ({ req, id, name, + apiKey, toolCallId, conversationId, messageId, - sessionId, + session_id, }) => { const currentDate = new Date(); const baseURL = getCodeBaseURL(); @@ -34,7 +41,7 @@ const processCodeOutput = async ({ if (!fileExt || !imageExtRegex.test(name)) { return { filename: name, - filepath: `/api/files/code/download/${sessionId}/${id}`, + filepath: `/api/files/code/download/${session_id}/${id}`, /** Note: expires 24 hours after creation */ expiresAt: currentDate.getTime() + 86400000, conversationId, @@ -45,14 +52,13 @@ const processCodeOutput = async ({ try { const formattedDate = currentDate.toISOString(); - const result = await loadAuthValues({ userId: req.user.id, authFields: [EnvVar.CODE_API_KEY] }); const response = await axios({ method: 'get', - url: `${baseURL}/download/${sessionId}/${id}`, + url: `${baseURL}/download/${session_id}/${id}`, responseType: 'arraybuffer', headers: { 'User-Agent': 'LibreChat/1.0', - 'X-API-Key': result[EnvVar.CODE_API_KEY], + 'X-API-Key': apiKey, }, timeout: 15000, }); @@ -82,6 +88,120 @@ const processCodeOutput = async ({ } }; +function checkIfActive(dateString) { + const givenDate = new Date(dateString); + const currentDate = new Date(); + const timeDifference = currentDate - givenDate; + const hoursPassed = timeDifference / (1000 * 60 * 60); + return hoursPassed < 23; +} + +/** + * Retrieves the `lastModified` time string for a specified file from Code Execution Server. + * + * @param {Object} params - The parameters object. + * @param {string} params.fileIdentifier - The identifier for the file (e.g., "session_id/fileId"). + * @param {string} params.apiKey - The API key for authentication. + * + * @returns {Promise} + * A promise that resolves to the `lastModified` time string of the file if successful, or null if there is an + * error in initialization or fetching the info. + */ +async function getSessionInfo(fileIdentifier, apiKey) { + try { + const baseURL = getCodeBaseURL(); + const session_id = fileIdentifier.split('/')[0]; + const response = await axios({ + method: 'get', + url: `${baseURL}/files/${session_id}`, + params: { + detail: 'summary', + }, + headers: { + 'User-Agent': 'LibreChat/1.0', + 'X-API-Key': apiKey, + }, + timeout: 5000, + }); + + return response.data.find((file) => file.name.startsWith(fileIdentifier))?.lastModified; + } catch (error) { + logger.error(`Error fetching session info: ${error.message}`, error); + return null; + } +} + +/** + * + * @param {Object} options + * @param {ServerRequest} options.req + * @param {Agent['tool_resources']} options.tool_resources + * @param {string} apiKey + * @returns {Promise>} + */ +const primeFiles = async (options, apiKey) => { + const { tool_resources } = options; + const file_ids = tool_resources?.[EToolResources.execute_code]?.file_ids ?? []; + const dbFiles = await getFiles({ file_id: { $in: file_ids } }); + + const files = []; + const sessions = new Map(); + for (const file of dbFiles) { + if (file.metadata.fileIdentifier) { + const [session_id, id] = file.metadata.fileIdentifier.split('/'); + const pushFile = () => { + files.push({ + id, + session_id, + name: file.filename, + }); + }; + if (sessions.has(session_id)) { + pushFile(); + continue; + } + const reuploadFile = async () => { + try { + const { getDownloadStream } = getStrategyFunctions(file.source); + const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions( + FileSources.execute_code, + ); + const stream = await getDownloadStream(file.filepath); + const fileIdentifier = await uploadCodeEnvFile({ + req: options.req, + stream, + filename: file.filename, + apiKey, + }); + await updateFile({ file_id: file.file_id, metadata: { fileIdentifier } }); + sessions.set(session_id, true); + pushFile(); + } catch (error) { + logger.error( + `Error re-uploading file ${id} in session ${session_id}: ${error.message}`, + error, + ); + } + }; + const uploadTime = await getSessionInfo(file.metadata.fileIdentifier, apiKey); + if (!uploadTime) { + logger.warn(`Failed to get upload time for file ${id} in session ${session_id}`); + await reuploadFile(); + continue; + } + if (!checkIfActive(uploadTime)) { + await reuploadFile(); + continue; + } + sessions.set(session_id, true); + pushFile(); + } + } + + return files; +}; + module.exports = { + primeFiles, processCodeOutput, }; diff --git a/api/server/services/Files/Firebase/crud.js b/api/server/services/Files/Firebase/crud.js index c4d1d05bf6b..76a6c1d8d40 100644 --- a/api/server/services/Files/Firebase/crud.js +++ b/api/server/services/Files/Firebase/crud.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const axios = require('axios'); const fetch = require('node-fetch'); -const { ref, uploadBytes, getDownloadURL, getStream, deleteObject } = require('firebase/storage'); +const { ref, uploadBytes, getDownloadURL, deleteObject } = require('firebase/storage'); const { getBufferMetadata } = require('~/server/utils'); const { getFirebaseStorage } = require('./initialize'); const { logger } = require('~/config'); @@ -155,7 +155,7 @@ function extractFirebaseFilePath(urlString) { * Deletes a file from Firebase storage. This function determines the filepath from the * Firebase storage URL via regex for deletion. Validated by the user's ID. * - * @param {Express.Request} req - The request object from Express. + * @param {ServerRequest} req - The request object from Express. * It should contain a `user` object with an `id` property. * @param {MongoFile} file - The file object to be deleted. * @@ -195,7 +195,7 @@ const deleteFirebaseFile = async (req, file) => { * Uploads a file to Firebase Storage. * * @param {Object} params - The params object. - * @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` + * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * representing the user. * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * have a `path` property that points to the location of the uploaded file. @@ -225,16 +225,22 @@ async function uploadFileToFirebase({ req, file, file_id }) { * Retrieves a readable stream for a file from Firebase storage. * * @param {string} filepath - The filepath. - * @returns {ReadableStream} A readable stream of the file. + * @returns {Promise} A readable stream of the file. */ -function getFirebaseFileStream(filepath) { +async function getFirebaseFileStream(filepath) { try { const storage = getFirebaseStorage(); if (!storage) { throw new Error('Firebase is not initialized'); } - const fileRef = ref(storage, filepath); - return getStream(fileRef); + + const response = await axios({ + method: 'get', + url: filepath, + responseType: 'stream', + }); + + return response.data; } catch (error) { logger.error('Error getting Firebase file stream:', error); throw error; diff --git a/api/server/services/Files/Local/crud.js b/api/server/services/Files/Local/crud.js index 12b7738828f..e004eab79ef 100644 --- a/api/server/services/Files/Local/crud.js +++ b/api/server/services/Files/Local/crud.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const axios = require('axios'); +const { EModelEndpoint } = require('librechat-data-provider'); const { getBufferMetadata } = require('~/server/utils'); const paths = require('~/config/paths'); const { logger } = require('~/config'); @@ -222,6 +223,10 @@ const deleteLocalFile = async (req, file) => { const parts = file.filepath.split(path.sep); const subfolder = parts[1]; + if (!subfolder && parts[0] === EModelEndpoint.agents) { + logger.warn(`Agent File ${file.file_id} is missing filepath, may have been deleted already`); + return; + } const filepath = path.join(publicPath, file.filepath); if (!isValidPath(req, publicPath, subfolder, filepath)) { @@ -235,7 +240,7 @@ const deleteLocalFile = async (req, file) => { * Uploads a file to the specified upload directory. * * @param {Object} params - The params object. - * @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` + * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * representing the user, and an `app.locals.paths` object with an `uploads` path. * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * have a `path` property that points to the location of the uploaded file. diff --git a/api/server/services/Files/OpenAI/crud.js b/api/server/services/Files/OpenAI/crud.js index 881b2063b4a..64478ce7b3a 100644 --- a/api/server/services/Files/OpenAI/crud.js +++ b/api/server/services/Files/OpenAI/crud.js @@ -7,7 +7,7 @@ const { logger } = require('~/config'); * Uploads a file that can be used across various OpenAI services. * * @param {Object} params - The params object. - * @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` + * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * representing the user, and an `app.locals.paths` object with an `imageOutput` path. * @param {Express.Multer.File} params.file - The file uploaded to the server via multer. * @param {OpenAIClient} params.openai - The initialized OpenAI client. @@ -42,7 +42,7 @@ async function uploadOpenAIFile({ req, file, openai }) { /** * Deletes a file previously uploaded to OpenAI. * - * @param {Express.Request} req - The request object from Express. + * @param {ServerRequest} req - The request object from Express. * @param {MongoFile} file - The database representation of the uploaded file. * @param {OpenAI} openai - The initialized OpenAI client. * @returns {Promise} diff --git a/api/server/services/Files/VectorDB/crud.js b/api/server/services/Files/VectorDB/crud.js index c9a8c315834..3d16c93e150 100644 --- a/api/server/services/Files/VectorDB/crud.js +++ b/api/server/services/Files/VectorDB/crud.js @@ -8,7 +8,7 @@ const { logger } = require('~/config'); * Deletes a file from the vector database. This function takes a file object, constructs the full path, and * verifies the path's validity before deleting the file. If the path is invalid, an error is thrown. * - * @param {Express.Request} req - The request object from Express. It should have an `app.locals.paths` object with + * @param {ServerRequest} req - The request object from Express. It should have an `app.locals.paths` object with * a `publicPath` property. * @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is * a string representing the path of the file relative to the publicPath. diff --git a/api/server/services/Files/process.js b/api/server/services/Files/process.js index 00d98bbc20b..d1cbc13ed10 100644 --- a/api/server/services/Files/process.js +++ b/api/server/services/Files/process.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const path = require('path'); const mime = require('mime'); const { v4 } = require('uuid'); @@ -12,14 +13,17 @@ const { mergeFileConfig, hostImageIdSuffix, checkOpenAIStorage, + removeNullishValues, hostImageNamePrefix, isAssistantsEndpoint, } = require('librechat-data-provider'); +const { EnvVar } = require('@librechat/agents'); const { addResourceFileId, deleteResourceFileId } = require('~/server/controllers/assistants/v2'); const { convertImage, resizeAndConvert } = require('~/server/services/Files/images'); const { addAgentResourceFile, removeAgentResourceFile } = require('~/models/Agent'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); const { createFile, updateFileUsage, deleteFiles } = require('~/models/File'); +const { loadAuthValues } = require('~/app/clients/tools/util'); const { LB_QueueAsyncCall } = require('~/server/utils/queue'); const { getStrategyFunctions } = require('./strategies'); const { determineFileType } = require('~/server/utils'); @@ -89,6 +93,7 @@ function enqueueDeleteOperation({ req, file, deleteFile, promises, resolvedFileI * @param {MongoFile[]} params.files - The file objects to delete. * @param {Express.Request} params.req - The express request object. * @param {DeleteFilesBody} params.req.body - The request body. + * @param {string} [params.req.body.agent_id] - The agent ID if file uploaded is associated to an agent. * @param {string} [params.req.body.assistant_id] - The assistant ID if file uploaded is associated to an assistant. * @param {string} [params.req.body.tool_resource] - The tool resource if assistant file uploaded is associated to a tool resource. * @@ -438,10 +443,25 @@ const processAgentFileUpload = async ({ req, res, file, metadata }) => { throw new Error('No agent ID provided for agent file upload'); } + let fileInfoMetadata; + if (tool_resource === EToolResources.execute_code) { + const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions(FileSources.execute_code); + const result = await loadAuthValues({ userId: req.user.id, authFields: [EnvVar.CODE_API_KEY] }); + const stream = fs.createReadStream(file.path); + const fileIdentifier = await uploadCodeEnvFile({ + req, + stream, + filename: file.originalname, + apiKey: result[EnvVar.CODE_API_KEY], + }); + fileInfoMetadata = { fileIdentifier }; + } + const source = tool_resource === EToolResources.file_search ? FileSources.vectordb : req.app.locals.fileStrategy; + const { handleFileUpload } = getStrategyFunctions(source); const { file_id, temp_file_id } = metadata; @@ -463,9 +483,9 @@ const processAgentFileUpload = async ({ req, res, file, metadata }) => { if (!messageAttachment && tool_resource) { await addAgentResourceFile({ req, - agent_id, file_id, - tool_resource: tool_resource, + agent_id, + tool_resource, }); } @@ -479,24 +499,24 @@ const processAgentFileUpload = async ({ req, res, file, metadata }) => { filepath = result.filepath; } - const result = await createFile( - { - user: req.user.id, - file_id, - temp_file_id, - bytes, - filepath, - filename: filename ?? file.originalname, - context: messageAttachment ? FileContext.message_attachment : FileContext.agents, - model: messageAttachment ? undefined : req.body.model, - type: file.mimetype, - embedded, - source, - height, - width, - }, - true, - ); + const fileInfo = removeNullishValues({ + user: req.user.id, + file_id, + temp_file_id, + bytes, + filepath, + filename: filename ?? file.originalname, + context: messageAttachment ? FileContext.message_attachment : FileContext.agents, + model: messageAttachment ? undefined : req.body.model, + metadata: fileInfoMetadata, + type: file.mimetype, + embedded, + source, + height, + width, + }); + + const result = await createFile(fileInfo, true); res.status(200).json({ message: 'Agent file uploaded and processed successfully', ...result }); }; diff --git a/api/server/services/Files/strategies.js b/api/server/services/Files/strategies.js index cfb837a50ed..ddfdd574690 100644 --- a/api/server/services/Files/strategies.js +++ b/api/server/services/Files/strategies.js @@ -5,11 +5,13 @@ const { saveURLToFirebase, deleteFirebaseFile, saveBufferToFirebase, + uploadFileToFirebase, uploadImageToFirebase, processFirebaseAvatar, getFirebaseFileStream, } = require('./Firebase'); const { + uploadLocalFile, getLocalFileURL, saveFileFromURL, saveLocalBuffer, @@ -20,17 +22,15 @@ const { getLocalFileStream, } = require('./Local'); const { uploadOpenAIFile, deleteOpenAIFile, getOpenAIFileStream } = require('./OpenAI'); +const { getCodeOutputDownloadStream, uploadCodeEnvFile } = require('./Code'); const { uploadVectors, deleteVectors } = require('./VectorDB'); -const { getCodeOutputDownloadStream } = require('./Code'); /** * Firebase Storage Strategy Functions * * */ const firebaseStrategy = () => ({ - // saveFile: - /** @type {typeof uploadVectors | null} */ - handleFileUpload: null, + handleFileUpload: uploadFileToFirebase, saveURL: saveURLToFirebase, getFileURL: getFirebaseURL, deleteFile: deleteFirebaseFile, @@ -46,8 +46,7 @@ const firebaseStrategy = () => ({ * * */ const localStrategy = () => ({ - /** @type {typeof uploadVectors | null} */ - handleFileUpload: null, + handleFileUpload: uploadLocalFile, saveURL: saveFileFromURL, getFileURL: getLocalFileURL, saveBuffer: saveLocalBuffer, @@ -124,8 +123,7 @@ const codeOutputStrategy = () => ({ prepareImagePayload: null, /** @type {typeof deleteLocalFile | null} */ deleteFile: null, - /** @type {typeof uploadVectors | null} */ - handleFileUpload: null, + handleFileUpload: uploadCodeEnvFile, getDownloadStream: getCodeOutputDownloadStream, }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 362b2fc6701..11a29e31f24 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -77,13 +77,27 @@ const anthropicModels = { }; const metaModels = { - 'llama2-13b': 4000, - 'llama2-70b': 4000, - 'llama3-8b': 8000, - 'llama3-70b': 8000, - 'llama3-1-8b': 127500, - 'llama3-1-70b': 127500, + llama3: 8000, + llama2: 4000, + 'llama3.1': 127500, + 'llama3-1': 127500, + 'llama3.1:405b': 127500, + 'llama3.1:70b': 127500, + 'llama3.1:8b': 127500, 'llama3-1-405b': 127500, + 'llama3-1-70b': 127500, + 'llama3-1-8b': 127500, + 'llama3-70b': 8000, + 'llama3-8b': 8000, + 'llama2-70b': 4000, + 'llama2-13b': 4000, + 'llama3:70b': 8000, + 'llama3:8b': 8000, + 'llama2:70b': 4000, +}; + +const ollamaModels = { + 'qwen2.5': 32000, }; const ai21Models = { @@ -102,6 +116,7 @@ const bedrockModels = { ...anthropicModels, ...mistralModels, ...cohereModels, + ...ollamaModels, ...metaModels, ...ai21Models, ...amazonModels, diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index e76e01a5684..4912d3c80c0 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -1,5 +1,5 @@ const { EModelEndpoint } = require('librechat-data-provider'); -const { getModelMaxTokens, matchModelName, maxTokensMap } = require('./tokens'); +const { getModelMaxTokens, processModelData, matchModelName, maxTokensMap } = require('./tokens'); describe('getModelMaxTokens', () => { test('should return correct tokens for exact match', () => { @@ -317,3 +317,108 @@ describe('matchModelName', () => { expect(matchModelName('chat-', EModelEndpoint.google)).toBe('chat-'); }); }); + +describe('Meta Models Tests', () => { + describe('getModelMaxTokens', () => { + test('should return correct tokens for LLaMa 2 models', () => { + expect(getModelMaxTokens('llama2')).toBe(4000); + expect(getModelMaxTokens('llama2.70b')).toBe(4000); + expect(getModelMaxTokens('llama2-13b')).toBe(4000); + expect(getModelMaxTokens('llama2-70b')).toBe(4000); + }); + + test('should return correct tokens for LLaMa 3 models', () => { + expect(getModelMaxTokens('llama3')).toBe(8000); + expect(getModelMaxTokens('llama3.8b')).toBe(8000); + expect(getModelMaxTokens('llama3.70b')).toBe(8000); + expect(getModelMaxTokens('llama3-8b')).toBe(8000); + expect(getModelMaxTokens('llama3-70b')).toBe(8000); + }); + + test('should return correct tokens for LLaMa 3.1 models', () => { + expect(getModelMaxTokens('llama3.1:8b')).toBe(127500); + expect(getModelMaxTokens('llama3.1:70b')).toBe(127500); + expect(getModelMaxTokens('llama3.1:405b')).toBe(127500); + expect(getModelMaxTokens('llama3-1-8b')).toBe(127500); + expect(getModelMaxTokens('llama3-1-70b')).toBe(127500); + expect(getModelMaxTokens('llama3-1-405b')).toBe(127500); + }); + + test('should handle partial matches for Meta models', () => { + // Test with full model names + expect(getModelMaxTokens('meta/llama3.1:405b')).toBe(127500); + expect(getModelMaxTokens('meta/llama3.1:70b')).toBe(127500); + expect(getModelMaxTokens('meta/llama3.1:8b')).toBe(127500); + expect(getModelMaxTokens('meta/llama3-1-8b')).toBe(127500); + + // Test base versions + expect(getModelMaxTokens('meta/llama3.1')).toBe(127500); + expect(getModelMaxTokens('meta/llama3-1')).toBe(127500); + expect(getModelMaxTokens('meta/llama3')).toBe(8000); + expect(getModelMaxTokens('meta/llama2')).toBe(4000); + }); + }); + + describe('matchModelName', () => { + test('should match exact LLaMa model names', () => { + expect(matchModelName('llama2')).toBe('llama2'); + expect(matchModelName('llama3')).toBe('llama3'); + expect(matchModelName('llama3.1:8b')).toBe('llama3.1:8b'); + }); + + test('should match LLaMa model variations', () => { + // Test full model names + expect(matchModelName('meta/llama3.1:405b')).toBe('llama3.1:405b'); + expect(matchModelName('meta/llama3.1:70b')).toBe('llama3.1:70b'); + expect(matchModelName('meta/llama3.1:8b')).toBe('llama3.1:8b'); + expect(matchModelName('meta/llama3-1-8b')).toBe('llama3-1-8b'); + + // Test base versions + expect(matchModelName('meta/llama3.1')).toBe('llama3.1'); + expect(matchModelName('meta/llama3-1')).toBe('llama3-1'); + }); + + test('should handle custom endpoint for Meta models', () => { + expect(matchModelName('llama2', EModelEndpoint.bedrock)).toBe('llama2'); + expect(matchModelName('llama3', EModelEndpoint.bedrock)).toBe('llama3'); + expect(matchModelName('llama3.1:8b', EModelEndpoint.bedrock)).toBe('llama3.1:8b'); + }); + }); + + describe('processModelData with Meta models', () => { + test('should process Meta model data correctly', () => { + const input = { + data: [ + { + id: 'llama2', + pricing: { + prompt: '0.00001', + completion: '0.00003', + }, + context_length: 4000, + }, + { + id: 'llama3', + pricing: { + prompt: '0.00002', + completion: '0.00004', + }, + context_length: 8000, + }, + ], + }; + + const result = processModelData(input); + expect(result.llama2).toEqual({ + prompt: 10, + completion: 30, + context: 4000, + }); + expect(result.llama3).toEqual({ + prompt: 20, + completion: 40, + context: 8000, + }); + }); + }); +}); diff --git a/client/src/components/Bookmarks/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm.tsx index 85018d3cfb2..df89e8bbb55 100644 --- a/client/src/components/Bookmarks/BookmarkForm.tsx +++ b/client/src/components/Bookmarks/BookmarkForm.tsx @@ -7,12 +7,12 @@ import type { TConversationTag, TConversationTagRequest, } from 'librechat-data-provider'; -import { cn, removeFocusOutlines, defaultTextProps, logger } from '~/utils'; import { Checkbox, Label, TextareaAutosize, Input } from '~/components'; import { useBookmarkContext } from '~/Providers/BookmarkContext'; import { useConversationTagMutation } from '~/data-provider'; import { useToastContext } from '~/Providers'; import { useLocalize } from '~/hooks'; +import { cn, logger } from '~/utils'; type TBookmarkFormProps = { tags?: string[]; diff --git a/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx b/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx index 775bebc5d8f..21574abbefa 100644 --- a/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx +++ b/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx @@ -47,26 +47,28 @@ const getKnownClass = ({ export default function UnknownIcon({ className = '', - endpoint, - iconURL, + endpoint: _endpoint, + iconURL = '', context, }: { iconURL?: string; className?: string; - endpoint: EModelEndpoint | string | null; + endpoint?: EModelEndpoint | string | null; context?: 'landing' | 'menu-item' | 'nav' | 'message'; }) { + const endpoint = _endpoint ?? ''; if (!endpoint) { return ; } + console.log('UnknownIcon', endpoint); const currentEndpoint = endpoint.toLowerCase(); if (iconURL) { return {`${endpoint}; } - const assetPath = knownEndpointAssets[currentEndpoint]; + const assetPath: string = knownEndpointAssets[currentEndpoint] ?? ''; if (!assetPath) { return ; diff --git a/client/src/components/Plugins/Store/PluginAuthForm.tsx b/client/src/components/Plugins/Store/PluginAuthForm.tsx index aa8ce212831..01894504cfa 100644 --- a/client/src/components/Plugins/Store/PluginAuthForm.tsx +++ b/client/src/components/Plugins/Store/PluginAuthForm.tsx @@ -1,22 +1,26 @@ -import { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider'; import { Save } from 'lucide-react'; import { useForm } from 'react-hook-form'; +import { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider'; import { HoverCard, HoverCardTrigger } from '~/components/ui'; import PluginTooltip from './PluginTooltip'; +import { useLocalize } from '~/hooks'; type TPluginAuthFormProps = { plugin: TPlugin | undefined; onSubmit: (installActionData: TPluginAction) => void; - isAssistantTool?: boolean; + isEntityTool?: boolean; }; -function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormProps) { +function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps) { const { register, handleSubmit, formState: { errors, isDirty, isValid, isSubmitting }, } = useForm(); + const localize = useLocalize(); + const authConfig = plugin?.authConfig ?? []; + return (
@@ -28,11 +32,11 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr pluginKey: plugin?.pluginKey ?? '', action: 'install', auth, - isAssistantTool, + isEntityTool, }), )} > - {plugin?.authConfig?.map((config: TPluginAuthConfig, i: number) => { + {authConfig.map((config: TPluginAuthConfig, i: number) => { const authField = config.authField.split('||')[0]; return (
@@ -66,8 +70,7 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr {errors[authField] && ( - {/* @ts-ignore - Type 'string | FieldError | Merge> | undefined' is not assignable to type 'ReactNode' */} - {errors[authField].message} + {errors[authField].message as string} )}
@@ -79,7 +82,7 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr className="btn btn-primary relative" >
- Save + {localize('com_ui_save')}
diff --git a/client/src/components/SidePanel/Agents/AgentConfig.tsx b/client/src/components/SidePanel/Agents/AgentConfig.tsx index aca975d9b4d..fb14d7e5f3e 100644 --- a/client/src/components/SidePanel/Agents/AgentConfig.tsx +++ b/client/src/components/SidePanel/Agents/AgentConfig.tsx @@ -11,7 +11,6 @@ import { icons } from '~/components/Chat/Menus/Endpoints/Icons'; import Action from '~/components/SidePanel/Builder/Action'; import { ToolSelectDialog } from '~/components/Tools'; import { useLocalize, useAuthContext } from '~/hooks'; -import CapabilitiesForm from './CapabilitiesForm'; import { processAgentOption } from '~/utils'; import { Spinner } from '~/components/svg'; import DeleteButton from './DeleteButton'; @@ -19,6 +18,7 @@ import AgentAvatar from './AgentAvatar'; import FileSearch from './FileSearch'; import ShareAgent from './ShareAgent'; import AgentTool from './AgentTool'; +// import CodeForm from './Code/Form'; import { Panel } from '~/common'; const labelClass = 'mb-2 text-token-text-primary block font-medium'; @@ -92,6 +92,26 @@ export default function AgentConfig({ return _agent.knowledge_files ?? []; }, [agent, agent_id, fileMap]); + const code_files = useMemo(() => { + if (typeof agent === 'string') { + return []; + } + + if (agent?.id !== agent_id) { + return []; + } + + if (agent.code_files) { + return agent.code_files; + } + + const _agent = processAgentOption({ + agent, + fileMap, + }); + return _agent.code_files ?? []; + }, [agent, agent_id, fileMap]); + /* Mutations */ const update = useUpdateAgentMutation({ onSuccess: (data) => { @@ -293,7 +313,7 @@ export default function AgentConfig({
@@ -303,11 +323,8 @@ export default function AgentConfig({
- + {/* Code Execution */} + {/* {codeEnabled && } */} {/* File Search */} {fileSearchEnabled && } {/* Agent Tools & Actions */} diff --git a/client/src/components/SidePanel/Agents/AgentTool.tsx b/client/src/components/SidePanel/Agents/AgentTool.tsx index ed5eed0c073..59c2e267f1a 100644 --- a/client/src/components/SidePanel/Agents/AgentTool.tsx +++ b/client/src/components/SidePanel/Agents/AgentTool.tsx @@ -12,7 +12,7 @@ import { cn } from '~/utils'; export default function AgentTool({ tool, allTools, - agent_id, + agent_id = '', }: { tool: string; allTools: TPlugin[]; @@ -28,7 +28,7 @@ export default function AgentTool({ const removeTool = (tool: string) => { if (tool) { updateUserPlugins.mutate( - { pluginKey: tool, action: 'uninstall', auth: null, isAgentTool: true }, + { pluginKey: tool, action: 'uninstall', auth: null, isEntityTool: true }, { onError: (error: unknown) => { showToast({ message: `Error while deleting the tool: ${error}`, status: 'error' }); diff --git a/client/src/components/SidePanel/Agents/CapabilitiesForm.tsx b/client/src/components/SidePanel/Agents/CapabilitiesForm.tsx deleted file mode 100644 index 064ab26646b..00000000000 --- a/client/src/components/SidePanel/Agents/CapabilitiesForm.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useMemo } from 'react'; -// import { Capabilities } from 'librechat-data-provider'; -// import { useFormContext, useWatch } from 'react-hook-form'; -import type { TConfig } from 'librechat-data-provider'; -// import type { AgentForm } from '~/common'; -// import ImageVision from './ImageVision'; -import { useLocalize } from '~/hooks'; -import Retrieval from './Retrieval'; -// import CodeFiles from './CodeFiles'; -import Code from './Code'; - -export default function CapabilitiesForm({ - codeEnabled, - retrievalEnabled, - agentsConfig, -}: { - codeEnabled?: boolean; - retrievalEnabled?: boolean; - agentsConfig?: TConfig | null; -}) { - const localize = useLocalize(); - - // const methods = useFormContext(); - // const { control } = methods; - // const agent = useWatch({ control, name: 'agent' }); - // const agent_id = useWatch({ control, name: 'id' }); - // const files = useMemo(() => { - // if (typeof agent === 'string') { - // return []; - // } - // return agent?.code_files; - // }, [agent]); - - const retrievalModels = useMemo( - () => new Set(agentsConfig?.retrievalModels ?? []), - [agentsConfig], - ); - - return ( -
-
- - - -
-
- {codeEnabled === true && } - {retrievalEnabled === true && } - {/* {imageVisionEnabled && version == 1 && } */} - {/* {codeEnabled && } */} -
-
- ); -} diff --git a/client/src/components/SidePanel/Agents/Code.tsx b/client/src/components/SidePanel/Agents/Code.tsx deleted file mode 100644 index bb3d3834298..00000000000 --- a/client/src/components/SidePanel/Agents/Code.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AgentCapabilities } from 'librechat-data-provider'; -import { useFormContext, Controller } from 'react-hook-form'; -import type { AgentForm } from '~/common'; -import { - Checkbox, - HoverCard, - HoverCardContent, - HoverCardPortal, - HoverCardTrigger, -} from '~/components/ui'; -import { CircleHelpIcon } from '~/components/svg'; -import { useLocalize } from '~/hooks'; -import { ESide } from '~/common'; - -export default function Code() { - const localize = useLocalize(); - const methods = useFormContext(); - const { control, setValue, getValues } = methods; - - return ( - <> - -
- ( - - )} - /> - - - -
-

- {/* // TODO: add a Code Interpreter description */} -

-
-
-
-
-
- - ); -} diff --git a/client/src/components/SidePanel/Agents/Code/Action.tsx b/client/src/components/SidePanel/Agents/Code/Action.tsx new file mode 100644 index 00000000000..4a1af816d5d --- /dev/null +++ b/client/src/components/SidePanel/Agents/Code/Action.tsx @@ -0,0 +1,151 @@ +import { useState } from 'react'; +import { KeyRoundIcon } from 'lucide-react'; +import { AuthType, AgentCapabilities } from 'librechat-data-provider'; +import { useFormContext, Controller, useForm, useWatch } from 'react-hook-form'; +import type { AgentForm } from '~/common'; +import { + Input, + OGDialog, + Checkbox, + HoverCard, + HoverCardContent, + HoverCardPortal, + HoverCardTrigger, + Button, +} from '~/components/ui'; +import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; +import { useLocalize, useAuthCodeTool } from '~/hooks'; +import { CircleHelpIcon } from '~/components/svg'; +import { ESide } from '~/common'; + +type ApiKeyFormData = { + apiKey: string; + authType?: string | AuthType; +}; + +export default function Action({ authType = '', isToolAuthenticated = false }) { + const localize = useLocalize(); + const methods = useFormContext(); + const { control, setValue, getValues } = methods; + const [isDialogOpen, setIsDialogOpen] = useState(false); + const runCodeIsEnabled = useWatch({ control, name: AgentCapabilities.execute_code }); + + const { installTool, removeTool } = useAuthCodeTool({ isEntityTool: true }); + + const { reset, register, handleSubmit } = useForm(); + const isUserProvided = authType === AuthType.USER_PROVIDED; + + const handleCheckboxChange = (checked: boolean) => { + if (isToolAuthenticated) { + setValue(AgentCapabilities.execute_code, checked, { shouldDirty: true }); + } else if (runCodeIsEnabled) { + setValue(AgentCapabilities.execute_code, false, { shouldDirty: true }); + } else { + setIsDialogOpen(true); + } + }; + + const onSubmit = (data: { apiKey: string }) => { + reset(); + installTool(data.apiKey); + setIsDialogOpen(false); + }; + + const handleRevokeApiKey = () => { + reset(); + removeTool(); + setIsDialogOpen(false); + }; + + return ( + <> + +
+ ( + + )} + /> + +
+ {isUserProvided && (isToolAuthenticated || runCodeIsEnabled) && ( + + )} + + + +
+ + +
+

+ {/* // TODO: add a Code Interpreter description */} +

+
+
+
+
+
+ + + (e.target.readOnly = false)} + {...register('apiKey', { required: true })} + /> + + } + selection={{ + selectHandler: handleSubmit(onSubmit), + selectClasses: 'bg-green-500 hover:bg-green-600 text-white', + selectText: localize('com_ui_save'), + }} + buttons={ + isUserProvided && + isToolAuthenticated && ( + + ) + } + showCancelButton={true} + /> + + + ); +} diff --git a/client/src/components/SidePanel/Agents/CodeFiles.tsx b/client/src/components/SidePanel/Agents/Code/Files.tsx similarity index 76% rename from client/src/components/SidePanel/Agents/CodeFiles.tsx rename to client/src/components/SidePanel/Agents/Code/Files.tsx index e5f21017b14..2e92671571f 100644 --- a/client/src/components/SidePanel/Agents/CodeFiles.tsx +++ b/client/src/components/SidePanel/Agents/Code/Files.tsx @@ -1,21 +1,22 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef } from 'react'; +import { useFormContext } from 'react-hook-form'; import { EToolResources, EModelEndpoint, mergeFileConfig, + AgentCapabilities, fileConfig as defaultFileConfig, } from 'librechat-data-provider'; import type { EndpointFileConfig } from 'librechat-data-provider'; -import type { ExtendedFile } from '~/common'; +import type { ExtendedFile, AgentForm } from '~/common'; +import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks'; import FileRow from '~/components/Chat/Input/Files/FileRow'; import { useGetFileConfig } from '~/data-provider'; -import { useFileHandling } from '~/hooks/Files'; -import useLocalize from '~/hooks/useLocalize'; import { useChatContext } from '~/Providers'; -const tool_resource = EToolResources.code_interpreter; +const tool_resource = EToolResources.execute_code; -export default function CodeFiles({ +export default function Files({ agent_id, files: _files, }: { @@ -24,22 +25,29 @@ export default function CodeFiles({ }) { const localize = useLocalize(); const { setFilesLoading } = useChatContext(); + const { watch } = useFormContext(); const fileInputRef = useRef(null); const [files, setFiles] = useState>(new Map()); const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ select: (data) => mergeFileConfig(data), }); - const { handleFileChange } = useFileHandling({ + const { abortUpload, handleFileChange } = useFileHandling({ + fileSetter: setFiles, overrideEndpoint: EModelEndpoint.agents, additionalMetadata: { agent_id, tool_resource }, - fileSetter: setFiles, }); - useEffect(() => { - if (_files) { - setFiles(new Map(_files)); - } - }, [_files]); + useLazyEffect( + () => { + if (_files) { + setFiles(new Map(_files)); + } + }, + [_files], + 750, + ); + + const codeChecked = watch(AgentCapabilities.execute_code); const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents] as | EndpointFileConfig @@ -68,6 +76,7 @@ export default function CodeFiles({ files={files} setFiles={setFiles} agent_id={agent_id} + abortUpload={abortUpload} tool_resource={tool_resource} setFilesLoading={setFilesLoading} Wrapper={({ children }) =>
{children}
} @@ -75,7 +84,7 @@ export default function CodeFiles({
diff --git a/client/src/components/Tools/ToolSelectDialog.tsx b/client/src/components/Tools/ToolSelectDialog.tsx index 895f8aff47f..a2a1dc79cb3 100644 --- a/client/src/components/Tools/ToolSelectDialog.tsx +++ b/client/src/components/Tools/ToolSelectDialog.tsx @@ -90,7 +90,7 @@ function ToolSelectDialog({ const onRemoveTool = (tool: string) => { setShowPluginAuthForm(false); updateUserPlugins.mutate( - { pluginKey: tool, action: 'uninstall', auth: null, isAssistantTool: true }, + { pluginKey: tool, action: 'uninstall', auth: null, isEntityTool: true }, { onError: (error: unknown) => { handleInstallError(error as TError); @@ -199,7 +199,7 @@ function ToolSelectDialog({ handleInstall(installActionData)} - isAssistantTool={true} + isEntityTool={true} />
)} diff --git a/client/src/data-provider/Files/mutations.ts b/client/src/data-provider/Files/mutations.ts index 5cdd226517d..9f51a5e7172 100644 --- a/client/src/data-provider/Files/mutations.ts +++ b/client/src/data-provider/Files/mutations.ts @@ -10,13 +10,6 @@ import { import type * as t from 'librechat-data-provider'; import type { UseMutationResult } from '@tanstack/react-query'; -export type TGenTitleMutation = UseMutationResult< - t.TGenTitleResponse, - unknown, - t.TGenTitleRequest, - unknown ->; - export const useUploadFileMutation = ( _options?: t.UploadMutationOptions, signal?: AbortSignal | null, @@ -152,9 +145,9 @@ export const useDeleteFilesMutation = ( return useMutation([MutationKeys.fileDelete], { mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body), ...options, - onSuccess: (data, ...args) => { + onSuccess: (data, vars, context) => { queryClient.setQueryData([QueryKeys.files], (cachefiles) => { - const { files: filesDeleted } = args[0]; + const { files: filesDeleted } = vars; const fileMap = filesDeleted.reduce((acc, file) => { acc.set(file.file_id, file); @@ -163,7 +156,10 @@ export const useDeleteFilesMutation = ( return (cachefiles ?? []).filter((file) => !fileMap.has(file.file_id)); }); - onSuccess?.(data, ...args); + onSuccess?.(data, vars, context); + if (vars.agent_id != null && vars.agent_id) { + queryClient.refetchQueries([QueryKeys.agent, vars.agent_id]); + } }, }); }; diff --git a/client/src/data-provider/Tools/index.ts b/client/src/data-provider/Tools/index.ts new file mode 100644 index 00000000000..c8699b24307 --- /dev/null +++ b/client/src/data-provider/Tools/index.ts @@ -0,0 +1,2 @@ +export * from './queries'; +// export * from './mutations'; diff --git a/client/src/data-provider/Tools/queries.ts b/client/src/data-provider/Tools/queries.ts new file mode 100644 index 00000000000..7ca8e2f0efb --- /dev/null +++ b/client/src/data-provider/Tools/queries.ts @@ -0,0 +1,20 @@ +import { QueryKeys, dataService } from 'librechat-data-provider'; +import { useQuery } from '@tanstack/react-query'; +import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query'; +import type t from 'librechat-data-provider'; + +export const useVerifyAgentToolAuth = ( + params: t.VerifyToolAuthParams, + config?: UseQueryOptions, +): QueryObserverResult => { + return useQuery( + [QueryKeys.toolAuth, params.toolId], + () => dataService.getVerifyAgentToolAuth(params), + { + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + ...config, + }, + ); +}; diff --git a/client/src/data-provider/index.ts b/client/src/data-provider/index.ts index 1729c969bbc..2a9dc740f23 100644 --- a/client/src/data-provider/index.ts +++ b/client/src/data-provider/index.ts @@ -1,4 +1,5 @@ export * from './Files'; +export * from './Tools'; export * from './connection'; export * from './mutations'; export * from './prompts'; diff --git a/client/src/hooks/Generic/index.ts b/client/src/hooks/Generic/index.ts new file mode 100644 index 00000000000..9d9d64459fe --- /dev/null +++ b/client/src/hooks/Generic/index.ts @@ -0,0 +1 @@ +export * from './useLazyEffect'; diff --git a/client/src/hooks/Generic/useLazyEffect.ts b/client/src/hooks/Generic/useLazyEffect.ts new file mode 100644 index 00000000000..b205a162d2c --- /dev/null +++ b/client/src/hooks/Generic/useLazyEffect.ts @@ -0,0 +1,18 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +// https://stackoverflow.com/a/67504622/51500 +import { DependencyList, EffectCallback, useCallback, useEffect, useRef } from 'react'; +import debounce from 'lodash/debounce'; + +export function useLazyEffect(effect: EffectCallback, deps: DependencyList = [], wait = 300) { + const cleanUp = useRef void)>(); + const effectRef = useRef(); + effectRef.current = useCallback(effect, deps); + const lazyEffect = useCallback( + debounce(() => (cleanUp.current = effectRef.current?.()), wait), + [], + ); + useEffect(lazyEffect, deps); + useEffect(() => { + return () => (cleanUp.current instanceof Function ? cleanUp.current() : undefined); + }, []); +} diff --git a/client/src/hooks/Plugins/index.ts b/client/src/hooks/Plugins/index.ts index 53db4f0d27c..0668e757d65 100644 --- a/client/src/hooks/Plugins/index.ts +++ b/client/src/hooks/Plugins/index.ts @@ -1,2 +1,3 @@ +export { default as useAuthCodeTool } from './useAuthCodeTool'; export { default as usePluginInstall } from './usePluginInstall'; export { default as usePluginDialogHelpers } from './usePluginDialogHelpers'; diff --git a/client/src/hooks/Plugins/useAuthCodeTool.ts b/client/src/hooks/Plugins/useAuthCodeTool.ts new file mode 100644 index 00000000000..b33e05c169a --- /dev/null +++ b/client/src/hooks/Plugins/useAuthCodeTool.ts @@ -0,0 +1,53 @@ +import { useCallback } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { AuthType, Tools, QueryKeys } from 'librechat-data-provider'; +import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; +// import { useToastContext } from '~/Providers'; + +const useAuthCodeTool = (options?: { isEntityTool: boolean }) => { + // const { showToast } = useToastContext(); + const queryClient = useQueryClient(); + const isEntityTool = options?.isEntityTool ?? true; + const updateUserPlugins = useUpdateUserPluginsMutation({ + onMutate: (vars) => { + queryClient.setQueryData([QueryKeys.toolAuth, Tools.execute_code], () => ({ + authenticated: vars.action === 'install', + message: AuthType.USER_PROVIDED, + })); + }, + onSuccess: () => { + queryClient.invalidateQueries([QueryKeys.toolAuth, Tools.execute_code]); + }, + onError: () => { + queryClient.invalidateQueries([QueryKeys.toolAuth, Tools.execute_code]); + }, + }); + + const installTool = useCallback( + (apiKey: string) => { + updateUserPlugins.mutate({ + pluginKey: Tools.execute_code, + action: 'install', + auth: { LIBRECHAT_CODE_API_KEY: apiKey }, + isEntityTool, + }); + }, + [updateUserPlugins, isEntityTool], + ); + + const removeTool = useCallback(() => { + updateUserPlugins.mutate({ + pluginKey: Tools.execute_code, + action: 'uninstall', + auth: { LIBRECHAT_CODE_API_KEY: null }, + isEntityTool, + }); + }, [updateUserPlugins, isEntityTool]); + + return { + removeTool, + installTool, + }; +}; + +export default useAuthCodeTool; diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index 26e466b4475..863173df3de 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -6,6 +6,7 @@ export * from './Config'; export * from './Conversations'; export * from './Nav'; export * from './Files'; +export * from './Generic'; export * from './Input'; export * from './Messages'; export * from './Plugins'; diff --git a/package-lock.json b/package-lock.json index b61352eff6e..f218774e768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "@langchain/core": "^0.2.18", "@langchain/google-genai": "^0.0.11", "@langchain/google-vertexai": "^0.0.17", - "@librechat/agents": "^1.6.9", + "@librechat/agents": "^1.7.7", "axios": "^1.7.7", "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", @@ -1260,9 +1260,9 @@ } }, "api/node_modules/openai": { - "version": "4.63.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.63.0.tgz", - "integrity": "sha512-Y9V4KODbmrOpqiOmCDVnPfMxMqKLOx8Hwcdn/r8mePq4yv7FSXGnxCs8/jZKO7zCB/IVPWihpJXwJNAIOEiZ2g==", + "version": "4.70.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.70.2.tgz", + "integrity": "sha512-Q2ymi/KPUYv+LJ9rFxeYxpkVAhcrZFTVvnJbdF1pUHg9eMC6lY8PU4TO1XOK5UZzOZuuVicouRwVMi1iDrT4qw==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -8245,6 +8245,29 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@ibm-cloud/watsonx-ai": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.1.2.tgz", + "integrity": "sha512-0+ClK12jk1Jk28Hwc2BDmKkTXPjFkQOfCKzUk82TsoPwAIEVN+rlM1cny52d3oSMXXbeKorVDmnIEbXPseHiQA==", + "peer": true, + "dependencies": { + "@types/node": "^18.0.0", + "extend": "3.0.2", + "ibm-cloud-sdk-core": "^5.0.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@ibm-cloud/watsonx-ai/node_modules/@types/node": { + "version": "18.19.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.63.tgz", + "integrity": "sha512-hcUB7THvrGmaEcPcvUZCZtQ2Z3C+UR/aOcraBLCvTsFMh916Gc1kCCYcfcMuB76HM2pSerxl1PoP3KnmHzd9Lw==", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -8875,200 +8898,6 @@ "node": ">= 14" } }, - "node_modules/@langchain/anthropic": { - "version": "0.2.18", - "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.2.18.tgz", - "integrity": "sha512-4ZDTxMwGKTPRAi2Supu/faBSmwPIm/ga5QlazyO78Mf/8QbDR2DcvM5394FAy+X/nRAfnMbyXteO5IRJm653gw==", - "dependencies": { - "@anthropic-ai/sdk": "^0.25.2", - "@langchain/core": ">=0.2.21 <0.3.0", - "fast-xml-parser": "^4.4.1", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.25.2.tgz", - "integrity": "sha512-F1Hck/asswwidFLtGdMg3XYgRxEUfygNbpkq5KEaEGsHNaSfxeX18/uZGQCL0oQNcj/tYNx8BaFXVwRhFDi45g==", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - } - }, - "node_modules/@langchain/anthropic/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", - "dependencies": { - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/anthropic/node_modules/@types/node": { - "version": "18.19.53", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.53.tgz", - "integrity": "sha512-GLxgUgHhDKO1Edw9Q0lvMbiO/IQXJwJlMaqxSGBXMpPy8uhkCs2iiPFaB2Q/gmobnFkckD3rqTBMVjXdwq+nKg==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@langchain/anthropic/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" - }, - "node_modules/@langchain/anthropic/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/anthropic/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", - "dependencies": { - "@types/uuid": "^10.0.0", - "commander": "^10.0.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "openai": "*" - }, - "peerDependenciesMeta": { - "openai": { - "optional": true - } - } - }, - "node_modules/@langchain/anthropic/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@langchain/aws": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@langchain/aws/-/aws-0.0.10.tgz", - "integrity": "sha512-q9sL34lq2UpSVDBTNixy2+DJbjd5RSlCGBoatVeWpfMDv2J47sJZlfVGfXLidFZhQox6uL1GyQJoKrIb+Cu97Q==", - "dependencies": { - "@aws-sdk/client-bedrock-agent-runtime": "^3.616.0", - "@aws-sdk/client-bedrock-runtime": "^3.602.0", - "@aws-sdk/client-kendra": "^3.352.0", - "@aws-sdk/credential-provider-node": "^3.600.0", - "@langchain/core": ">=0.2.21 <0.3.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.22.5" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/aws/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", - "dependencies": { - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/aws/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" - }, - "node_modules/@langchain/aws/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/aws/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", - "dependencies": { - "@types/uuid": "^10.0.0", - "commander": "^10.0.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "openai": "*" - }, - "peerDependenciesMeta": { - "openai": { - "optional": true - } - } - }, - "node_modules/@langchain/aws/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@langchain/community": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.0.17.tgz", @@ -9655,95 +9484,15 @@ "node": ">=18" } }, - "node_modules/@langchain/langgraph": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.0.31.tgz", - "integrity": "sha512-f5QMSLy/RnLktsqnNm2mq8gp1xplHwQf87XIPVO0IYuumOJiafx5lE7ahPO+fVmCzAz6LxcsVocvD0JqxXR/2w==", - "dependencies": { - "@langchain/core": ">=0.2.18 <0.3.0", - "uuid": "^10.0.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "better-sqlite3": "^9.5.0" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - } - } - }, - "node_modules/@langchain/langgraph/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", - "dependencies": { - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/langgraph/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" - }, - "node_modules/@langchain/langgraph/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/langgraph/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", + "node_modules/@langchain/langgraph-sdk": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.20.tgz", + "integrity": "sha512-58iGYL0PppSiIHtIUNAN+x6TCl+Vb0dAmlToSMJHUng8W53ffXHQTNqVNxJlPsHO6zdgPJm4DRl53z6vkSUZpw==", "dependencies": { - "@types/uuid": "^10.0.0", - "commander": "^10.0.1", + "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "openai": "*" - }, - "peerDependenciesMeta": { - "openai": { - "optional": true - } - } - }, - "node_modules/@langchain/langgraph/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" + "uuid": "^9.0.0" } }, "node_modules/@langchain/mistralai": { @@ -9762,9 +9511,9 @@ } }, "node_modules/@langchain/mistralai/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.36.tgz", + "integrity": "sha512-qHLvScqERDeH7y2cLuJaSAlMwg3f/3Oc9nayRSXRU2UuaK/SOhI42cxiPLj1FnuHJSmN0rBQFkrLx02gI4mcVg==", "dependencies": { "ansi-styles": "^5.0.0", "camelcase": "6", @@ -9799,9 +9548,9 @@ } }, "node_modules/@langchain/mistralai/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.68.tgz", + "integrity": "sha512-otmiysWtVAqzMx3CJ4PrtUBhWRG5Co8Z4o7hSZENPjlit9/j3/vm3TSvbaxpDYakZxtMjhkcJTqrdYFipISEiQ==", "dependencies": { "@types/uuid": "^10.0.0", "commander": "^10.0.1", @@ -9872,88 +9621,6 @@ "openai": "bin/cli" } }, - "node_modules/@langchain/textsplitters": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.0.3.tgz", - "integrity": "sha512-cXWgKE3sdWLSqAa8ykbCcUsUF1Kyr5J3HOWYGuobhPEycXW4WI++d5DhzdpL238mzoEXTi90VqfSCra37l5YqA==", - "dependencies": { - "@langchain/core": ">0.2.0 <0.3.0", - "js-tiktoken": "^1.0.12" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/textsplitters/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", - "dependencies": { - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/textsplitters/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" - }, - "node_modules/@langchain/textsplitters/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/textsplitters/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", - "dependencies": { - "@types/uuid": "^10.0.0", - "commander": "^10.0.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "openai": "*" - }, - "peerDependenciesMeta": { - "openai": { - "optional": true - } - } - }, - "node_modules/@langchain/textsplitters/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@lezer/common": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", @@ -10006,45 +9673,95 @@ } }, "node_modules/@librechat/agents": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-1.6.9.tgz", - "integrity": "sha512-a2ef4cpC8BpegiIOJkC5rVqtp2lAfTEm/wWUfQ/wtEFPPyUBlnzoQRcO79V3Veju3DzkmMBJP5oUrzGZ+lKemg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-1.7.7.tgz", + "integrity": "sha512-0VOvPtPzbAUF5BXt+qeIlQqYzPV4RC4+iz3g9HooBPDIuPlaEHAH7RC2fiRKO6XJ/1E5/8YSj6YugV3MJGyyoQ==", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/credential-provider-node": "^3.613.0", "@aws-sdk/types": "^3.609.0", - "@langchain/anthropic": "^0.2.15", - "@langchain/aws": "^0.0.10", - "@langchain/community": "^0.2.20", - "@langchain/core": "^0.2.31", + "@langchain/anthropic": "^0.3.7", + "@langchain/aws": "^0.1.1", + "@langchain/community": "^0.3.11", + "@langchain/core": "^0.3.16", "@langchain/google-vertexai": "^0.0.20", - "@langchain/langgraph": "^0.0.31", + "@langchain/langgraph": "^0.2.19", "@langchain/mistralai": "^0.0.26", + "@langchain/ollama": "^0.1.1", + "@langchain/openai": "^0.3.11", "@smithy/eventstream-codec": "^2.2.0", "@smithy/protocol-http": "^3.0.6", "@smithy/signature-v4": "^2.0.10", "@smithy/util-utf8": "^2.0.0", "dotenv": "^16.4.5", - "langchain": "^0.2.10", "nanoid": "^3.3.7" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@librechat/agents/node_modules/@anthropic-ai/sdk": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", + "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/anthropic": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.7.tgz", + "integrity": "sha512-MjV7BNPalnG3S6PqXYHRtv3nEML1fFHl9OsqjT5KCPcULxJImnIZrJX5qMTnezM5A+Q6KOZt3e07x7aYCmU3Sg==", + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "fast-xml-parser": "^4.4.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/aws": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@langchain/aws/-/aws-0.1.1.tgz", + "integrity": "sha512-6JoRoW/8nca+jJcklZ6kJ7qBUQoBskygGiF5Wf3A89JPyGCHDds8rPQFNJO5S7ki+kRRNWh5G5hcc2ahaqZjWQ==", + "dependencies": { + "@aws-sdk/client-bedrock-agent-runtime": "^3.616.0", + "@aws-sdk/client-bedrock-runtime": "^3.602.0", + "@aws-sdk/client-kendra": "^3.352.0", + "@aws-sdk/credential-provider-node": "^3.600.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.22.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.2.33", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.2.33.tgz", - "integrity": "sha512-YsytROnBYoPqtUcV2In+afyLACzxwFrYRn7EBKYL7XWl3XNwrT85U1+nLM5b+MOjXvg9YfJSjrs1Tlbfy4st8g==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.11.tgz", + "integrity": "sha512-hgnqsgWAhfUj9Kp0y+FGxlKot/qJFxat9GfIPJSJU4ViN434PgeMAQK53tkGZ361E2Zoo1V4RoGlSw4AjJILiA==", "dependencies": { - "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/openai": ">=0.2.0 <0.3.0", + "@langchain/openai": ">=0.2.0 <0.4.0", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", "js-yaml": "^4.1.0", - "langchain": "~0.2.3", - "langsmith": "~0.1.30", + "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", + "langsmith": "^0.2.0", "uuid": "^10.0.0", "zod": "^3.22.3", "zod-to-json-schema": "^3.22.5" @@ -10080,9 +9797,11 @@ "@google-cloud/storage": "^6.10.1 || ^7.7.0", "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^2.6.4", - "@langchain/langgraph": "*", + "@ibm-cloud/watsonx-ai": "*", + "@langchain/core": ">=0.2.21 <0.4.0", "@layerup/layerup-security": "^1.5.12", - "@mendable/firecrawl-js": "^0.0.13", + "@libsql/client": "^0.14.0", + "@mendable/firecrawl-js": "^1.4.3", "@mlc-ai/web-llm": "*", "@mozilla/readability": "*", "@neondatabase/serverless": "*", @@ -10103,7 +9822,7 @@ "@tensorflow-models/universal-sentence-encoder": "*", "@tensorflow/tfjs-converter": "*", "@tensorflow/tfjs-core": "*", - "@upstash/ratelimit": "^1.1.3", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", "@upstash/redis": "^1.20.6", "@upstash/vector": "^1.1.1", "@vercel/kv": "^0.2.3", @@ -10124,7 +9843,6 @@ "closevector-web": "0.1.6", "cohere-ai": "*", "convex": "^1.3.1", - "couchbase": "^4.3.0", "crypto-js": "^4.2.0", "d3-dsv": "^2.0.0", "discord.js": "^14.14.1", @@ -10137,6 +9855,7 @@ "googleapis": "*", "hnswlib-node": "^3.0.0", "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", "ignore": "^5.2.0", "interface-datastore": "^8.2.11", "ioredis": "^5.3.2", @@ -10150,7 +9869,6 @@ "mongodb": ">=5.2.0", "mysql2": "^3.9.8", "neo4j-driver": "*", - "node-llama-cpp": "*", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", "pdf-parse": "1.1.1", @@ -10160,6 +9878,7 @@ "playwright": "^1.32.1", "portkey-ai": "^0.1.11", "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", "redis": "*", "replicate": "^0.29.4", "sonix-speech-recognition": "^2.1.1", @@ -10257,10 +9976,10 @@ "@huggingface/inference": { "optional": true }, - "@langchain/langgraph": { + "@layerup/layerup-security": { "optional": true }, - "@layerup/layerup-security": { + "@libsql/client": { "optional": true }, "@mendable/firecrawl-js": { @@ -10389,9 +10108,6 @@ "convex": { "optional": true }, - "couchbase": { - "optional": true - }, "crypto-js": { "optional": true }, @@ -10467,9 +10183,6 @@ "neo4j-driver": { "optional": true }, - "node-llama-cpp": { - "optional": true - }, "notion-to-md": { "optional": true }, @@ -10497,6 +10210,9 @@ "puppeteer": { "optional": true }, + "pyodide": { + "optional": true + }, "redis": { "optional": true }, @@ -10541,16 +10257,95 @@ } } }, + "node_modules/@librechat/agents/node_modules/@langchain/community/node_modules/langchain": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.5.tgz", + "integrity": "sha512-Gq0xC45Sq6nszS8kQG9suCrmBsuXH0INMmiF7D2TwPb6mtG35Jiq4grCk9ykpwPsarTHdty3SzUbII/FqiYSSw==", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.4.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.2.0", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.2.21 <0.4.0", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, "node_modules/@librechat/agents/node_modules/@langchain/core": { - "version": "0.2.34", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.34.tgz", - "integrity": "sha512-Hkveq1UcOjUj1DVn5erbqElyRj1t04NORSuSIZAJCtPO7EDkIqomjAarJ5+I5NUpQeIONgbOdnY9TkJ6cKUSVA==", + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.16.tgz", + "integrity": "sha512-g83M2Z1XlhECFUtT4C7XLsVVGt2Hk3Y/KhS5tZSsz+Gqtxwd790/MD7MxdUHpZj0VKkvrFuWARWpJmNKlkiY+g==", "dependencies": { "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", + "langsmith": "^0.2.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", @@ -10574,25 +10369,130 @@ "node": ">=18" } }, + "node_modules/@librechat/agents/node_modules/@langchain/google-vertexai/node_modules/@langchain/core": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.36.tgz", + "integrity": "sha512-qHLvScqERDeH7y2cLuJaSAlMwg3f/3Oc9nayRSXRU2UuaK/SOhI42cxiPLj1FnuHJSmN0rBQFkrLx02gI4mcVg==", + "dependencies": { + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.1.56-rc.1", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/google-vertexai/node_modules/langsmith": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.68.tgz", + "integrity": "sha512-otmiysWtVAqzMx3CJ4PrtUBhWRG5Co8Z4o7hSZENPjlit9/j3/vm3TSvbaxpDYakZxtMjhkcJTqrdYFipISEiQ==", + "dependencies": { + "@types/uuid": "^10.0.0", + "commander": "^10.0.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/langgraph": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.19.tgz", + "integrity": "sha512-dgFdnEokC5zY32skZ6rcBYJNTH3WQXVY0LRI1zBGvbmK/nPfanIB1URwNxqRFjj/qHRKofxoehqYr1ww1zB+zA==", + "dependencies": { + "@langchain/langgraph-checkpoint": "~0.0.10", + "@langchain/langgraph-sdk": "~0.0.20", + "double-ended-queue": "^2.1.0-0", + "uuid": "^10.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.36 <0.3.0 || >=0.3.9 < 0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/langgraph-checkpoint": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.11.tgz", + "integrity": "sha512-nroHHkAi/UPn9LqqZcgOydfB8qZw5TXuXDFc43MIydnW4lb8m9hVHnQ3lgb2WGSgtbZJnsIx0TzL19oemJBRKg==", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/ollama": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.1.1.tgz", + "integrity": "sha512-IQEdzGkfKzdoyys3GW5hCXc64d/u1xkrYXved73BLO+bnyQfzrM224jdsiYGUpjW3cUaO1ebD6PUiMYcANPPFQ==", + "dependencies": { + "ollama": "^0.5.9", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@librechat/agents/node_modules/@langchain/openai": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.2.11.tgz", - "integrity": "sha512-Pu8+WfJojCgSf0bAsXb4AjqvcDyAWyoEB1AoCRNACgEnBWZuitz3hLwCo9I+6hAbeg3QJ37g82yKcmvKAg1feg==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.11.tgz", + "integrity": "sha512-mEFbpJ8w8NPArsquUlCwxvZTKNkXxqwzvTEYzv6Jb7gUoBDOZtwLg6AdcngTJ+w5VFh3wxgPy0g3zb9Aw0Qbpw==", "dependencies": { - "@langchain/core": ">=0.2.26 <0.3.0", "js-tiktoken": "^1.0.12", - "openai": "^4.57.3", + "openai": "^4.68.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.3" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.26 <0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" } }, "node_modules/@librechat/agents/node_modules/@types/node": { - "version": "18.19.53", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.53.tgz", - "integrity": "sha512-GLxgUgHhDKO1Edw9Q0lvMbiO/IQXJwJlMaqxSGBXMpPy8uhkCs2iiPFaB2Q/gmobnFkckD3rqTBMVjXdwq+nKg==", + "version": "18.19.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.63.tgz", + "integrity": "sha512-hcUB7THvrGmaEcPcvUZCZtQ2Z3C+UR/aOcraBLCvTsFMh916Gc1kCCYcfcMuB76HM2pSerxl1PoP3KnmHzd9Lw==", "dependencies": { "undici-types": "~5.26.4" } @@ -10625,269 +10525,10 @@ "node-addon-api": "^8.0.0" } }, - "node_modules/@librechat/agents/node_modules/langchain": { - "version": "0.2.20", - "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.2.20.tgz", - "integrity": "sha512-tbels6Rr524iMM3VOQ4aTGnEOOjAA1BQuBR8u/8gJ2yT48lMtIQRAN32Y4KVjKK+hEWxHHlmLBrtgLpTphFjNA==", - "dependencies": { - "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/openai": ">=0.1.0 <0.3.0", - "@langchain/textsplitters": "~0.0.0", - "binary-extensions": "^2.2.0", - "js-tiktoken": "^1.0.12", - "js-yaml": "^4.1.0", - "jsonpointer": "^5.0.1", - "langsmith": "^0.1.56-rc.1", - "openapi-types": "^12.1.3", - "p-retry": "4", - "uuid": "^10.0.0", - "yaml": "^2.2.1", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@aws-sdk/client-s3": "*", - "@aws-sdk/client-sagemaker-runtime": "*", - "@aws-sdk/client-sfn": "*", - "@aws-sdk/credential-provider-node": "*", - "@azure/storage-blob": "*", - "@browserbasehq/sdk": "*", - "@gomomento/sdk": "*", - "@gomomento/sdk-core": "*", - "@gomomento/sdk-web": "^1.51.1", - "@langchain/anthropic": "*", - "@langchain/aws": "*", - "@langchain/cohere": "*", - "@langchain/google-genai": "*", - "@langchain/google-vertexai": "*", - "@langchain/groq": "*", - "@langchain/mistralai": "*", - "@langchain/ollama": "*", - "@mendable/firecrawl-js": "*", - "@notionhq/client": "*", - "@pinecone-database/pinecone": "*", - "@supabase/supabase-js": "*", - "@vercel/kv": "*", - "@xata.io/client": "*", - "apify-client": "*", - "assemblyai": "*", - "axios": "*", - "cheerio": "*", - "chromadb": "*", - "convex": "*", - "couchbase": "*", - "d3-dsv": "*", - "epub2": "*", - "fast-xml-parser": "*", - "handlebars": "^4.7.8", - "html-to-text": "*", - "ignore": "*", - "ioredis": "*", - "jsdom": "*", - "mammoth": "*", - "mongodb": "*", - "node-llama-cpp": "*", - "notion-to-md": "*", - "officeparser": "*", - "pdf-parse": "*", - "peggy": "^3.0.2", - "playwright": "*", - "puppeteer": "*", - "pyodide": ">=0.24.1 <0.27.0", - "redis": "*", - "sonix-speech-recognition": "*", - "srt-parser-2": "*", - "typeorm": "*", - "weaviate-ts-client": "*", - "web-auth-library": "*", - "ws": "*", - "youtube-transcript": "*", - "youtubei.js": "*" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-s3": { - "optional": true - }, - "@aws-sdk/client-sagemaker-runtime": { - "optional": true - }, - "@aws-sdk/client-sfn": { - "optional": true - }, - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@browserbasehq/sdk": { - "optional": true - }, - "@gomomento/sdk": { - "optional": true - }, - "@gomomento/sdk-core": { - "optional": true - }, - "@gomomento/sdk-web": { - "optional": true - }, - "@langchain/anthropic": { - "optional": true - }, - "@langchain/aws": { - "optional": true - }, - "@langchain/cohere": { - "optional": true - }, - "@langchain/google-genai": { - "optional": true - }, - "@langchain/google-vertexai": { - "optional": true - }, - "@langchain/groq": { - "optional": true - }, - "@langchain/mistralai": { - "optional": true - }, - "@langchain/ollama": { - "optional": true - }, - "@mendable/firecrawl-js": { - "optional": true - }, - "@notionhq/client": { - "optional": true - }, - "@pinecone-database/pinecone": { - "optional": true - }, - "@supabase/supabase-js": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "axios": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "chromadb": { - "optional": true - }, - "convex": { - "optional": true - }, - "couchbase": { - "optional": true - }, - "d3-dsv": { - "optional": true - }, - "epub2": { - "optional": true - }, - "faiss-node": { - "optional": true - }, - "fast-xml-parser": { - "optional": true - }, - "handlebars": { - "optional": true - }, - "html-to-text": { - "optional": true - }, - "ignore": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "mammoth": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "node-llama-cpp": { - "optional": true - }, - "notion-to-md": { - "optional": true - }, - "officeparser": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "peggy": { - "optional": true - }, - "playwright": { - "optional": true - }, - "puppeteer": { - "optional": true - }, - "pyodide": { - "optional": true - }, - "redis": { - "optional": true - }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, - "typeorm": { - "optional": true - }, - "weaviate-ts-client": { - "optional": true - }, - "web-auth-library": { - "optional": true - }, - "ws": { - "optional": true - }, - "youtube-transcript": { - "optional": true - }, - "youtubei.js": { - "optional": true - } - } - }, "node_modules/@librechat/agents/node_modules/langsmith": { - "version": "0.1.61", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz", - "integrity": "sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.2.3.tgz", + "integrity": "sha512-SPMYPVqR9kwXZVmJ2PXC61HeBnXIFHrjfjDxQ14H0+n5p4gqjLzgSHIQyxBlFeWQUQzArJxe65Ap+s+Xo1cZog==", "dependencies": { "@types/uuid": "^10.0.0", "commander": "^10.0.1", @@ -10906,9 +10547,9 @@ } }, "node_modules/@librechat/agents/node_modules/node-addon-api": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", - "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.1.tgz", + "integrity": "sha512-vmEOvxwiH8tlOcv4SyE8RH34rI5/nWVaigUeAUPawC6f0+HoDthwI0vkMu4tbtsZrXq6QXFfrkhjofzKEs5tpA==", "optional": true, "peer": true, "engines": { @@ -10916,9 +10557,9 @@ } }, "node_modules/@librechat/agents/node_modules/openai": { - "version": "4.65.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.65.0.tgz", - "integrity": "sha512-LfA4KUBpH/8rA3vjCQ74LZtdK/8wx9W6Qxq8MHqEdImPsN1XPQ2ompIuJWkKS6kXt5Cs5i8Eb65IIo4M7U+yeQ==", + "version": "4.70.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.70.2.tgz", + "integrity": "sha512-Q2ymi/KPUYv+LJ9rFxeYxpkVAhcrZFTVvnJbdF1pUHg9eMC6lY8PU4TO1XOK5UZzOZuuVicouRwVMi1iDrT4qw==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -15353,8 +14994,7 @@ "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" }, "node_modules/@types/triple-beam": { "version": "1.3.5", @@ -19044,6 +18684,11 @@ "url": "https://dotenvx.com" } }, + "node_modules/double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" + }, "node_modules/downloadjs": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", @@ -22099,6 +21744,113 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/ibm-cloud-sdk-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.1.0.tgz", + "integrity": "sha512-KJCbPz3tiXB1NGAD7cL4JtwpWV8yd/C7jsaHsxvedMo2ZblNG8emMyvSpGhiKAQVZmi3c0ujz6eJdy22NHuUWQ==", + "peer": true, + "dependencies": { + "@types/debug": "^4.1.12", + "@types/node": "~10.14.19", + "@types/tough-cookie": "^4.0.0", + "axios": "1.7.4", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "extend": "3.0.2", + "file-type": "16.5.4", + "form-data": "4.0.0", + "isstream": "0.1.2", + "jsonwebtoken": "^9.0.2", + "mime-types": "2.1.35", + "retry-axios": "^2.6.0", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "10.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz", + "integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw==", + "peer": true + }, + "node_modules/ibm-cloud-sdk-core/node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "peer": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -23239,6 +22991,12 @@ "ws": "*" } }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "peer": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -28244,9 +28002,9 @@ } }, "node_modules/ollama": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.0.tgz", - "integrity": "sha512-CRtRzsho210EGdK52GrUMohA2pU+7NbgEaBG3DcYeRmvQthDO7E2LHOkLlUUeaYUlNmEd8icbjC02ug9meSYnw==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz", + "integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==", "dependencies": { "whatwg-fetch": "^3.6.20" } @@ -30625,8 +30383,7 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "devOptional": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/pstree.remy": { "version": "1.1.8", @@ -30711,8 +30468,7 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "devOptional": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -32183,8 +31939,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "devOptional": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.8", @@ -32281,6 +32036,18 @@ "node": ">= 4" } }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "peer": true, + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -34217,7 +33984,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "devOptional": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -34232,7 +33998,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "devOptional": true, "engines": { "node": ">= 4.0.0" } @@ -34915,7 +34680,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "devOptional": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index ce9b77d4ac5..a33b368a1b9 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -125,10 +125,10 @@ export const assistants = ({ return url; }; -export const agents = ({ path, options }: { path?: string; options?: object }) => { +export const agents = ({ path = '', options }: { path?: string; options?: object }) => { let url = '/api/agents'; - if (path) { + if (path && path !== '') { url += `/${path}`; } diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index bcaa739652e..81ff5eca418 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -584,6 +584,7 @@ export const alternateName = { [EModelEndpoint.anthropic]: 'Anthropic', [EModelEndpoint.custom]: 'Custom', [EModelEndpoint.bedrock]: 'AWS Bedrock', + ollama: 'Ollama', }; const sharedOpenAIModels = [ diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index 0df22f62369..ccce126a814 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -304,6 +304,16 @@ export const getAvailableTools = ( return request.get(path); }; +export const getVerifyAgentToolAuth = ( + params: q.VerifyToolAuthParams, +): Promise => { + return request.get( + endpoints.agents({ + path: `tools/${params.toolId}/auth`, + }), + ); +}; + /* Files */ export const getFiles = (): Promise => { diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index a4ed0b121cd..b977853974c 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -25,6 +25,7 @@ export enum QueryKeys { files = 'files', fileConfig = 'fileConfig', tools = 'tools', + toolAuth = 'toolAuth', agentTools = 'agentTools', actions = 'actions', assistantDocs = 'assistantDocs', diff --git a/packages/data-provider/src/react-query/react-query-service.ts b/packages/data-provider/src/react-query/react-query-service.ts index 997751358a3..0394addf5e9 100644 --- a/packages/data-provider/src/react-query/react-query-service.ts +++ b/packages/data-provider/src/react-query/react-query-service.ts @@ -408,16 +408,16 @@ export const useAvailablePluginsQuery = ( ); }; -export const useUpdateUserPluginsMutation = (): UseMutationResult< - t.TUser, - unknown, - t.TUpdateUserPlugins, - unknown -> => { +export const useUpdateUserPluginsMutation = ( + _options?: m.UpdatePluginAuthOptions, +): UseMutationResult => { const queryClient = useQueryClient(); + const { onSuccess, ...options } = _options ?? {}; return useMutation((payload: t.TUpdateUserPlugins) => dataService.updateUserPlugins(payload), { - onSuccess: () => { + ...options, + onSuccess: (...args) => { queryClient.invalidateQueries([QueryKeys.user]); + onSuccess?.(...args); }, }); }; diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 4438dd32a2d..cef29a8cc62 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -8,7 +8,7 @@ export const isUUID = z.string().uuid(); export enum AuthType { OVERRIDE_AUTH = 'override_auth', USER_PROVIDED = 'user_provided', - SYSTEM_DEFINED = 'SYSTEM_DEFINED', + SYSTEM_DEFINED = 'system_defined', } export const authTypeSchema = z.nativeEnum(AuthType); @@ -369,7 +369,7 @@ export const tPluginSchema = z.object({ pluginKey: z.string(), description: z.string(), icon: z.string(), - authConfig: z.array(tPluginAuthConfigSchema), + authConfig: z.array(tPluginAuthConfigSchema).optional(), authenticated: z.boolean().optional(), isButton: z.boolean().optional(), }); diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index a125a2c9938..031a85f8c84 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -72,14 +72,13 @@ export type TPluginAction = { pluginKey: string; action: 'install' | 'uninstall'; auth?: unknown; - isAssistantTool?: boolean; + isEntityTool?: boolean; }; export type GroupedConversations = [key: string, TConversation[]][]; export type TUpdateUserPlugins = { - isAssistantTool?: boolean; - isAgentTool?: boolean; + isEntityTool?: boolean; pluginKey: string; action: string; auth?: unknown; diff --git a/packages/data-provider/src/types/files.ts b/packages/data-provider/src/types/files.ts index 49ae79a3134..5985096f4c1 100644 --- a/packages/data-provider/src/types/files.ts +++ b/packages/data-provider/src/types/files.ts @@ -69,6 +69,7 @@ export type TFile = { height?: number; expiresAt?: string | Date; preview?: string; + metadata?: { fileIdentifier?: string }; createdAt?: string | Date; updatedAt?: string | Date; }; diff --git a/packages/data-provider/src/types/mutations.ts b/packages/data-provider/src/types/mutations.ts index b9f8f2c3858..8d4aed25016 100644 --- a/packages/data-provider/src/types/mutations.ts +++ b/packages/data-provider/src/types/mutations.ts @@ -248,3 +248,6 @@ export type AcceptTermsMutationOptions = MutationOptions< unknown, void >; + +/* Tools */ +export type UpdatePluginAuthOptions = MutationOptions; diff --git a/packages/data-provider/src/types/queries.ts b/packages/data-provider/src/types/queries.ts index 13fb6a577a9..7aeaa6eb207 100644 --- a/packages/data-provider/src/types/queries.ts +++ b/packages/data-provider/src/types/queries.ts @@ -1,13 +1,13 @@ import type { InfiniteData } from '@tanstack/react-query'; +import type * as s from '../schemas'; import type * as t from '../types'; -import type { TMessage, TConversation, TSharedLink, TConversationTag } from '../schemas'; export type Conversation = { id: string; createdAt: number; participants: string[]; lastMessage: string; - conversations: TConversation[]; + conversations: s.TConversation[]; }; // Parameters for listing conversations (e.g., for pagination) @@ -24,33 +24,33 @@ export type ConversationListParams = { // Type for the response from the conversation list API export type ConversationListResponse = { - conversations: TConversation[]; + conversations: s.TConversation[]; pageNumber: string; pageSize: string | number; pages: string | number; - messages: TMessage[]; + messages: s.TMessage[]; }; export type ConversationData = InfiniteData; export type ConversationUpdater = ( data: ConversationData, - conversation: TConversation, + conversation: s.TConversation, ) => ConversationData; -export type SharedMessagesResponse = Omit & { - messages: TMessage[]; +export type SharedMessagesResponse = Omit & { + messages: s.TMessage[]; }; export type SharedLinkListParams = Omit & { isPublic?: boolean; }; export type SharedLinksResponse = Omit & { - sharedLinks: TSharedLink[]; + sharedLinks: s.TSharedLink[]; }; // Type for the response from the conversation list API export type SharedLinkListResponse = { - sharedLinks: TSharedLink[]; + sharedLinks: s.TSharedLink[]; pageNumber: string; pageSize: string | number; pages: string | number; @@ -71,4 +71,7 @@ export type AllPromptGroupsFilterRequest = { export type AllPromptGroupsResponse = t.TPromptGroup[]; -export type ConversationTagsResponse = TConversationTag[]; +export type ConversationTagsResponse = s.TConversationTag[]; + +export type VerifyToolAuthParams = { toolId: string }; +export type VerifyToolAuthResponse = { authenticated: boolean; message?: string | s.AuthType }; From 9437e953151d7742c39aa9b1a0a83fc71bb3f19e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 1 Nov 2024 18:54:17 -0400 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=91=20fix:=20Access=20Control=20fo?= =?UTF-8?q?r=20Bookmarks=20(UI)=20(#4612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Nav/Bookmarks/BookmarkNav.tsx | 15 +-------------- client/src/components/Nav/Nav.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx index 452a97a7de1..99d435ab701 100644 --- a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx +++ b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx @@ -1,7 +1,5 @@ import { type FC } from 'react'; import { useRecoilValue } from 'recoil'; -import { useLocation } from 'react-router-dom'; -import { TConversation } from 'librechat-data-provider'; import { Menu, MenuButton, MenuItems } from '@headlessui/react'; import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons'; import { BookmarkContext } from '~/Providers/BookmarkContext'; @@ -19,19 +17,8 @@ type BookmarkNavProps = { const BookmarkNav: FC = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => { const localize = useLocalize(); - const location = useLocation(); - const { data } = useGetConversationTags(); - - const activeConvo = useRecoilValue(store.conversationByIndex(0)); - const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation); - - let conversation: TConversation | null | undefined; - if (location.state?.from?.pathname.includes('/chat')) { - conversation = globalConvo; - } else { - conversation = activeConvo; - } + const conversation = useRecoilValue(store.conversationByIndex(0)); return ( diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index ffc2bbadceb..9f5ac601c79 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -176,11 +176,13 @@ const Nav = ({ {isSearchEnabled === true && ( )} - + {hasAccessToBookmarks === true && ( + + )} } /> From 2e519f9b574693d3c202c1b5d3a6cc075a660658 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 4 Nov 2024 12:59:04 -0500 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=A4=96=20feat:=20Custom=20Endpoint=20?= =?UTF-8?q?Agents=20(experimental)=20(#4627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip: first pass, custom endpoint agents * chore: imports * chore: consolidate exports * fix: imports * feat: convert message.content array to strings for legacy format handling (deepseek/groq) * refactor: normalize ollama endpoint name * refactor: update mocking in isDomainAllowed.spec.js * refactor: update deepseekModels in tokens.js and tokens.spec.js --- api/app/clients/prompts/formatMessages.js | 32 ++++++++ api/server/controllers/agents/client.js | 8 ++ api/server/routes/files/multer.js | 2 +- api/server/services/Config/getCustomConfig.js | 19 ++++- api/server/services/Config/index.js | 2 +- .../services/Config/loadConfigEndpoints.js | 2 +- .../services/Config/loadConfigModels.js | 14 +++- .../services/Config/loadConfigModels.spec.js | 74 +++++++++++++++++-- .../services/Endpoints/agents/initialize.js | 57 +++++--------- .../services/Endpoints/custom/initialize.js | 27 ++++--- api/server/services/Files/Audio/STTService.js | 2 +- api/server/services/Files/Audio/TTSService.js | 6 +- .../Files/Audio/getCustomConfigSpeech.js | 2 +- api/server/services/Files/Audio/getVoices.js | 2 +- api/server/services/Files/Audio/index.js | 2 +- api/server/services/isDomainAllowed.js | 2 +- api/server/services/isDomainAllowed.spec.js | 6 +- api/typedefs.js | 23 ++++++ api/utils/tokens.js | 5 ++ api/utils/tokens.spec.js | 10 +++ package-lock.json | 2 +- packages/data-provider/package.json | 2 +- .../data-provider/src/types/assistants.ts | 2 + 23 files changed, 230 insertions(+), 73 deletions(-) diff --git a/api/app/clients/prompts/formatMessages.js b/api/app/clients/prompts/formatMessages.js index 29784d65319..c9839102d39 100644 --- a/api/app/clients/prompts/formatMessages.js +++ b/api/app/clients/prompts/formatMessages.js @@ -217,9 +217,41 @@ const formatAgentMessages = (payload) => { return messages; }; +/** + * Formats an array of messages for LangChain, making sure all content fields are strings + * @param {Array<(HumanMessage|AIMessage|SystemMessage|ToolMessage)>} payload - The array of messages to format. + * @returns {Array<(HumanMessage|AIMessage|SystemMessage|ToolMessage)>} - The array of formatted LangChain messages, including ToolMessages for tool calls. + */ +const formatContentStrings = (payload) => { + const messages = []; + + for (const message of payload) { + if (typeof message.content === 'string') { + continue; + } + + if (!Array.isArray(message.content)) { + continue; + } + + // Reduce text types to a single string, ignore all other types + const content = message.content.reduce((acc, curr) => { + if (curr.type === ContentTypes.TEXT) { + return `${acc}${curr[ContentTypes.TEXT]}\n`; + } + return acc; + }, ''); + + message.content = content.trim(); + } + + return messages; +}; + module.exports = { formatMessage, formatFromLangChain, formatAgentMessages, + formatContentStrings, formatLangChainMessages, }; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index 0534014bec7..c7f832db1e3 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -13,6 +13,7 @@ const { VisionModes, openAISchema, EModelEndpoint, + KnownEndpoints, anthropicSchema, bedrockOutputParser, removeNullishValues, @@ -25,6 +26,7 @@ const { const { formatMessage, formatAgentMessages, + formatContentStrings, createContextHandlers, } = require('~/app/clients/prompts'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); @@ -44,6 +46,8 @@ const providerParsers = { [EModelEndpoint.bedrock]: bedrockOutputParser, }; +const legacyContentEndpoints = new Set([KnownEndpoints.groq, KnownEndpoints.deepseek]); + class AgentClient extends BaseClient { constructor(options = {}) { super(null, options); @@ -74,6 +78,7 @@ class AgentClient extends BaseClient { this.collectedUsage = collectedUsage; /** @type {ArtifactPromises} */ this.artifactPromises = artifactPromises; + /** @type {AgentClientOptions} */ this.options = Object.assign({ endpoint: options.endpoint }, clientOptions); } @@ -478,6 +483,9 @@ class AgentClient extends BaseClient { this.run = run; const messages = formatAgentMessages(payload); + if (legacyContentEndpoints.has(this.options.agent.endpoint)) { + formatContentStrings(messages); + } await run.processStream({ messages }, config, { [Callback.TOOL_ERROR]: (graph, error, toolId) => { logger.error( diff --git a/api/server/routes/files/multer.js b/api/server/routes/files/multer.js index 76c4d50c3e8..4f0d38f22f8 100644 --- a/api/server/routes/files/multer.js +++ b/api/server/routes/files/multer.js @@ -3,7 +3,7 @@ const path = require('path'); const crypto = require('crypto'); const multer = require('multer'); const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider'); -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); const storage = multer.diskStorage({ destination: function (req, file, cb) { diff --git a/api/server/services/Config/getCustomConfig.js b/api/server/services/Config/getCustomConfig.js index a479ca37b71..6704475b5c7 100644 --- a/api/server/services/Config/getCustomConfig.js +++ b/api/server/services/Config/getCustomConfig.js @@ -1,4 +1,4 @@ -const { CacheKeys } = require('librechat-data-provider'); +const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); const loadCustomConfig = require('./loadCustomConfig'); const getLogStores = require('~/cache/getLogStores'); @@ -22,4 +22,19 @@ async function getCustomConfig() { return customConfig; } -module.exports = getCustomConfig; +/** + * + * @param {string | EModelEndpoint} endpoint + */ +const getCustomEndpointConfig = async (endpoint) => { + const customConfig = await getCustomConfig(); + if (!customConfig) { + throw new Error(`Config not found for the ${endpoint} custom endpoint.`); + } + + const { endpoints = {} } = customConfig; + const customEndpoints = endpoints[EModelEndpoint.custom] ?? []; + return customEndpoints.find((endpointConfig) => endpointConfig.name === endpoint); +}; + +module.exports = { getCustomConfig, getCustomEndpointConfig }; diff --git a/api/server/services/Config/index.js b/api/server/services/Config/index.js index 2e8ccb1433c..6dba63e8ee4 100644 --- a/api/server/services/Config/index.js +++ b/api/server/services/Config/index.js @@ -10,12 +10,12 @@ const loadDefaultEndpointsConfig = require('./loadDefaultEConfig'); module.exports = { config, - getCustomConfig, loadCustomConfig, loadConfigModels, loadDefaultModels, loadOverrideConfig, loadAsyncEndpoints, + ...getCustomConfig, loadConfigEndpoints, loadDefaultEndpointsConfig, }; diff --git a/api/server/services/Config/loadConfigEndpoints.js b/api/server/services/Config/loadConfigEndpoints.js index 203a461b00e..25da3b4271b 100644 --- a/api/server/services/Config/loadConfigEndpoints.js +++ b/api/server/services/Config/loadConfigEndpoints.js @@ -1,6 +1,6 @@ const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'); +const { getCustomConfig } = require('./getCustomConfig'); const { isUserProvided } = require('~/server/utils'); -const getCustomConfig = require('./getCustomConfig'); /** * Load config endpoints from the cached configuration object diff --git a/api/server/services/Config/loadConfigModels.js b/api/server/services/Config/loadConfigModels.js index cb0b800d740..5aca002b8b3 100644 --- a/api/server/services/Config/loadConfigModels.js +++ b/api/server/services/Config/loadConfigModels.js @@ -1,7 +1,16 @@ +const { Providers } = require('@librechat/agents'); const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'); const { fetchModels } = require('~/server/services/ModelService'); +const { getCustomConfig } = require('./getCustomConfig'); const { isUserProvided } = require('~/server/utils'); -const getCustomConfig = require('./getCustomConfig'); + +/** + * @param {string} name + * @returns {string} + */ +function normalizeEndpointName(name = '') { + return name.toLowerCase() === Providers.OLLAMA ? Providers.OLLAMA : name; +} /** * Load config endpoints from the cached configuration object @@ -61,7 +70,8 @@ async function loadConfigModels(req) { for (let i = 0; i < customEndpoints.length; i++) { const endpoint = customEndpoints[i]; - const { models, name, baseURL, apiKey } = endpoint; + const { models, name: configName, baseURL, apiKey } = endpoint; + const name = normalizeEndpointName(configName); endpointsMap[name] = endpoint; const API_KEY = extractEnvVariable(apiKey); diff --git a/api/server/services/Config/loadConfigModels.spec.js b/api/server/services/Config/loadConfigModels.spec.js index 828ecd881ed..e7199c59deb 100644 --- a/api/server/services/Config/loadConfigModels.spec.js +++ b/api/server/services/Config/loadConfigModels.spec.js @@ -1,6 +1,6 @@ const { fetchModels } = require('~/server/services/ModelService'); +const { getCustomConfig } = require('./getCustomConfig'); const loadConfigModels = require('./loadConfigModels'); -const getCustomConfig = require('./getCustomConfig'); jest.mock('~/server/services/ModelService'); jest.mock('./getCustomConfig'); @@ -253,13 +253,13 @@ describe('loadConfigModels', () => { }), ); - // For groq and Ollama, since the apiKey is "user_provided", models should not be fetched + // For groq and ollama, since the apiKey is "user_provided", models should not be fetched // Depending on your implementation's behavior regarding "default" models without fetching, // you may need to adjust the following assertions: expect(result.groq).toBe(exampleConfig.endpoints.custom[2].models.default); - expect(result.Ollama).toBe(exampleConfig.endpoints.custom[3].models.default); + expect(result.ollama).toBe(exampleConfig.endpoints.custom[3].models.default); - // Verifying fetchModels was not called for groq and Ollama + // Verifying fetchModels was not called for groq and ollama expect(fetchModels).not.toHaveBeenCalledWith( expect.objectContaining({ name: 'groq', @@ -267,7 +267,7 @@ describe('loadConfigModels', () => { ); expect(fetchModels).not.toHaveBeenCalledWith( expect.objectContaining({ - name: 'Ollama', + name: 'ollama', }), ); }); @@ -335,4 +335,68 @@ describe('loadConfigModels', () => { expect(result.FalsyFetchModel).toEqual(['defaultModel1', 'defaultModel2']); }); + + it('normalizes Ollama endpoint name to lowercase', async () => { + const testCases = [ + { + name: 'Ollama', + apiKey: 'user_provided', + baseURL: 'http://localhost:11434/v1/', + models: { + default: ['mistral', 'llama2'], + fetch: false, + }, + }, + { + name: 'OLLAMA', + apiKey: 'user_provided', + baseURL: 'http://localhost:11434/v1/', + models: { + default: ['mixtral', 'codellama'], + fetch: false, + }, + }, + { + name: 'OLLaMA', + apiKey: 'user_provided', + baseURL: 'http://localhost:11434/v1/', + models: { + default: ['phi', 'neural-chat'], + fetch: false, + }, + }, + ]; + + getCustomConfig.mockResolvedValue({ + endpoints: { + custom: testCases, + }, + }); + + const result = await loadConfigModels(mockRequest); + + // All variations of "Ollama" should be normalized to lowercase "ollama" + // and the last config in the array should override previous ones + expect(result.Ollama).toBeUndefined(); + expect(result.OLLAMA).toBeUndefined(); + expect(result.OLLaMA).toBeUndefined(); + expect(result.ollama).toEqual(['phi', 'neural-chat']); + + // Verify fetchModels was not called since these are user_provided + expect(fetchModels).not.toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Ollama', + }), + ); + expect(fetchModels).not.toHaveBeenCalledWith( + expect.objectContaining({ + name: 'OLLAMA', + }), + ); + expect(fetchModels).not.toHaveBeenCalledWith( + expect.objectContaining({ + name: 'OLLaMA', + }), + ); + }); }); diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 190972ac51f..93bb8376953 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -1,16 +1,3 @@ -// const { -// ErrorTypes, -// EModelEndpoint, -// resolveHeaders, -// mapModelToAzureConfig, -// } = require('librechat-data-provider'); -// const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); -// const { isEnabled, isUserProvided } = require('~/server/utils'); -// const { getAzureCredentials } = require('~/utils'); -// const { OpenAIClient } = require('~/app'); - -const { z } = require('zod'); -const { tool } = require('@langchain/core/tools'); const { createContentAggregator, Providers } = require('@librechat/agents'); const { EModelEndpoint, @@ -25,30 +12,11 @@ const initAnthropic = require('~/server/services/Endpoints/anthropic/initialize' const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options'); const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); const initCustom = require('~/server/services/Endpoints/custom/initialize'); +const { getCustomEndpointConfig } = require('~/server/services/Config'); const { loadAgentTools } = require('~/server/services/ToolService'); const AgentClient = require('~/server/controllers/agents/client'); const { getModelMaxTokens } = require('~/utils'); -/* For testing errors */ -const _getWeather = tool( - async ({ location }) => { - if (location === 'SAN FRANCISCO') { - return 'It\'s 60 degrees and foggy'; - } else if (location.toLowerCase() === 'san francisco') { - throw new Error('Input queries must be all capitals'); - } else { - throw new Error('Invalid input.'); - } - }, - { - name: 'get_weather', - description: 'Call to get the current weather', - schema: z.object({ - location: z.string(), - }), - }, -); - const providerConfigMap = { [EModelEndpoint.openAI]: initOpenAI, [EModelEndpoint.azureOpenAI]: initOpenAI, @@ -85,18 +53,25 @@ const initializeClient = async ({ req, res, endpointOption }) => { if (!agent) { throw new Error('Agent not found'); } + const { tools, toolMap } = await loadAgentTools({ req, tools: agent.tools, agent_id: agent.id, tool_resources: agent.tool_resources, - // openAIApiKey: process.env.OPENAI_API_KEY, }); + const provider = agent.provider; let modelOptions = { model: agent.model }; - let getOptions = providerConfigMap[agent.provider]; + let getOptions = providerConfigMap[provider]; if (!getOptions) { - throw new Error(`Provider ${agent.provider} not supported`); + const customEndpointConfig = await getCustomEndpointConfig(provider); + if (!customEndpointConfig) { + throw new Error(`Provider ${provider} not supported`); + } + getOptions = initCustom; + agent.provider = Providers.OPENAI; + agent.endpoint = provider.toLowerCase(); } // TODO: pass-in override settings that are specific to current run @@ -106,10 +81,14 @@ const initializeClient = async ({ req, res, endpointOption }) => { res, endpointOption, optionsOnly: true, - overrideEndpoint: agent.provider, + overrideEndpoint: provider, overrideModel: agent.model, }); + modelOptions = Object.assign(modelOptions, options.llmConfig); + if (options.configOptions) { + modelOptions.configuration = options.configOptions; + } const sender = getResponseSender({ ...endpointOption, @@ -128,11 +107,11 @@ const initializeClient = async ({ req, res, endpointOption }) => { collectedUsage, artifactPromises, endpoint: EModelEndpoint.agents, - configOptions: options.configOptions, attachments: endpointOption.attachments, maxContextTokens: agent.max_context_tokens ?? - getModelMaxTokens(modelOptions.model, providerEndpointMap[agent.provider]), + getModelMaxTokens(modelOptions.model, providerEndpointMap[provider]) ?? + 4000, }); return { client }; }; diff --git a/api/server/services/Endpoints/custom/initialize.js b/api/server/services/Endpoints/custom/initialize.js index a2f410cdf1d..2390ea368dc 100644 --- a/api/server/services/Endpoints/custom/initialize.js +++ b/api/server/services/Endpoints/custom/initialize.js @@ -2,17 +2,17 @@ const { CacheKeys, ErrorTypes, envVarRegex, - EModelEndpoint, FetchTokenConfig, extractEnvVariable, } = require('librechat-data-provider'); +const { Providers } = require('@librechat/agents'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); +const { getCustomEndpointConfig } = require('~/server/services/Config'); const { fetchModels } = require('~/server/services/ModelService'); const getLogStores = require('~/cache/getLogStores'); const { isUserProvided } = require('~/server/utils'); const { OpenAIClient } = require('~/app'); -const { Providers } = require('@librechat/agents'); const { PROXY } = process.env; @@ -20,15 +20,11 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid const { key: expiresAt } = req.body; const endpoint = overrideEndpoint ?? req.body.endpoint; - const customConfig = await getCustomConfig(); - if (!customConfig) { + const endpointConfig = await getCustomEndpointConfig(endpoint); + if (!endpointConfig) { throw new Error(`Config not found for the ${endpoint} custom endpoint.`); } - const { endpoints = {} } = customConfig; - const customEndpoints = endpoints[EModelEndpoint.custom] ?? []; - const endpointConfig = customEndpoints.find((endpointConfig) => endpointConfig.name === endpoint); - const CUSTOM_API_KEY = extractEnvVariable(endpointConfig.apiKey); const CUSTOM_BASE_URL = extractEnvVariable(endpointConfig.baseURL); @@ -138,10 +134,21 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid if (optionsOnly) { const modelOptions = endpointOption.model_parameters; - if (endpoint === Providers.OLLAMA && clientOptions.reverseProxyUrl) { + if (endpoint !== Providers.OLLAMA) { + const requestOptions = Object.assign( + { + modelOptions, + }, + clientOptions, + ); + return getLLMConfig(apiKey, requestOptions); + } + + if (clientOptions.reverseProxyUrl) { modelOptions.baseUrl = clientOptions.reverseProxyUrl.split('/v1')[0]; delete clientOptions.reverseProxyUrl; } + return { llmConfig: modelOptions, }; diff --git a/api/server/services/Files/Audio/STTService.js b/api/server/services/Files/Audio/STTService.js index bf2b00c9d0b..03f6b286104 100644 --- a/api/server/services/Files/Audio/STTService.js +++ b/api/server/services/Files/Audio/STTService.js @@ -2,7 +2,7 @@ const axios = require('axios'); const FormData = require('form-data'); const { Readable } = require('stream'); const { extractEnvVariable, STTProviders } = require('librechat-data-provider'); -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); const { genAzureEndpoint } = require('~/utils'); const { logger } = require('~/config'); diff --git a/api/server/services/Files/Audio/TTSService.js b/api/server/services/Files/Audio/TTSService.js index cf99633cb7e..d9b1e1d44fd 100644 --- a/api/server/services/Files/Audio/TTSService.js +++ b/api/server/services/Files/Audio/TTSService.js @@ -1,9 +1,9 @@ const axios = require('axios'); const { extractEnvVariable, TTSProviders } = require('librechat-data-provider'); -const { logger } = require('~/config'); -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); -const { genAzureEndpoint } = require('~/utils'); const { getRandomVoiceId, createChunkProcessor, splitTextIntoChunks } = require('./streamAudio'); +const { getCustomConfig } = require('~/server/services/Config'); +const { genAzureEndpoint } = require('~/utils'); +const { logger } = require('~/config'); /** * Service class for handling Text-to-Speech (TTS) operations. diff --git a/api/server/services/Files/Audio/getCustomConfigSpeech.js b/api/server/services/Files/Audio/getCustomConfigSpeech.js index a0fd23e3fad..36f97bc4914 100644 --- a/api/server/services/Files/Audio/getCustomConfigSpeech.js +++ b/api/server/services/Files/Audio/getCustomConfigSpeech.js @@ -1,4 +1,4 @@ -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); const { logger } = require('~/config'); /** diff --git a/api/server/services/Files/Audio/getVoices.js b/api/server/services/Files/Audio/getVoices.js index 07c050ab33e..24612d85e29 100644 --- a/api/server/services/Files/Audio/getVoices.js +++ b/api/server/services/Files/Audio/getVoices.js @@ -1,5 +1,5 @@ const { TTSProviders } = require('librechat-data-provider'); -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); const { getProvider } = require('./TTSService'); /** diff --git a/api/server/services/Files/Audio/index.js b/api/server/services/Files/Audio/index.js index b698e9da87a..4378391e276 100644 --- a/api/server/services/Files/Audio/index.js +++ b/api/server/services/Files/Audio/index.js @@ -1,7 +1,7 @@ -const getVoices = require('./getVoices'); const getCustomConfigSpeech = require('./getCustomConfigSpeech'); const TTSService = require('./TTSService'); const STTService = require('./STTService'); +const getVoices = require('./getVoices'); module.exports = { getVoices, diff --git a/api/server/services/isDomainAllowed.js b/api/server/services/isDomainAllowed.js index 48e0747511e..2eb6c0db247 100644 --- a/api/server/services/isDomainAllowed.js +++ b/api/server/services/isDomainAllowed.js @@ -1,4 +1,4 @@ -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); async function isDomainAllowed(email) { if (!email) { diff --git a/api/server/services/isDomainAllowed.spec.js b/api/server/services/isDomainAllowed.spec.js index b1cf03a5672..216b7d58113 100644 --- a/api/server/services/isDomainAllowed.spec.js +++ b/api/server/services/isDomainAllowed.spec.js @@ -1,7 +1,9 @@ -const getCustomConfig = require('~/server/services/Config/getCustomConfig'); +const { getCustomConfig } = require('~/server/services/Config'); const isDomainAllowed = require('./isDomainAllowed'); -jest.mock('~/server/services/Config/getCustomConfig', () => jest.fn()); +jest.mock('~/server/services/Config', () => ({ + getCustomConfig: jest.fn(), +})); describe('isDomainAllowed', () => { afterEach(() => { diff --git a/api/typedefs.js b/api/typedefs.js index 8f0e9fef252..371fe944477 100644 --- a/api/typedefs.js +++ b/api/typedefs.js @@ -942,6 +942,29 @@ * @memberof typedefs */ +/** + * @typedef {Object} AgentClientOptions + * @property {Agent} agent - The agent configuration object + * @property {string} endpoint - The endpoint identifier for the agent + * @property {Object} req - The request object + * @property {string} [name] - The username + * @property {string} [modelLabel] - The label for the model being used + * @property {number} [maxContextTokens] - Maximum number of tokens allowed in context + * @property {Object} [endpointTokenConfig] - Token configuration for the endpoint + * @property {boolean} [resendFiles] - Whether to resend files + * @property {string} [imageDetail] - Detail level for image processing + * @property {Object} [spec] - Specification object + * @property {Promise} [attachments] - Promise resolving to file attachments + * @property {Object} [headers] - Additional headers for requests + * @property {string} [proxy] - Proxy configuration + * @property {Object} [tools] - Available tools for the agent + * @property {Object} [toolMap] - Mapping of tool configurations + * @property {Object} [eventHandlers] - Custom event handlers + * @property {Object} [addParams] - Additional parameters to add to requests + * @property {string[]} [dropParams] - Parameters to remove from requests + * @memberof typedefs + */ + /** * @exports ImportBatchBuilder * @typedef {import('./server/utils/import/importBatchBuilder.js').ImportBatchBuilder} ImportBatchBuilder diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 11a29e31f24..92833035af6 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -76,6 +76,10 @@ const anthropicModels = { 'claude-3.5-sonnet-latest': 200000, }; +const deepseekModels = { + deepseek: 127500, +}; + const metaModels = { llama3: 8000, llama2: 4000, @@ -117,6 +121,7 @@ const bedrockModels = { ...mistralModels, ...cohereModels, ...ollamaModels, + ...deepseekModels, ...metaModels, ...ai21Models, ...amazonModels, diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index 4912d3c80c0..cacf72cb4a8 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -357,6 +357,11 @@ describe('Meta Models Tests', () => { expect(getModelMaxTokens('meta/llama3')).toBe(8000); expect(getModelMaxTokens('meta/llama2')).toBe(4000); }); + + test('should match Deepseek model variations', () => { + expect(getModelMaxTokens('deepseek-chat')).toBe(127500); + expect(getModelMaxTokens('deepseek-coder')).toBe(127500); + }); }); describe('matchModelName', () => { @@ -383,6 +388,11 @@ describe('Meta Models Tests', () => { expect(matchModelName('llama3', EModelEndpoint.bedrock)).toBe('llama3'); expect(matchModelName('llama3.1:8b', EModelEndpoint.bedrock)).toBe('llama3.1:8b'); }); + + test('should match Deepseek model variations', () => { + expect(matchModelName('deepseek-chat')).toBe('deepseek'); + expect(matchModelName('deepseek-coder')).toBe('deepseek'); + }); }); describe('processModelData with Meta models', () => { diff --git a/package-lock.json b/package-lock.json index f218774e768..b72673368d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36283,7 +36283,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.51", + "version": "0.7.52", "license": "ISC", "dependencies": { "@types/js-yaml": "^4.0.9", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index 0170ec74021..b68c5ce1990 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.51", + "version": "0.7.52", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/types/assistants.ts b/packages/data-provider/src/types/assistants.ts index fde34fd13df..6e21c0b5b27 100644 --- a/packages/data-provider/src/types/assistants.ts +++ b/packages/data-provider/src/types/assistants.ts @@ -184,6 +184,8 @@ export type Agent = { id: string; name: string | null; author?: string | null; + /** The original custom endpoint name, lowercased */ + endpoint?: string | null; authorName?: string | null; description: string | null; created_at: number; From fc41032923d494a3ba9e9dc55c2c39f4c9e041d7 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 4 Nov 2024 15:10:24 -0500 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=A4=96=20feat:=20Claude=203.5=20Haiku?= =?UTF-8?q?=20(#4629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- api/app/clients/AnthropicClient.js | 7 ++++--- api/models/tx.js | 4 ++++ api/models/tx.spec.js | 16 ++++++++++++++++ api/utils/tokens.js | 2 ++ packages/data-provider/src/config.ts | 1 + 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index f77bb75af27..ff9edb2fc58 100644 --- a/.env.example +++ b/.env.example @@ -82,7 +82,7 @@ PROXY= #============# ANTHROPIC_API_KEY=user_provided -# ANTHROPIC_MODELS=claude-3-5-sonnet-20241022,claude-3-5-sonnet-latest,claude-3-5-sonnet-20240620,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307,claude-2.1,claude-2,claude-1.2,claude-1,claude-1-100k,claude-instant-1,claude-instant-1-100k +# ANTHROPIC_MODELS=claude-3-5-haiku-20241022,claude-3-5-sonnet-20241022,claude-3-5-sonnet-latest,claude-3-5-sonnet-20240620,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307,claude-2.1,claude-2,claude-1.2,claude-1,claude-1-100k,claude-instant-1,claude-instant-1-100k # ANTHROPIC_REVERSE_PROXY= #============# diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index ff0dc4de5b7..6e9d4accc20 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -98,8 +98,8 @@ class AnthropicClient extends BaseClient { ); const modelMatch = matchModelName(this.modelOptions.model, EModelEndpoint.anthropic); - this.isClaude3 = modelMatch.startsWith('claude-3'); - this.isLegacyOutput = !modelMatch.startsWith('claude-3-5-sonnet'); + this.isClaude3 = modelMatch.includes('claude-3'); + this.isLegacyOutput = !modelMatch.includes('claude-3-5-sonnet'); this.supportsCacheControl = this.options.promptCache && this.checkPromptCacheSupport(modelMatch); @@ -634,7 +634,7 @@ class AnthropicClient extends BaseClient { ); }; - if (this.modelOptions.model.startsWith('claude-3')) { + if (this.modelOptions.model.includes('claude-3')) { await buildMessagesPayload(); processTokens(); return { @@ -687,6 +687,7 @@ class AnthropicClient extends BaseClient { } if ( modelMatch === 'claude-3-5-sonnet' || + modelMatch === 'claude-3-5-haiku' || modelMatch === 'claude-3-haiku' || modelMatch === 'claude-3-opus' ) { diff --git a/api/models/tx.js b/api/models/tx.js index 1b1501bd282..c9a88b6d9db 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -56,6 +56,8 @@ const tokenValues = Object.assign( 'claude-3-sonnet': { prompt: 3, completion: 15 }, 'claude-3-5-sonnet': { prompt: 3, completion: 15 }, 'claude-3.5-sonnet': { prompt: 3, completion: 15 }, + 'claude-3-5-haiku': { prompt: 1, completion: 5 }, + 'claude-3.5-haiku': { prompt: 1, completion: 5 }, 'claude-3-haiku': { prompt: 0.25, completion: 1.25 }, 'claude-2.1': { prompt: 8, completion: 24 }, 'claude-2': { prompt: 8, completion: 24 }, @@ -81,6 +83,8 @@ const tokenValues = Object.assign( const cacheTokenValues = { 'claude-3.5-sonnet': { write: 3.75, read: 0.3 }, 'claude-3-5-sonnet': { write: 3.75, read: 0.3 }, + 'claude-3.5-haiku': { write: 1.25, read: 0.1 }, + 'claude-3-5-haiku': { write: 1.25, read: 0.1 }, 'claude-3-haiku': { write: 0.3, read: 0.03 }, }; diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index 730c33a05d7..d9ffafcb1e0 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -92,6 +92,20 @@ describe('getValueKey', () => { expect(getValueKey('claude-3.5-sonnet-turbo')).toBe('claude-3.5-sonnet'); expect(getValueKey('claude-3.5-sonnet-0125')).toBe('claude-3.5-sonnet'); }); + + it('should return "claude-3-5-haiku" for model type of "claude-3-5-haiku-"', () => { + expect(getValueKey('claude-3-5-haiku-20240620')).toBe('claude-3-5-haiku'); + expect(getValueKey('anthropic/claude-3-5-haiku')).toBe('claude-3-5-haiku'); + expect(getValueKey('claude-3-5-haiku-turbo')).toBe('claude-3-5-haiku'); + expect(getValueKey('claude-3-5-haiku-0125')).toBe('claude-3-5-haiku'); + }); + + it('should return "claude-3.5-haiku" for model type of "claude-3.5-haiku-"', () => { + expect(getValueKey('claude-3.5-haiku-20240620')).toBe('claude-3.5-haiku'); + expect(getValueKey('anthropic/claude-3.5-haiku')).toBe('claude-3.5-haiku'); + expect(getValueKey('claude-3.5-haiku-turbo')).toBe('claude-3.5-haiku'); + expect(getValueKey('claude-3.5-haiku-0125')).toBe('claude-3.5-haiku'); + }); }); describe('getMultiplier', () => { @@ -248,6 +262,8 @@ describe('getCacheMultiplier', () => { it('should return the correct cache multiplier for a given valueKey and cacheType', () => { expect(getCacheMultiplier({ valueKey: 'claude-3-5-sonnet', cacheType: 'write' })).toBe(3.75); expect(getCacheMultiplier({ valueKey: 'claude-3-5-sonnet', cacheType: 'read' })).toBe(0.3); + expect(getCacheMultiplier({ valueKey: 'claude-3-5-haiku', cacheType: 'write' })).toBe(1.25); + expect(getCacheMultiplier({ valueKey: 'claude-3-5-haiku', cacheType: 'read' })).toBe(0.1); expect(getCacheMultiplier({ valueKey: 'claude-3-haiku', cacheType: 'write' })).toBe(0.3); expect(getCacheMultiplier({ valueKey: 'claude-3-haiku', cacheType: 'read' })).toBe(0.03); }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 92833035af6..4bf66508f87 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -70,6 +70,8 @@ const anthropicModels = { 'claude-3-haiku': 200000, 'claude-3-sonnet': 200000, 'claude-3-opus': 200000, + 'claude-3.5-haiku': 200000, + 'claude-3-5-haiku': 200000, 'claude-3-5-sonnet': 200000, 'claude-3.5-sonnet': 200000, 'claude-3-5-sonnet-latest': 200000, diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 81ff5eca418..5f503203313 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -608,6 +608,7 @@ const sharedOpenAIModels = [ ]; const sharedAnthropicModels = [ + 'claude-3-5-haiku-20241022', 'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet-20240620', 'claude-3-5-sonnet-latest', From 3428c3c647c4378d9942e419ffdf27762e947dca Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 4 Nov 2024 16:27:54 -0500 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9D=8E=20feat:=20Known=20Endpoint,=20xAI?= =?UTF-8?q?=20(#4632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Known Endpoint, xAI * chore: update librechat-data-provider version to 0.7.53 * ci: name property removal * feat: add XAI_API_KEY to example environment variables --- .env.example | 1 + api/app/clients/ChatGPTClient.js | 1 - api/app/clients/OpenAIClient.js | 1 - api/app/clients/PluginsClient.js | 1 - api/app/clients/specs/OpenAIClient.test.js | 8 +++++--- client/public/assets/xai.svg | 1 + .../components/Chat/Menus/Endpoints/UnknownIcon.tsx | 13 ++++++++++--- package-lock.json | 2 +- packages/data-provider/package.json | 2 +- packages/data-provider/src/config.ts | 4 +++- 10 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 client/public/assets/xai.svg diff --git a/.env.example b/.env.example index ff9edb2fc58..b0a0f20b9fc 100644 --- a/.env.example +++ b/.env.example @@ -76,6 +76,7 @@ PROXY= # SHUTTLEAI_API_KEY= # TOGETHERAI_API_KEY= # UNIFY_API_KEY= +# XAI_API_KEY= #============# # Anthropic # diff --git a/api/app/clients/ChatGPTClient.js b/api/app/clients/ChatGPTClient.js index 104e9e5ac3f..22f7cf31385 100644 --- a/api/app/clients/ChatGPTClient.js +++ b/api/app/clients/ChatGPTClient.js @@ -678,7 +678,6 @@ ${botMessage.message} const instructionsPayload = { role: 'system', - name: 'instructions', content: promptPrefix, }; diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index bc773db1724..848547314b8 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -553,7 +553,6 @@ class OpenAIClient extends BaseClient { promptPrefix = `Instructions:\n${promptPrefix.trim()}`; instructions = { role: 'system', - name: 'instructions', content: promptPrefix, }; diff --git a/api/app/clients/PluginsClient.js b/api/app/clients/PluginsClient.js index da7988acaf6..4eb258df203 100644 --- a/api/app/clients/PluginsClient.js +++ b/api/app/clients/PluginsClient.js @@ -458,7 +458,6 @@ class PluginsClient extends OpenAIClient { const instructionsPayload = { role: 'system', - name: 'instructions', content: promptPrefix, }; diff --git a/api/app/clients/specs/OpenAIClient.test.js b/api/app/clients/specs/OpenAIClient.test.js index 0725efd9d83..556cee745ed 100644 --- a/api/app/clients/specs/OpenAIClient.test.js +++ b/api/app/clients/specs/OpenAIClient.test.js @@ -446,7 +446,7 @@ describe('OpenAIClient', () => { promptPrefix: 'Test Prefix', }); expect(result).toHaveProperty('prompt'); - const instructions = result.prompt.find((item) => item.name === 'instructions'); + const instructions = result.prompt.find((item) => item.content.includes('Test Prefix')); expect(instructions).toBeDefined(); expect(instructions.content).toContain('Test Prefix'); }); @@ -476,7 +476,9 @@ describe('OpenAIClient', () => { const result = await client.buildMessages(messages, parentMessageId, { isChatCompletion: true, }); - const instructions = result.prompt.find((item) => item.name === 'instructions'); + const instructions = result.prompt.find((item) => + item.content.includes('Test Prefix from options'), + ); expect(instructions.content).toContain('Test Prefix from options'); }); @@ -484,7 +486,7 @@ describe('OpenAIClient', () => { const result = await client.buildMessages(messages, parentMessageId, { isChatCompletion: true, }); - const instructions = result.prompt.find((item) => item.name === 'instructions'); + const instructions = result.prompt.find((item) => item.content.includes('Test Prefix')); expect(instructions).toBeUndefined(); }); diff --git a/client/public/assets/xai.svg b/client/public/assets/xai.svg new file mode 100644 index 00000000000..2aca45ed4f0 --- /dev/null +++ b/client/public/assets/xai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx b/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx index 21574abbefa..a0a9ef220f9 100644 --- a/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx +++ b/client/src/components/Chat/Menus/Endpoints/UnknownIcon.tsx @@ -1,6 +1,7 @@ import { EModelEndpoint, KnownEndpoints } from 'librechat-data-provider'; import { CustomMinimalIcon } from '~/components/svg'; import { IconContext } from '~/common'; +import { cn } from '~/utils'; const knownEndpointAssets = { [KnownEndpoints.anyscale]: '/assets/anyscale.png', @@ -18,12 +19,19 @@ const knownEndpointAssets = { [KnownEndpoints.shuttleai]: '/assets/shuttleai.png', [KnownEndpoints['together.ai']]: '/assets/together.png', [KnownEndpoints.unify]: '/assets/unify.webp', + [KnownEndpoints.xai]: '/assets/xai.svg', }; const knownEndpointClasses = { [KnownEndpoints.cohere]: { [IconContext.landing]: 'p-2', }, + [KnownEndpoints.xai]: { + [IconContext.landing]: 'p-2', + [IconContext.menuItem]: 'bg-white', + [IconContext.message]: 'bg-white', + [IconContext.nav]: 'bg-white', + }, }; const getKnownClass = ({ @@ -39,10 +47,10 @@ const getKnownClass = ({ return className; } - const match = knownEndpointClasses[currentEndpoint]?.[context]; + const match = knownEndpointClasses[currentEndpoint]?.[context] ?? ''; const defaultClass = context === IconContext.landing ? '' : className; - return match ?? defaultClass; + return cn(match, defaultClass); }; export default function UnknownIcon({ @@ -61,7 +69,6 @@ export default function UnknownIcon({ return ; } - console.log('UnknownIcon', endpoint); const currentEndpoint = endpoint.toLowerCase(); if (iconURL) { diff --git a/package-lock.json b/package-lock.json index b72673368d0..74536ed1bb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36283,7 +36283,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.52", + "version": "0.7.53", "license": "ISC", "dependencies": { "@types/js-yaml": "^4.0.9", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index b68c5ce1990..bfc6776dae0 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.52", + "version": "0.7.53", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 5f503203313..04f3faf0771 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -550,6 +550,7 @@ export enum KnownEndpoints { shuttleai = 'shuttleai', 'together.ai' = 'together.ai', unify = 'unify', + xai = 'xai', } export enum FetchTokenConfig { @@ -584,7 +585,8 @@ export const alternateName = { [EModelEndpoint.anthropic]: 'Anthropic', [EModelEndpoint.custom]: 'Custom', [EModelEndpoint.bedrock]: 'AWS Bedrock', - ollama: 'Ollama', + [KnownEndpoints.ollama]: 'Ollama', + [KnownEndpoints.xai]: 'xAI', }; const sharedOpenAIModels = [