From 30efe0b70289db941d0bcb392bdb30a46a6cba58 Mon Sep 17 00:00:00 2001 From: Jack Wotherspoon Date: Tue, 2 Jun 2026 13:55:44 -0400 Subject: [PATCH] fix(cli): filter out tool response metadata from resumed session history --- packages/cli/src/utils/sessionUtils.test.ts | 55 +++++++++++++++++++++ packages/cli/src/utils/sessionUtils.ts | 16 +++--- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/utils/sessionUtils.test.ts b/packages/cli/src/utils/sessionUtils.test.ts index cfdadf795f..8c23c98f64 100644 --- a/packages/cli/src/utils/sessionUtils.test.ts +++ b/packages/cli/src/utils/sessionUtils.test.ts @@ -1141,4 +1141,59 @@ describe('convertSessionToHistoryFormats', () => { throw new Error('Expected tool_group history item'); } }); + + it('should NOT include tool response text messages in uiHistory', () => { + const messages: MessageRecord[] = [ + { + id: '1', + type: 'user', + timestamp: new Date().toISOString(), + content: [{ text: 'Hello' }], + }, + { + id: '2', + type: 'gemini', + timestamp: new Date().toISOString(), + content: [{ text: 'Thinking...' }], + toolCalls: [ + { + id: 'call_1', + name: 'read_file', + args: { path: 'test.ts' }, + status: CoreToolCallStatus.Success, + timestamp: new Date().toISOString(), + result: [{ text: 'file content' }], + }, + ], + }, + { + id: '3', + type: 'user', + timestamp: new Date().toISOString(), + content: [ + { + functionResponse: { + name: 'read_file', + response: { content: 'file content' }, + }, + }, + ], + }, + ]; + + const { uiHistory } = convertSessionToHistoryFormats(messages); + + const userMessages = uiHistory.filter((item) => item.type === 'user'); + expect(userMessages).toHaveLength(1); + expect(userMessages[0].text).toBe('Hello'); + + const toolGroups = uiHistory.filter((item) => item.type === 'tool_group'); + expect(toolGroups).toHaveLength(1); + + // Verify no message contains the "[Function Response" string which comes from verbose partToString + const allText = uiHistory + .map((item) => ('text' in item ? item.text : '')) + .join(' '); + expect(allText).not.toContain('[Function Response'); + }); }); diff --git a/packages/cli/src/utils/sessionUtils.ts b/packages/cli/src/utils/sessionUtils.ts index a2918eae3e..b48a2f9334 100644 --- a/packages/cli/src/utils/sessionUtils.ts +++ b/packages/cli/src/utils/sessionUtils.ts @@ -6,13 +6,13 @@ import { checkExhaustive, - partListUnionToString, SESSION_FILE_PREFIX, CoreToolCallStatus, type Storage, type ConversationRecord, type MessageRecord, loadConversationRecord, + partToString, } from '@google/gemini-cli-core'; import * as fs from 'node:fs/promises'; import path from 'node:path'; @@ -171,7 +171,7 @@ export const extractFirstUserMessage = (messages: MessageRecord[]): string => { const userMessage = messages // First try filtering out slash commands. .filter((msg) => { - const content = partListUnionToString(msg.content); + const content = partToString(msg.content); return ( !content.startsWith('/') && !content.startsWith('?') && @@ -186,9 +186,9 @@ export const extractFirstUserMessage = (messages: MessageRecord[]): string => { // Fallback to first user message even if it's a slash command const firstMsg = messages.find((msg) => msg.type === 'user'); if (!firstMsg) return 'Empty conversation'; - content = cleanMessage(partListUnionToString(firstMsg.content)); + content = cleanMessage(partToString(firstMsg.content)); } else { - content = cleanMessage(partListUnionToString(userMessage.content)); + content = cleanMessage(partToString(userMessage.content)); } return content; @@ -312,14 +312,14 @@ export const getAllSessionFiles = async ( if (options.includeFullContent) { fullContent = content.messages - .map((msg) => partListUnionToString(msg.content)) + .map((msg) => partToString(msg.content)) .join(' '); messages = content.messages.map((msg) => ({ role: msg.type === 'user' ? ('user' as const) : ('assistant' as const), - content: partListUnionToString(msg.content), + content: partToString(msg.content), })); } @@ -601,9 +601,9 @@ export function convertSessionToHistoryFormats( // Add the message only if it has content const displayContentString = msg.displayContent - ? partListUnionToString(msg.displayContent) + ? partToString(msg.displayContent) : undefined; - const contentString = partListUnionToString(msg.content); + const contentString = partToString(msg.content); const uiText = displayContentString || contentString; if (uiText.trim()) {