diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index cb57276fee..86f7e21685 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -163,46 +163,57 @@ function extractCuratedHistory(comprehensiveHistory: Content[]): Content[] { return []; } const curatedHistory: Content[] = []; - const length = comprehensiveHistory.length; - let i = 0; - while (i < length) { - const role = comprehensiveHistory[i].role; - const parts: Part[] = []; - let isValid = true; - while (i < length && comprehensiveHistory[i].role === role) { - if ( - isValid && - role === 'model' && - !isValidContent(comprehensiveHistory[i]) - ) { - isValid = false; + for (const entry of comprehensiveHistory) { + const role = entry.role; + const rawParts = entry.parts || []; + + // Filter parts: keep only non-empty text or non-text parts + const validParts = rawParts.filter((part) => { + if (part === undefined || Object.keys(part).length === 0) { + return false; } + if (!part.thought && part.text !== undefined && part.text.trim() === '') { + return false; + } + return true; + }); - const entryParts = comprehensiveHistory[i].parts || []; - for (const part of entryParts) { + if (validParts.length === 0) { + continue; + } + + const lastEntry = curatedHistory[curatedHistory.length - 1]; + if (lastEntry && lastEntry.role === role) { + const lastEntryParts = lastEntry.parts || []; + // Merge into last entry + for (const part of validParts) { if ( - parts.length > 0 && - parts[parts.length - 1].text !== undefined && + lastEntryParts.length > 0 && + lastEntryParts[lastEntryParts.length - 1].text !== undefined && part.text !== undefined ) { - // Merge consecutive text parts using a new object to avoid corruption - parts[parts.length - 1] = { - ...parts[parts.length - 1], - text: parts[parts.length - 1].text + '\n' + part.text, + // Coalesce text parts + const lastText = lastEntryParts[lastEntryParts.length - 1].text!; + const separator = lastText.endsWith('\n') ? '' : '\n'; + lastEntryParts[lastEntryParts.length - 1] = { + ...lastEntryParts[lastEntryParts.length - 1], + text: lastText + separator + part.text, }; } else { - // Deep copy the part to avoid reference issues - parts.push({ ...part }); + lastEntryParts.push({ ...part }); } } - i++; - } - - if (isValid && parts.length > 0) { - curatedHistory.push({ role, parts }); + lastEntry.parts = lastEntryParts; + } else { + // Create new entry with deep-copied parts + curatedHistory.push({ + role, + parts: validParts.map((p) => ({ ...p })), + }); } } + return curatedHistory; } export class InvalidStreamError extends Error { diff --git a/packages/test-utils/src/test-rig.ts b/packages/test-utils/src/test-rig.ts index 1d189ec51a..d91770232f 100644 --- a/packages/test-utils/src/test-rig.ts +++ b/packages/test-utils/src/test-rig.ts @@ -12,7 +12,7 @@ import { fileURLToPath } from 'node:url'; import { env } from 'node:process'; import { setTimeout as sleep } from 'node:timers/promises'; import { - DEFAULT_GEMINI_FLASH_MODEL, + PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, GEMINI_DIR, } from '@google/gemini-cli-core'; export { GEMINI_DIR }; @@ -460,7 +460,7 @@ export class TestRig { ...(env['GEMINI_TEST_TYPE'] === 'integration' ? { model: { - name: DEFAULT_GEMINI_FLASH_MODEL, + name: PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, }, } : {}),