fix(client): friendly labels and correct arg parsing for native tools

Map skill, read_file, create_file, edit_file, set_memory, and
delete_memory to localized display names so summaries stop showing raw
tool ids, and give the memory tools the Brain icon the Memories panel
already uses. read_file and skill rows parse the arg the backend
schemas actually emit (path, with file_path as a legacy fallback;
skillName) and fall back to a generic noun instead of rendering an
empty name in their labels.
This commit is contained in:
Marco Beretta 2026-07-31 02:24:37 +02:00
parent a498f0cc5f
commit fa02825ead
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
6 changed files with 50 additions and 6 deletions

View file

@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { FileText } from 'lucide-react';
import type { TAttachment, PartMetadata } from 'librechat-data-provider';
import ProgressText from '~/components/Chat/Messages/Content/ProgressText';
import { toolPanelSpacingClassName } from '../disclosure';
import useToolCallState from './useToolCallState';
import useLazyHighlight from './useLazyHighlight';
import CodeWindowHeader from './CodeWindowHeader';
@ -85,7 +86,10 @@ export default function ReadFileCall({
onExpand?: () => void;
}) {
const localize = useLocalize();
const filePath = useMemo(() => parseJsonField(args, 'file_path'), [args]);
const filePath = useMemo(
() => parseJsonField(args, 'path') || parseJsonField(args, 'file_path'),
[args],
);
const intent = useToolCallIntent(args);
const fileName = filePath.split('/').pop() || filePath;
const lang = useMemo(() => langFromPath(filePath), [filePath]);
@ -130,7 +134,12 @@ export default function ReadFileCall({
<div style={expandStyle}>
<div className="overflow-hidden" ref={expandRef}>
{hasOutput && (
<div className="my-2 overflow-hidden rounded-lg border border-border-light bg-surface-secondary">
<div
className={cn(
toolPanelSpacingClassName,
'overflow-hidden rounded-lg border border-border-light bg-surface-secondary',
)}
>
<CodeWindowHeader language={fileName} code={output} />
<pre className="max-h-[300px] overflow-auto bg-surface-chat p-4 font-mono text-xs dark:bg-surface-primary-alt">
<code className={`hljs language-${lang} !whitespace-pre`}>

View file

@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { ScrollText } from 'lucide-react';
import type { TAttachment, PartMetadata } from 'librechat-data-provider';
import ProgressText from '~/components/Chat/Messages/Content/ProgressText';
import { toolPanelSpacingClassName } from '../disclosure';
import useToolCallState from './useToolCallState';
import { AttachmentGroup } from './Attachment';
import parseJsonField from './parseJsonField';
@ -33,14 +34,15 @@ export default function SkillCall({
onExpand?: () => void;
}) {
const localize = useLocalize();
const skillName = useMemo(() => parseJsonField(args, 'skillName'), [args]);
const parsedSkillName = useMemo(() => parseJsonField(args, 'skillName'), [args]);
const skillName = parsedSkillName || localize('com_ui_skill').toLowerCase();
const intent = useToolCallIntent(args);
const { showCode, toggleCode, expandStyle, expandRef, phase, hasOutput } = useToolCallState({
initialProgress,
isSubmitting,
output,
hasInput: !!skillName,
hasInput: !!parsedSkillName,
onExpand,
runStepStatus,
});
@ -67,14 +69,19 @@ export default function SkillCall({
aria-hidden="true"
/>
}
hasInput={!!skillName || hasOutput}
hasInput={!!parsedSkillName || hasOutput}
isExpanded={showCode}
/>
</div>
<div style={expandStyle}>
<div className="overflow-hidden" ref={expandRef}>
{hasOutput && (
<div className="my-2 overflow-hidden rounded-lg border border-border-light bg-surface-secondary">
<div
className={cn(
toolPanelSpacingClassName,
'overflow-hidden rounded-lg border border-border-light bg-surface-secondary',
)}
>
<div className="bg-surface-primary-alt p-4 text-xs dark:bg-transparent">
<div className="mb-1.5 text-[10px] font-medium uppercase tracking-wide text-text-secondary">
{localize('com_ui_output')}

View file

@ -8,6 +8,7 @@ import {
FileText,
MessageCircleQuestion,
ScrollText,
Brain,
Zap,
Wrench,
} from 'lucide-react';
@ -30,6 +31,7 @@ export type ToolIconType =
| 'read_file'
| 'bash_tool'
| 'ask_user_question'
| 'memory'
| 'action'
| 'generic';
@ -44,6 +46,7 @@ const ICON_MAP: Record<ToolIconType, React.ComponentType<{ className?: string }>
read_file: FileText,
bash_tool: BashIcon,
ask_user_question: MessageCircleQuestion,
memory: Brain,
action: Zap,
generic: Wrench,
};
@ -82,6 +85,9 @@ export function getToolIconType(name: string): ToolIconType {
if (name === 'ask_user_question') {
return 'ask_user_question';
}
if (name === 'set_memory' || name === 'delete_memory') {
return 'memory';
}
if (name.startsWith(Constants.LC_TRANSFER_TO_)) {
return 'agent_handoff';
}

View file

@ -1621,8 +1621,10 @@
"com_ui_more_options": "More options",
"com_ui_my_prompts": "My Prompts",
"com_ui_my_skills": "My Skills",
"com_ui_n_actions_failed": "{{0}} failed",
"com_ui_n_files": "{{0}} files",
"com_ui_n_files_changed": "{{0}} files changed",
"com_ui_n_searches": "{{0}} searches",
"com_ui_name": "Name",
"com_ui_name_sort": "Sort by Name",
"com_ui_navigate_results": "Navigate results",
@ -1689,6 +1691,7 @@
"com_ui_omitted": "Omitted",
"com_ui_on": "On",
"com_ui_one_file_changed": "1 file changed",
"com_ui_one_action_failed": "1 failed",
"com_ui_open_archived_chat_new_tab_title": "{{title}} (opens in new tab)",
"com_ui_open_artifact": "Open artifact",
"com_ui_open_as_artifact": "Open as artifact",
@ -1807,6 +1810,7 @@
"com_ui_queued_turn_reconciliation_required": "Awaiting reconciliation",
"com_ui_quote_selections": "{{0}} selections",
"com_ui_quotes_queued": "Quotes added for your next message",
"com_ui_ran_n_actions": "Ran {{0}} actions",
"com_ui_ran_n_agents": "Ran {{0}} agents",
"com_ui_read_aloud": "Read aloud",
"com_ui_read_file": "Read {{0}}",
@ -1893,6 +1897,7 @@
"com_ui_run_code_error": "There was an error running the code",
"com_ui_running": "Running...",
"com_ui_running_command": "Running command",
"com_ui_running_n_actions": "Running {{0}} actions",
"com_ui_running_n_agents": "Running {{0}} agents",
"com_ui_sandbox_starting": "Starting sandbox environment",
"com_ui_save": "Save",
@ -1981,7 +1986,9 @@
"com_ui_search_skills": "Search skills...",
"com_ui_search_table": "Search table",
"com_ui_search_table_description": "Type to filter results",
"com_ui_searched_web_and_files": "Searched web and files",
"com_ui_searching_files": "Searching your files",
"com_ui_searching_web_and_files": "Searching web and files",
"com_ui_seconds": "seconds",
"com_ui_secret_key": "Secret Key",
"com_ui_select": "Select",
@ -2393,9 +2400,14 @@
"com_ui_tool_name_ask_user_question": "Question",
"com_ui_tool_name_code": "Code",
"com_ui_tool_name_code_analysis": "Code Analysis",
"com_ui_tool_name_create_file": "Create File",
"com_ui_tool_name_delete_memory": "Delete Memory",
"com_ui_tool_name_edit_file": "Edit File",
"com_ui_tool_name_file_search": "File Search",
"com_ui_tool_name_image_edit": "Image Edit",
"com_ui_tool_name_read_file": "Read File",
"com_ui_tool_name_image_gen": "Image Generation",
"com_ui_tool_name_set_memory": "Save Memory",
"com_ui_tool_name_web_search": "Web Search",
"com_ui_tool_response_placeholder": "Type a substitute result to return to the agent",
"com_ui_tools": "Tools",

View file

@ -74,6 +74,10 @@ describe('getToolDisplayLabel', () => {
);
});
it.each(['set_memory', 'delete_memory'])('returns a friendly memory label for %s', (toolName) => {
expect(getToolDisplayLabel(toolName, identityLocalize)).toBe(TOOL_FRIENDLY_NAME_KEYS[toolName]);
});
it('returns the raw name for an unknown native tool', () => {
expect(getToolDisplayLabel('custom_tool', identityLocalize)).toBe('custom_tool');
});

View file

@ -22,6 +22,12 @@ export const TOOL_FRIENDLY_NAME_KEYS: Record<string, TranslationKeys> = {
code_interpreter: 'com_ui_tool_name_code_analysis',
retrieval: 'com_ui_tool_name_file_search',
ask_user_question: 'com_ui_tool_name_ask_user_question',
create_file: 'com_ui_tool_name_create_file',
set_memory: 'com_ui_tool_name_set_memory',
edit_file: 'com_ui_tool_name_edit_file',
delete_memory: 'com_ui_tool_name_delete_memory',
skill: 'com_ui_skill',
read_file: 'com_ui_tool_name_read_file',
};
export interface ParsedToolName {