| const path = require('path'); |
| const { v4 } = require('uuid'); |
| const axios = require('axios'); |
| const { logger } = require('@librechat/data-schemas'); |
| const { getCodeBaseURL } = require('@librechat/agents'); |
| const { logAxiosError, getBasePath } = require('@librechat/api'); |
| const { |
| Tools, |
| FileContext, |
| FileSources, |
| imageExtRegex, |
| EToolResources, |
| } = require('librechat-data-provider'); |
| const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions'); |
| const { getStrategyFunctions } = require('~/server/services/Files/strategies'); |
| const { convertImage } = require('~/server/services/Files/images/convert'); |
| const { createFile, getFiles, updateFile } = require('~/models/File'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const processCodeOutput = async ({ |
| req, |
| id, |
| name, |
| apiKey, |
| toolCallId, |
| conversationId, |
| messageId, |
| session_id, |
| }) => { |
| const appConfig = req.config; |
| const currentDate = new Date(); |
| const baseURL = getCodeBaseURL(); |
| const basePath = getBasePath(); |
| const fileExt = path.extname(name); |
| if (!fileExt || !imageExtRegex.test(name)) { |
| return { |
| filename: name, |
| filepath: `${basePath}/api/files/code/download/${session_id}/${id}`, |
| |
| expiresAt: currentDate.getTime() + 86400000, |
| conversationId, |
| toolCallId, |
| messageId, |
| }; |
| } |
|
|
| try { |
| const formattedDate = currentDate.toISOString(); |
| const response = await axios({ |
| method: 'get', |
| url: `${baseURL}/download/${session_id}/${id}`, |
| responseType: 'arraybuffer', |
| headers: { |
| 'User-Agent': 'LibreChat/1.0', |
| 'X-API-Key': apiKey, |
| }, |
| timeout: 15000, |
| }); |
|
|
| const buffer = Buffer.from(response.data, 'binary'); |
|
|
| const file_id = v4(); |
| const _file = await convertImage(req, buffer, 'high', `${file_id}${fileExt}`); |
| const file = { |
| ..._file, |
| file_id, |
| usage: 1, |
| filename: name, |
| conversationId, |
| user: req.user.id, |
| type: `image/${appConfig.imageOutputType}`, |
| createdAt: formattedDate, |
| updatedAt: formattedDate, |
| source: appConfig.fileStrategy, |
| context: FileContext.execute_code, |
| }; |
| createFile(file, true); |
| |
| return Object.assign(file, { messageId, toolCallId }); |
| } catch (error) { |
| logAxiosError({ |
| message: 'Error downloading code environment file', |
| error, |
| }); |
| } |
| }; |
|
|
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function getSessionInfo(fileIdentifier, apiKey) { |
| try { |
| const baseURL = getCodeBaseURL(); |
| const [path, queryString] = fileIdentifier.split('?'); |
| const session_id = path.split('/')[0]; |
|
|
| let queryParams = {}; |
| if (queryString) { |
| queryParams = Object.fromEntries(new URLSearchParams(queryString).entries()); |
| } |
|
|
| const response = await axios({ |
| method: 'get', |
| url: `${baseURL}/files/${session_id}`, |
| params: { |
| detail: 'summary', |
| ...queryParams, |
| }, |
| headers: { |
| 'User-Agent': 'LibreChat/1.0', |
| 'X-API-Key': apiKey, |
| }, |
| timeout: 5000, |
| }); |
|
|
| return response.data.find((file) => file.name.startsWith(path))?.lastModified; |
| } catch (error) { |
| logAxiosError({ |
| message: `Error fetching session info: ${error.message}`, |
| error, |
| }); |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const primeFiles = async (options, apiKey) => { |
| const { tool_resources, req, agentId } = options; |
| const file_ids = tool_resources?.[EToolResources.execute_code]?.file_ids ?? []; |
| const agentResourceIds = new Set(file_ids); |
| const resourceFiles = tool_resources?.[EToolResources.execute_code]?.files ?? []; |
|
|
| |
| const allFiles = (await getFiles({ file_id: { $in: file_ids } }, null, { text: 0 })) ?? []; |
|
|
| |
| let dbFiles; |
| if (req?.user?.id && agentId) { |
| dbFiles = await filterFilesByAgentAccess({ |
| files: allFiles, |
| userId: req.user.id, |
| role: req.user.role, |
| agentId, |
| }); |
| } else { |
| dbFiles = allFiles; |
| } |
|
|
| dbFiles = dbFiles.concat(resourceFiles); |
|
|
| const files = []; |
| const sessions = new Map(); |
| let toolContext = ''; |
|
|
| for (let i = 0; i < dbFiles.length; i++) { |
| const file = dbFiles[i]; |
| if (!file) { |
| continue; |
| } |
|
|
| if (file.metadata.fileIdentifier) { |
| const [path, queryString] = file.metadata.fileIdentifier.split('?'); |
| const [session_id, id] = path.split('/'); |
|
|
| const pushFile = () => { |
| if (!toolContext) { |
| toolContext = `- Note: The following files are available in the "${Tools.execute_code}" tool environment:`; |
| } |
| toolContext += `\n\t- /mnt/data/${file.filename}${ |
| agentResourceIds.has(file.file_id) ? '' : ' (just attached by user)' |
| }`; |
| files.push({ |
| id, |
| session_id, |
| name: file.filename, |
| }); |
| }; |
|
|
| if (sessions.has(session_id)) { |
| pushFile(); |
| continue; |
| } |
|
|
| let queryParams = {}; |
| if (queryString) { |
| queryParams = Object.fromEntries(new URLSearchParams(queryString).entries()); |
| } |
|
|
| const reuploadFile = async () => { |
| try { |
| const { getDownloadStream } = getStrategyFunctions(file.source); |
| const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions( |
| FileSources.execute_code, |
| ); |
| const stream = await getDownloadStream(options.req, file.filepath); |
| const fileIdentifier = await uploadCodeEnvFile({ |
| req: options.req, |
| stream, |
| filename: file.filename, |
| entity_id: queryParams.entity_id, |
| apiKey, |
| }); |
|
|
| |
| const updatedMetadata = { |
| ...file.metadata, |
| fileIdentifier, |
| }; |
|
|
| await updateFile({ |
| file_id: file.file_id, |
| metadata: updatedMetadata, |
| }); |
| 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, toolContext }; |
| }; |
|
|
| module.exports = { |
| primeFiles, |
| processCodeOutput, |
| }; |
|
|