Skip to content

Commit c4f1da2

Browse files
authored
🔄 fix: Avatar & Error Handling Enhancements (danny-avila#6687)
* fix: Ensure safe access to agent capabilities in AgentConfig * fix: don't show agent builder if agents endpoint is not enabled * fix: Improve error logging for MCP tool calls * fix: Enhance error message for MCP tool failures * feat: Add optional spec and iconURL properties to TEndpointOption type * chore: Update condition to use constant for new conversation parameter * feat: Enhance abort error handling with additional endpoint options to properly render error message fields * fix: Throw error instead of returning message for failed MCP tool calls * refactor: separate logic to generate new S3 URLs for expired links * feat: Implement S3 URL refresh for user avatars with error handling * fix: authcontext error in chats where agent chain is used * refactor: streamline balance configuration logic in getBalanceConfig function * fix: enhance icon resolution logic in SpecIcon component * fix: allow null values for spec and iconURL in TEndpointOption type * fix: update balance check to allow null tokenCredits
1 parent cfa44de commit c4f1da2

File tree

17 files changed

+184
-75
lines changed

17 files changed

+184
-75
lines changed

api/server/controllers/UserController.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
const { FileSources } = require('librechat-data-provider');
12
const {
23
Balance,
34
getFiles,
5+
updateUser,
46
deleteFiles,
57
deleteConvos,
68
deletePresets,
@@ -12,15 +14,31 @@ const User = require('~/models/User');
1214
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
1315
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
1416
const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService');
17+
const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud');
1518
const { processDeleteRequest } = require('~/server/services/Files/process');
1619
const { deleteAllSharedLinks } = require('~/models/Share');
1720
const { deleteToolCalls } = require('~/models/ToolCall');
1821
const { Transaction } = require('~/models/Transaction');
1922
const { logger } = require('~/config');
2023

2124
const getUserController = async (req, res) => {
25+
/** @type {MongoUser} */
2226
const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user };
2327
delete userData.totpSecret;
28+
if (req.app.locals.fileStrategy === FileSources.s3 && userData.avatar) {
29+
const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600);
30+
if (!avatarNeedsRefresh) {
31+
return res.status(200).send(userData);
32+
}
33+
const originalAvatar = userData.avatar;
34+
try {
35+
userData.avatar = await getNewS3URL(userData.avatar);
36+
await updateUser(userData.id, { avatar: userData.avatar });
37+
} catch (error) {
38+
userData.avatar = originalAvatar;
39+
logger.error('Error getting new S3 URL for avatar:', error);
40+
}
41+
}
2442
res.status(200).send(userData);
2543
};
2644

api/server/middleware/abortMiddleware.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ const createAbortController = (req, res, getAbortData, getReqData) => {
148148
return { abortController, onStart };
149149
};
150150

151+
/**
152+
* @param {ServerResponse} res
153+
* @param {ServerRequest} req
154+
* @param {Error | unknown} error
155+
* @param {Partial<TMessage> & { partialText?: string }} data
156+
* @returns { Promise<void> }
157+
*/
151158
const handleAbortError = async (res, req, error, data) => {
152159
if (error?.message?.includes('base64')) {
153160
logger.error('[handleAbortError] Error in base64 encoding', {
@@ -178,17 +185,30 @@ const handleAbortError = async (res, req, error, data) => {
178185
errorText = `{"type":"${ErrorTypes.NO_SYSTEM_MESSAGES}"}`;
179186
}
180187

188+
/**
189+
* @param {string} partialText
190+
* @returns {Promise<void>}
191+
*/
181192
const respondWithError = async (partialText) => {
193+
const endpointOption = req.body?.endpointOption;
182194
let options = {
183195
sender,
184196
messageId,
185197
conversationId,
186198
parentMessageId,
187199
text: errorText,
188-
shouldSaveMessage: true,
189200
user: req.user.id,
201+
shouldSaveMessage: true,
202+
spec: endpointOption?.spec,
203+
iconURL: endpointOption?.iconURL,
204+
modelLabel: endpointOption?.modelLabel,
205+
model: endpointOption?.modelOptions?.model || req.body?.model,
190206
};
191207

208+
if (req.body?.agent_id) {
209+
options.agent_id = req.body.agent_id;
210+
}
211+
192212
if (partialText) {
193213
options = {
194214
...options,

api/server/services/Config/getCustomConfig.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,16 @@ async function getCustomConfig() {
3131
async function getBalanceConfig() {
3232
const isLegacyEnabled = isEnabled(process.env.CHECK_BALANCE);
3333
const startBalance = process.env.START_BALANCE;
34-
if (isLegacyEnabled || (startBalance != null && startBalance)) {
35-
/** @type {TCustomConfig['balance']} */
36-
const config = {
37-
enabled: isLegacyEnabled,
38-
startBalance: startBalance ? parseInt(startBalance, 10) : undefined,
39-
};
40-
return config;
41-
}
34+
/** @type {TCustomConfig['balance']} */
35+
const config = {
36+
enabled: isLegacyEnabled,
37+
startBalance: startBalance != null && startBalance ? parseInt(startBalance, 10) : undefined,
38+
};
4239
const customConfig = await getCustomConfig();
4340
if (!customConfig) {
44-
return null;
41+
return config;
4542
}
46-
return customConfig?.['balance'] ?? null;
43+
return { ...config, ...(customConfig?.['balance'] ?? {}) };
4744
}
4845

4946
/**

api/server/services/Files/S3/crud.js

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,36 @@ function needsRefresh(signedUrl, bufferSeconds) {
303303
}
304304
}
305305

306+
/**
307+
* Generates a new URL for an expired S3 URL
308+
* @param {string} currentURL - The current file URL
309+
* @returns {Promise<string | undefined>}
310+
*/
311+
async function getNewS3URL(currentURL) {
312+
try {
313+
const s3Key = extractKeyFromS3Url(currentURL);
314+
if (!s3Key) {
315+
return;
316+
}
317+
const keyParts = s3Key.split('/');
318+
if (keyParts.length < 3) {
319+
return;
320+
}
321+
322+
const basePath = keyParts[0];
323+
const userId = keyParts[1];
324+
const fileName = keyParts.slice(2).join('/');
325+
326+
return await getS3URL({
327+
userId,
328+
fileName,
329+
basePath,
330+
});
331+
} catch (error) {
332+
logger.error('Error getting new S3 URL:', error);
333+
}
334+
}
335+
306336
/**
307337
* Refreshes S3 URLs for an array of files if they're expired or close to expiring
308338
*
@@ -333,30 +363,15 @@ async function refreshS3FileUrls(files, batchUpdateFiles, bufferSeconds = 3600)
333363
continue;
334364
}
335365
try {
336-
const s3Key = extractKeyFromS3Url(file.filepath);
337-
if (!s3Key) {
338-
continue;
339-
}
340-
const keyParts = s3Key.split('/');
341-
if (keyParts.length < 3) {
366+
const newURL = await getNewS3URL(file.filepath);
367+
if (!newURL) {
342368
continue;
343369
}
344-
345-
const basePath = keyParts[0];
346-
const userId = keyParts[1];
347-
const fileName = keyParts.slice(2).join('/');
348-
349-
const newUrl = await getS3URL({
350-
userId,
351-
fileName,
352-
basePath,
353-
});
354-
355370
filesToUpdate.push({
356371
file_id: file.file_id,
357-
filepath: newUrl,
372+
filepath: newURL,
358373
});
359-
files[i].filepath = newUrl;
374+
files[i].filepath = newURL;
360375
} catch (error) {
361376
logger.error(`Error refreshing S3 URL for file ${file.file_id}:`, error);
362377
}
@@ -425,4 +440,6 @@ module.exports = {
425440
getS3FileStream,
426441
refreshS3FileUrls,
427442
refreshS3Url,
443+
needsRefresh,
444+
getNewS3URL,
428445
};

api/server/services/MCP.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ async function createMCPTool({ req, toolKey, provider }) {
6969
}
7070
return result;
7171
} catch (error) {
72-
return `${toolName} MCP server tool call failed.`;
72+
logger.error(
73+
`[MCP][User: ${userId}][${serverName}] Error calling "${toolName}" MCP tool:`,
74+
error,
75+
);
76+
throw new Error(
77+
`"${toolKey}" tool call failed${error?.message ? `: ${error?.message}` : '.'}`,
78+
);
7379
}
7480
};
7581

api/typedefs.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,12 @@
828828
* @memberof typedefs
829829
*/
830830

831+
/**
832+
* @exports TEndpointOption
833+
* @typedef {import('librechat-data-provider').TEndpointOption} TEndpointOption
834+
* @memberof typedefs
835+
*/
836+
831837
/**
832838
* @exports TAttachment
833839
* @typedef {import('librechat-data-provider').TAttachment} TAttachment

client/src/components/Chat/Menus/Endpoints/components/SpecIcon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const SpecIcon: React.FC<SpecIconProps> = ({ currentSpec, endpointsConfig }) =>
2020
let Icon: IconType;
2121

2222
if (!iconURL.includes('http')) {
23-
Icon = (icons[iconKey] ?? icons.unknown) as IconType;
23+
Icon = (icons[iconURL] ?? icons[iconKey] ?? icons.unknown) as IconType;
2424
} else if (iconURL) {
2525
return (
2626
<URLIcon
@@ -32,7 +32,7 @@ const SpecIcon: React.FC<SpecIconProps> = ({ currentSpec, endpointsConfig }) =>
3232
/>
3333
);
3434
} else {
35-
Icon = (icons[endpoint ?? ''] ?? icons.unknown) as IconType;
35+
Icon = (icons[endpoint ?? ''] ?? icons[iconKey] ?? icons.unknown) as IconType;
3636
}
3737

3838
return (

client/src/components/Chat/Messages/Content/Parts/AgentUpdate.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import React, { useMemo } from 'react';
22
import { EModelEndpoint } from 'librechat-data-provider';
3+
import type { TMessage } from 'librechat-data-provider';
4+
import MessageIcon from '~/components/Share/MessageIcon';
35
import { useAgentsMapContext } from '~/Providers';
4-
import Icon from '~/components/Endpoints/Icon';
6+
import { useLocalize } from '~/hooks';
57

68
interface AgentUpdateProps {
79
currentAgentId: string;
810
}
911

1012
const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
13+
const localize = useLocalize();
1114
const agentsMap = useAgentsMapContext() || {};
1215
const currentAgent = useMemo(() => agentsMap[currentAgentId], [agentsMap, currentAgentId]);
1316
if (!currentAgentId) {
@@ -23,14 +26,19 @@ const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
2326
</div>
2427
<div className="my-4 flex items-center gap-2">
2528
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
26-
<Icon
27-
endpoint={EModelEndpoint.agents}
28-
agentName={currentAgent?.name ?? ''}
29-
iconURL={currentAgent?.avatar?.filepath}
30-
isCreatedByUser={false}
29+
<MessageIcon
30+
message={
31+
{
32+
endpoint: EModelEndpoint.agents,
33+
isCreatedByUser: false,
34+
} as TMessage
35+
}
36+
agent={currentAgent}
3137
/>
3238
</div>
33-
<div className="font-medium text-text-primary">{currentAgent?.name}</div>
39+
<div className="text-base font-medium text-text-primary">
40+
{currentAgent?.name || localize('com_ui_agent')}
41+
</div>
3442
</div>
3543
</div>
3644
);

client/src/components/Endpoints/ConvoIconURL.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const ConvoIconURL: React.FC<ConvoIconURLProps> = ({
6666
agentName={agentName}
6767
iconURL={endpointIconURL}
6868
assistantName={assistantName}
69-
avatar={assistantAvatar ?? agentAvatar}
69+
avatar={assistantAvatar || agentAvatar}
7070
/>
7171
)}
7272
</div>

client/src/components/Share/MessageIcon.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useMemo } from 'react';
2-
import type { TMessage, TPreset, Assistant, Agent } from 'librechat-data-provider';
2+
import type { TMessage, Assistant, Agent } from 'librechat-data-provider';
33
import type { TMessageProps } from '~/common';
44
import MessageEndpointIcon from '../Endpoints/MessageEndpointIcon';
55
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
@@ -14,11 +14,6 @@ export default function MessageIcon(
1414
) {
1515
const { message, conversation, assistant, agent } = props;
1616

17-
const assistantName = assistant ? (assistant.name as string | undefined) : '';
18-
const assistantAvatar = assistant ? (assistant.metadata?.avatar as string | undefined) : '';
19-
const agentName = agent ? (agent.name as string | undefined) : '';
20-
const agentAvatar = agent ? (agent.metadata?.avatar as string | undefined) : '';
21-
2217
const messageSettings = useMemo(
2318
() => ({
2419
...(conversation ?? {}),
@@ -33,7 +28,27 @@ export default function MessageIcon(
3328
const iconURL = messageSettings.iconURL ?? '';
3429
let endpoint = messageSettings.endpoint;
3530
endpoint = getIconEndpoint({ endpointsConfig: undefined, iconURL, endpoint });
36-
31+
const assistantName = (assistant ? assistant.name : '') ?? '';
32+
const assistantAvatar = (assistant ? assistant.metadata?.avatar : '') ?? '';
33+
const agentName = (agent ? agent.name : '') ?? '';
34+
const agentAvatar = (agent ? agent?.avatar?.filepath : '') ?? '';
35+
const avatarURL = useMemo(() => {
36+
let result = '';
37+
if (assistant) {
38+
result = assistantAvatar;
39+
} else if (agent) {
40+
result = agentAvatar;
41+
}
42+
return result;
43+
}, [assistant, agent, assistantAvatar, agentAvatar]);
44+
console.log('MessageIcon', {
45+
endpoint,
46+
iconURL,
47+
assistantName,
48+
assistantAvatar,
49+
agentName,
50+
agentAvatar,
51+
});
3752
if (message?.isCreatedByUser !== true && iconURL && iconURL.includes('http')) {
3853
return (
3954
<ConvoIconURL
@@ -68,7 +83,7 @@ export default function MessageIcon(
6883
<MessageEndpointIcon
6984
{...messageSettings}
7085
endpoint={endpoint}
71-
iconURL={assistant == null ? undefined : assistantAvatar}
86+
iconURL={avatarURL}
7287
model={message?.model ?? conversation?.model}
7388
assistantName={assistantName}
7489
agentName={agentName}

0 commit comments

Comments
 (0)