diff --git a/src/shared/native-chat-tool-summary.test.ts b/src/shared/native-chat-tool-summary.test.ts index 9ae5d1a99be..ae8efb8f194 100644 --- a/src/shared/native-chat-tool-summary.test.ts +++ b/src/shared/native-chat-tool-summary.test.ts @@ -41,8 +41,13 @@ describe('describeToolInput', () => { it('truncates an overlong path like any other preview', () => { const path = `/very/${'long/'.repeat(30)}file.ts` - expect(describeToolInput({ file_path: path }).length).toBeLessThanOrEqual(80) - expect(describeToolInput({ file_path: path })).toContain('…') + const label = describeToolInput({ file_path: path }) + expect(label.length).toBeLessThanOrEqual(80) + expect(label).toContain('…') + // A bounded JSON preview also satisfies the two assertions above, so pin the + // label to the path itself or this passes with path-labelling deleted. + expect(label).not.toContain('file_path') + expect(label.endsWith('/file.ts')).toBe(true) }) it('keeps the filename when trimming an overlong path', () => { @@ -115,6 +120,17 @@ describe('Codex JSON-string tool arguments', () => { }) }) +describe('isStructuredToolInput', () => { + it('does not offer an expander whose detail would repeat the row label', () => { + // `{}` formats back to `{}` — the label itself. + expect(isStructuredToolInput({})).toBe(false) + expect(isStructuredToolInput([])).toBe(false) + expect(isStructuredToolInput('{}')).toBe(false) + expect(isStructuredToolInput({ command: 'ls' })).toBe(true) + expect(isStructuredToolInput([1])).toBe(true) + }) +}) + describe('summarizeToolInput bounded preview', () => { it('collapses depth beyond the bound instead of serializing the whole tree', () => { const deep = { a: { b: { c: { d: 'buried' } } } } diff --git a/src/shared/native-chat-tool-summary.ts b/src/shared/native-chat-tool-summary.ts index b625373a242..4418fd767f3 100644 --- a/src/shared/native-chat-tool-summary.ts +++ b/src/shared/native-chat-tool-summary.ts @@ -60,7 +60,12 @@ export function formatToolInput(input: unknown): string { * the row label — i.e. whether expanding the row is worth offering. */ export function isStructuredToolInput(input: unknown): boolean { const normalized = normalizeToolInput(input) - return normalized !== null && typeof normalized === 'object' + if (normalized === null || typeof normalized !== 'object') { + return false + } + // An empty object formats back to the row label verbatim, so offering the + // expander would promise detail and then repeat the row. + return Array.isArray(normalized) ? normalized.length > 0 : Object.keys(normalized).length > 0 } export function toolFilePath(input: unknown): string | null {