fix(cli): filter out tool response metadata from resumed session history

This commit is contained in:
Jack Wotherspoon
2026-06-02 13:55:44 -04:00
parent e440e02866
commit 30efe0b702
2 changed files with 63 additions and 8 deletions
@@ -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');
});
});
+8 -8
View File
@@ -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()) {