mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🚧 WIP: Merge Dev Build (#4611)
* 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
This commit is contained in:
parent
1909efd6ba
commit
95011ce349
58 changed files with 1418 additions and 1002 deletions
|
|
@ -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<MongoFile & { messageId: string, toolCallId: string } | { filename: string; filepath: string; expiresAt: number; conversationId: string; toolCallId: string; messageId: string } | undefined>} 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<string|null>}
|
||||
* 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<Array<{ id: string; session_id: string; name: string }>>}
|
||||
*/
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue