| | const { nanoid } = require('nanoid'); |
| | const { EnvVar } = require('@librechat/agents'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const { checkAccess, loadWebSearchAuth } = require('@librechat/api'); |
| | const { |
| | Tools, |
| | AuthType, |
| | Permissions, |
| | ToolCallTypes, |
| | PermissionTypes, |
| | } = require('librechat-data-provider'); |
| | const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process'); |
| | const { processCodeOutput } = require('~/server/services/Files/Code/process'); |
| | const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall'); |
| | const { loadAuthValues } = require('~/server/services/Tools/credentials'); |
| | const { loadTools } = require('~/app/clients/tools/util'); |
| | const { getRoleByName } = require('~/models/Role'); |
| | const { getMessage } = require('~/models/Message'); |
| |
|
| | const fieldsMap = { |
| | [Tools.execute_code]: [EnvVar.CODE_API_KEY], |
| | }; |
| |
|
| | const toolAccessPermType = { |
| | [Tools.execute_code]: PermissionTypes.RUN_CODE, |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const verifyWebSearchAuth = async (req, res) => { |
| | try { |
| | const appConfig = req.config; |
| | const userId = req.user.id; |
| | |
| | const webSearchConfig = appConfig?.webSearch || {}; |
| | const result = await loadWebSearchAuth({ |
| | userId, |
| | loadAuthValues, |
| | webSearchConfig, |
| | throwError: false, |
| | }); |
| |
|
| | return res.status(200).json({ |
| | authenticated: result.authenticated, |
| | authTypes: result.authTypes, |
| | }); |
| | } catch (error) { |
| | console.error('Error in verifyWebSearchAuth:', error); |
| | return res.status(500).json({ message: error.message }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const verifyToolAuth = async (req, res) => { |
| | try { |
| | const { toolId } = req.params; |
| | if (toolId === Tools.web_search) { |
| | return await verifyWebSearchAuth(req, res); |
| | } |
| | 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, |
| | throwError: false, |
| | }); |
| | } catch (error) { |
| | logger.error('Error loading auth values', 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 }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const callTool = async (req, res) => { |
| | try { |
| | const appConfig = req.config; |
| | const { toolId = '' } = req.params; |
| | if (!fieldsMap[toolId]) { |
| | logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`); |
| | res.status(404).json({ message: 'Tool not found' }); |
| | return; |
| | } |
| |
|
| | const { partIndex, blockIndex, messageId, conversationId, ...args } = req.body; |
| | if (!messageId) { |
| | logger.warn(`[${toolId}/call] User ${req.user.id} attempted call without message ID`); |
| | res.status(400).json({ message: 'Message ID required' }); |
| | return; |
| | } |
| |
|
| | const message = await getMessage({ user: req.user.id, messageId }); |
| | if (!message) { |
| | logger.debug(`[${toolId}/call] User ${req.user.id} attempted call with invalid message ID`); |
| | res.status(404).json({ message: 'Message not found' }); |
| | return; |
| | } |
| | logger.debug(`[${toolId}/call] User: ${req.user.id}`); |
| | let hasAccess = true; |
| | if (toolAccessPermType[toolId]) { |
| | hasAccess = await checkAccess({ |
| | user: req.user, |
| | permissionType: toolAccessPermType[toolId], |
| | permissions: [Permissions.USE], |
| | getRoleByName, |
| | }); |
| | } |
| | if (!hasAccess) { |
| | logger.warn( |
| | `[${toolAccessPermType[toolId]}] Forbidden: Insufficient permissions for User ${req.user.id}: ${Permissions.USE}`, |
| | ); |
| | return res.status(403).json({ message: 'Forbidden: Insufficient permissions' }); |
| | } |
| | const { loadedTools } = await loadTools({ |
| | user: req.user.id, |
| | tools: [toolId], |
| | functions: true, |
| | options: { |
| | req, |
| | returnMetadata: true, |
| | processFileURL, |
| | uploadImageBuffer, |
| | }, |
| | webSearch: appConfig.webSearch, |
| | fileStrategy: appConfig.fileStrategy, |
| | imageOutputType: appConfig.imageOutputType, |
| | }); |
| |
|
| | const tool = loadedTools[0]; |
| | const toolCallId = `${req.user.id}_${nanoid()}`; |
| | const result = await tool.invoke({ |
| | args, |
| | name: toolId, |
| | id: toolCallId, |
| | type: ToolCallTypes.TOOL_CALL, |
| | }); |
| |
|
| | const { content, artifact } = result; |
| | const toolCallData = { |
| | toolId, |
| | messageId, |
| | partIndex, |
| | blockIndex, |
| | conversationId, |
| | result: content, |
| | user: req.user.id, |
| | }; |
| |
|
| | if (!artifact || !artifact.files || toolId !== Tools.execute_code) { |
| | createToolCall(toolCallData).catch((error) => { |
| | logger.error(`Error creating tool call: ${error.message}`); |
| | }); |
| | return res.status(200).json({ |
| | result: content, |
| | }); |
| | } |
| |
|
| | const artifactPromises = []; |
| | for (const file of artifact.files) { |
| | const { id, name } = file; |
| | artifactPromises.push( |
| | (async () => { |
| | const fileMetadata = await processCodeOutput({ |
| | req, |
| | id, |
| | name, |
| | apiKey: tool.apiKey, |
| | messageId, |
| | toolCallId, |
| | conversationId, |
| | session_id: artifact.session_id, |
| | }); |
| |
|
| | if (!fileMetadata) { |
| | return null; |
| | } |
| |
|
| | return fileMetadata; |
| | })().catch((error) => { |
| | logger.error('Error processing code output:', error); |
| | return null; |
| | }), |
| | ); |
| | } |
| | const attachments = await Promise.all(artifactPromises); |
| | toolCallData.attachments = attachments; |
| | createToolCall(toolCallData).catch((error) => { |
| | logger.error(`Error creating tool call: ${error.message}`); |
| | }); |
| | res.status(200).json({ |
| | result: content, |
| | attachments, |
| | }); |
| | } catch (error) { |
| | logger.error('Error calling tool', error); |
| | res.status(500).json({ message: 'Error calling tool' }); |
| | } |
| | }; |
| |
|
| | const getToolCalls = async (req, res) => { |
| | try { |
| | const { conversationId } = req.query; |
| | const toolCalls = await getToolCallsByConvo(conversationId, req.user.id); |
| | res.status(200).json(toolCalls); |
| | } catch (error) { |
| | logger.error('Error getting tool calls', error); |
| | res.status(500).json({ message: 'Error getting tool calls' }); |
| | } |
| | }; |
| |
|
| | module.exports = { |
| | callTool, |
| | getToolCalls, |
| | verifyToolAuth, |
| | }; |
| |
|