From 4cab15605a29f095da4d2078e18a60cf1709c1de Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 1 Aug 2026 21:31:51 +0300 Subject: [PATCH] fix(ui): show glob pattern in tool description --- .../components/chat/message/parts/ToolPart.test.ts | 13 +++++++++++++ .../src/components/chat/message/parts/ToolPart.tsx | 3 ++- .../chat/message/parts/toolRenderUtils.ts | 13 +++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/chat/message/parts/ToolPart.test.ts b/packages/ui/src/components/chat/message/parts/ToolPart.test.ts index b263ea52..d31d076e 100644 --- a/packages/ui/src/components/chat/message/parts/ToolPart.test.ts +++ b/packages/ui/src/components/chat/message/parts/ToolPart.test.ts @@ -4,6 +4,7 @@ import { readTaskTagSessionIdFromOutput } from './taskSessionIdParser'; import { tryParseJsonOutput } from '../toolRenderers'; import { getStreamingThrottleText } from '../../hooks/useStreamingTextThrottle'; import { getStreamingOutputAppend, getToolOutput } from './toolOutput'; +import { getToolDescriptionFallback } from './toolRenderUtils'; describe('getToolOutput', () => { test('prefers authoritative state output', () => { @@ -64,3 +65,15 @@ describe('OpenChamber tool output', () => { expect(tryParseJsonOutput(JSON.stringify(result))).toEqual({ data: result, isJson: true }); }); }); + +describe('getToolDescriptionFallback', () => { + test('uses the glob pattern when the provided description and title are empty', () => { + expect(getToolDescriptionFallback('glob', '', { pattern: 'packages/electron/README.md' })) + .toBe('packages/electron/README.md'); + }); + + test('prefers an existing glob description over the pattern', () => { + expect(getToolDescriptionFallback('glob', 'Electron docs', { pattern: 'packages/electron/README.md' })) + .toBe('Electron docs'); + }); +}); diff --git a/packages/ui/src/components/chat/message/parts/ToolPart.tsx b/packages/ui/src/components/chat/message/parts/ToolPart.tsx index 39b08d9d..b703c356 100644 --- a/packages/ui/src/components/chat/message/parts/ToolPart.tsx +++ b/packages/ui/src/components/chat/message/parts/ToolPart.tsx @@ -58,6 +58,7 @@ import { getDiffPatchEntries, getPatchText, type DiffPatchEntry } from './toolDi import { isEmbeddedSessionChat } from '@/components/layout/contextPanelEmbeddedChat'; import { useStreamingTextThrottle } from '../../hooks/useStreamingTextThrottle'; import { getStreamingOutputAppend, getToolOutput } from './toolOutput'; +import { getToolDescriptionFallback } from './toolRenderUtils'; const TOOL_ROW_TEXT_CLASS = '!text-[length:var(--text-meta)] !leading-5 sm:!leading-6 tracking-normal'; const TOOL_ROW_TITLE_CLASS = cn('typography-meta font-medium', TOOL_ROW_TEXT_CLASS); @@ -791,7 +792,7 @@ const getToolDescription = (part: ToolPartType, state: ToolStateUnion, currentDi } const desc = input?.description || metadata?.description || ('title' in state && state.title) || ''; - return typeof desc === 'string' ? desc : ''; + return getToolDescriptionFallback(part.tool, desc, input); }; interface ToolScrollableSectionProps { diff --git a/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts b/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts index 62fc0a20..b906ec3a 100644 --- a/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts +++ b/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts @@ -29,3 +29,16 @@ export const isStandaloneTool = (toolName: unknown): boolean => { export const isStaticTool = (toolName: unknown): boolean => { return STATIC_TOOL_NAMES.has(normalizeToolName(toolName)); }; + +export const getToolDescriptionFallback = ( + toolName: unknown, + description: unknown, + input: Record | undefined, +): string => { + if (typeof description === 'string' && description.trim().length > 0) { + return description; + } + + const globPattern = normalizeToolName(toolName) === 'glob' ? input?.pattern : undefined; + return typeof globPattern === 'string' ? globPattern : ''; +};