fix(core): improve chat history merging and switch to stable preview flash model

- Refactor GeminiChat history merging to handle consecutive turns of any role.
- Implement part-level filtering to remove empty text segments while preserving tool calls.
- Switch integration tests to 'gemini-3.1-flash-lite-preview' for improved stability.
- Ensure crash report context follows alternating role rules.
This commit is contained in:
Sehoon Shon
2026-03-31 21:35:13 -04:00
parent 4bb389efb7
commit 428a4808db
2 changed files with 41 additions and 30 deletions
+39 -28
View File
@@ -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 {
+2 -2
View File
@@ -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,
},
}
: {}),