From 7129b1b1e48c9b8a11bce9f893e2c9154757a6ae Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 11 May 2026 02:15:51 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9C=20refactor:=20Improve=20Skill=20Ha?= =?UTF-8?q?ndling=20Logs=20(#13057)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Streamline batch upload error handling in `uploadCodeEnvFile` * refactor: Enhance session info error logging in `getSessionInfo` * refactor: Update error logging to use `logAxiosError` in various agent handlers and skill file processing functions * refactor: Consolidate missing resource checks in `createToolExecuteHandler` for better clarity --- api/server/services/Files/Code/crud.js | 119 ++++++++++------------ api/server/services/Files/Code/process.js | 7 +- packages/api/src/agents/handlers.ts | 36 ++++--- packages/api/src/agents/skillFiles.ts | 17 ++-- packages/api/src/agents/skills.ts | 9 +- 5 files changed, 96 insertions(+), 92 deletions(-) diff --git a/api/server/services/Files/Code/crud.js b/api/server/services/Files/Code/crud.js index eec205bb07..035027b52d 100644 --- a/api/server/services/Files/Code/crud.js +++ b/api/server/services/Files/Code/crud.js @@ -150,71 +150,62 @@ async function uploadCodeEnvFile({ req, stream, filename, kind, id, version }) { * @throws {Error} If the batch upload fails entirely. */ async function batchUploadCodeEnvFiles({ req, files, kind, id, version, read_only = false }) { - try { - const form = new FormData(); - appendCodeEnvFileIdentity(form, { kind, id, version }); - if (read_only) { - form.append('read_only', 'true'); - } - for (const file of files) { - appendCodeEnvFile(form, file.stream, file.filename); - } - - const baseURL = getCodeBaseURL(); - const authHeaders = await getCodeApiAuthHeaders(req); - /** @type {import('axios').AxiosRequestConfig} */ - const options = { - headers: { - ...form.getHeaders(), - 'Content-Type': 'multipart/form-data', - 'User-Agent': 'LibreChat/1.0', - 'User-Id': req.user.id, - ...authHeaders, - }, - httpAgent: codeServerHttpAgent, - httpsAgent: codeServerHttpsAgent, - timeout: 120000, - maxContentLength: MAX_FILE_SIZE, - maxBodyLength: MAX_FILE_SIZE, - }; - - const response = await axios.post(`${baseURL}/upload/batch`, form, options); - - /** @type {{ message: string; storage_session_id: string; files: Array<{ status: string; fileId?: string; filename: string; error?: string }>; succeeded: number; failed: number }} */ - const result = response.data; - if ( - !result || - typeof result !== 'object' || - !result.storage_session_id || - !Array.isArray(result.files) - ) { - throw new Error(`Unexpected batch upload response: ${JSON.stringify(result).slice(0, 200)}`); - } - if (result.message === 'error') { - throw new Error('All files in batch upload failed'); - } - - if (result.failed > 0) { - const failedNames = result.files - .filter((f) => f.status === 'error') - .map((f) => `${f.filename}: ${f.error || 'unknown'}`) - .join(', '); - logger.warn(`[batchUploadCodeEnvFiles] ${result.failed} file(s) failed: ${failedNames}`); - } - - const successFiles = result.files - .filter((f) => f.status === 'success' && f.fileId) - .map((f) => ({ fileId: f.fileId, filename: f.filename })); - - return { storage_session_id: result.storage_session_id, files: successFiles }; - } catch (error) { - throw new Error( - logAxiosError({ - message: `Error in batch upload to code environment: ${error instanceof Error ? error.message : String(error)}`, - error, - }), - ); + const form = new FormData(); + appendCodeEnvFileIdentity(form, { kind, id, version }); + if (read_only) { + form.append('read_only', 'true'); } + for (const file of files) { + appendCodeEnvFile(form, file.stream, file.filename); + } + + const baseURL = getCodeBaseURL(); + const authHeaders = await getCodeApiAuthHeaders(req); + /** @type {import('axios').AxiosRequestConfig} */ + const options = { + headers: { + ...form.getHeaders(), + 'Content-Type': 'multipart/form-data', + 'User-Agent': 'LibreChat/1.0', + 'User-Id': req.user.id, + ...authHeaders, + }, + httpAgent: codeServerHttpAgent, + httpsAgent: codeServerHttpsAgent, + timeout: 120000, + maxContentLength: MAX_FILE_SIZE, + maxBodyLength: MAX_FILE_SIZE, + }; + + const response = await axios.post(`${baseURL}/upload/batch`, form, options); + + /** @type {{ message: string; storage_session_id: string; files: Array<{ status: string; fileId?: string; filename: string; error?: string }>; succeeded: number; failed: number }} */ + const result = response.data; + if ( + !result || + typeof result !== 'object' || + !result.storage_session_id || + !Array.isArray(result.files) + ) { + throw new Error(`Unexpected batch upload response: ${JSON.stringify(result).slice(0, 200)}`); + } + if (result.message === 'error') { + throw new Error('All files in batch upload failed'); + } + + if (result.failed > 0) { + const failedNames = result.files + .filter((f) => f.status === 'error') + .map((f) => `${f.filename}: ${f.error || 'unknown'}`) + .join(', '); + logger.warn(`[batchUploadCodeEnvFiles] ${result.failed} file(s) failed: ${failedNames}`); + } + + const successFiles = result.files + .filter((f) => f.status === 'success' && f.fileId) + .map((f) => ({ fileId: f.fileId, filename: f.filename })); + + return { storage_session_id: result.storage_session_id, files: successFiles }; } module.exports = { diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js index adda87b201..a04c6329c6 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -707,10 +707,9 @@ async function getSessionInfo(ref, req) { return response.data?.lastModified; } catch (error) { - logAxiosError({ - message: `Error fetching session info: ${error.message}`, - error, - }); + logger.debug( + `[getSessionInfo] session lookup failed (treating as cache miss): ${error?.message ?? String(error)}`, + ); return null; } } diff --git a/packages/api/src/agents/handlers.ts b/packages/api/src/agents/handlers.ts index 415ff6ac02..5a6e3834bf 100644 --- a/packages/api/src/agents/handlers.ts +++ b/packages/api/src/agents/handlers.ts @@ -14,10 +14,10 @@ import type { CodeEnvRef } from 'librechat-data-provider'; import type { StructuredToolInterface } from '@librechat/agents/langchain/tools'; import type { SkillFileRecord } from './skillFiles'; import type { ServerRequest } from '~/types'; +import { logAxiosError, runOutsideTracing } from '~/utils'; import { buildSkillPrimeMessage } from './skills'; import { cleanCodeToolOutput } from './cleanup'; import { primeSkillFiles } from './skillFiles'; -import { runOutsideTracing } from '~/utils'; export interface ToolEndCallbackData { output: { @@ -753,10 +753,10 @@ async function handleReadFileCall( if (file.isBinary == null && updateSkillFileContent) { updateSkillFileContent(skill._id, relativePath, { isBinary: true }).catch( (err: unknown) => { - logger.warn( - '[handleReadFileCall] cache write failed:', - err instanceof Error ? err.message : err, - ); + logAxiosError({ + message: '[handleReadFileCall] cache write failed', + error: err, + }); }, ); } @@ -793,10 +793,10 @@ async function handleReadFileCall( if (file.content == null && updateSkillFileContent && buffer.length <= MAX_CACHE_BYTES) { updateSkillFileContent(skill._id, relativePath, { content: text, isBinary: false }).catch( (err: unknown) => { - logger.warn( - '[handleReadFileCall] cache write failed:', - err instanceof Error ? err.message : err, - ); + logAxiosError({ + message: '[handleReadFileCall] cache write failed', + error: err, + }); }, ); } @@ -1081,11 +1081,23 @@ export function createToolExecuteHandler(options: ToolExecuteOptions): EventHand typeof f.storage_session_id === 'string' && !!f.storage_session_id, hasVersion: typeof f.version === 'number', })); - const missingResourceId = summary.filter((s) => !s.hasResourceId).length; + let missingResourceId = 0; + let missingStorageSessionId = 0; + let missingVersion = 0; + const kindCounts: Record = {}; + for (const s of summary) { + if (!s.hasResourceId) missingResourceId++; + if (!s.hasStorageSessionId) missingStorageSessionId++; + if (!s.hasVersion) missingVersion++; + const k = typeof s.kind === 'string' ? s.kind : 'unknown'; + kindCounts[k] = (kindCounts[k] ?? 0) + 1; + } logger.debug( `[code-env:inject] tool=${tc.name} files=${refs.length} ` + - `missingResourceId=${missingResourceId}`, - { summary }, + `missingResourceId=${missingResourceId} ` + + `missingStorageSessionId=${missingStorageSessionId} ` + + `missingVersion=${missingVersion} ` + + `kinds=${JSON.stringify(kindCounts)}`, ); if (missingResourceId > 0) { logger.warn( diff --git a/packages/api/src/agents/skillFiles.ts b/packages/api/src/agents/skillFiles.ts index 3081d4b332..b395f383b2 100644 --- a/packages/api/src/agents/skillFiles.ts +++ b/packages/api/src/agents/skillFiles.ts @@ -6,6 +6,7 @@ import type { CodeEnvRef } from 'librechat-data-provider'; import type { Types } from 'mongoose'; import type { ServerRequest } from '~/types'; import { extractInvokedSkillsFromPayload } from './run'; +import { logAxiosError } from '~/utils'; export interface SkillFileRecord { relativePath: string; @@ -288,20 +289,20 @@ export async function primeSkillFiles( try { await updateSkillFileCodeEnvIds(updates); } catch (err: unknown) { - logger.warn( - '[primeSkillFiles] Failed to persist codeEnvRefs:', - err instanceof Error ? err.message : err, - ); + logAxiosError({ + message: `[primeSkillFiles] Failed to persist codeEnvRefs`, + error: err, + }); } } } return { storage_session_id: result.storage_session_id, files }; } catch (error) { - logger.error( - `[primeSkillFiles] Batch upload failed for skill "${skill.name}":`, - error instanceof Error ? error.message : error, - ); + logAxiosError({ + message: `[primeSkillFiles] Batch upload failed for skill "${skill.name}"`, + error, + }); return null; } } diff --git a/packages/api/src/agents/skills.ts b/packages/api/src/agents/skills.ts index 45b43df8b8..7112ee9592 100644 --- a/packages/api/src/agents/skills.ts +++ b/packages/api/src/agents/skills.ts @@ -8,6 +8,7 @@ import type { Agent } from 'librechat-data-provider'; import type { Types } from 'mongoose'; import type { InitializeAgentDbMethods } from './initialize'; import { registerCodeExecutionTools } from './tools'; +import { logAxiosError } from '~/utils'; const SKILL_CATALOG_LIMIT = 100; /** Max pages scanned per run when filtering out inactive skills. */ @@ -809,10 +810,10 @@ export async function resolveAlwaysApplySkills( cursor, }); } catch (err) { - logger.warn( - '[resolveAlwaysApplySkills] listAlwaysApplySkills failed:', - err instanceof Error ? err.message : err, - ); + logAxiosError({ + message: '[resolveAlwaysApplySkills] listAlwaysApplySkills failed', + error: err, + }); return resolved; }