From 0c5eae4d35f8c5688e7faa2135a16d7f2a7828e7 Mon Sep 17 00:00:00 2001 From: davidapierce Date: Wed, 22 Jul 2026 21:48:27 +0000 Subject: [PATCH] Update filtering out thought parts from getHistoryTurns --- packages/core/src/core/geminiChat.test.ts | 56 +++++++++++++++++++++++ packages/core/src/core/geminiChat.ts | 50 ++++++++++++++++---- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index 4483585202..6780c9c0ea 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -2282,6 +2282,62 @@ describe('GeminiChat', () => { text: 'actual conversational response', }); }); + + it('should completely filter out thought parts from getHistoryTurns when context management is disabled but model is gemini-2/modern', () => { + vi.mocked(mockConfig.isContextManagementEnabled).mockReturnValue(false); + vi.mocked(mockConfig.getModel).mockReturnValue('gemini-2.5-pro'); + + chat.setHistory([ + { + role: 'user', + parts: [{ text: 'hello' }], + }, + { + role: 'model', + parts: [ + { text: 'internal monologue', thought: true } as unknown as Part, + { text: 'actual conversational response' }, + ], + }, + ]); + + const turns = chat.getHistoryTurns(true); + + expect(turns).toHaveLength(2); + const modelTurn = turns[1]; + expect(modelTurn.content.parts).toHaveLength(1); + expect(modelTurn.content.parts![0]).toEqual({ + text: 'actual conversational response', + }); + }); + + it('should completely filter out thought parts from getHistoryTurns when model supports modern features', () => { + vi.mocked(mockConfig.isContextManagementEnabled).mockReturnValue(false); + vi.mocked(mockConfig.getModel).mockReturnValue('gemini-3.1-pro-preview'); + + chat.setHistory([ + { + role: 'user', + parts: [{ text: 'hello' }], + }, + { + role: 'model', + parts: [ + { text: 'internal monologue', thought: true } as unknown as Part, + { text: 'actual conversational response' }, + ], + }, + ]); + + const turns = chat.getHistoryTurns(true); + + expect(turns).toHaveLength(2); + const modelTurn = turns[1]; + expect(modelTurn.content.parts).toHaveLength(1); + expect(modelTurn.content.parts![0]).toEqual({ + text: 'actual conversational response', + }); + }); }); describe('ensureActiveLoopHasThoughtSignatures', () => { diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index 5104f42ff5..a43c400f7c 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -30,7 +30,11 @@ import { getRetryErrorType, } from '../utils/retry.js'; import type { ValidationRequiredError } from '../utils/googleQuotaErrors.js'; -import { resolveModel, supportsModernFeatures } from '../config/models.js'; +import { + resolveModel, + supportsModernFeatures, + isGemini2Model, +} from '../config/models.js'; import { hasCycleInSchema } from '../tools/tools.js'; import type { StructuredError } from './turn.js'; import type { CompletedToolCall } from '../scheduler/types.js'; @@ -766,9 +770,10 @@ export class GeminiChat { abortSignal, }; - let contentsToUse: Content[] = supportsModernFeatures(modelToUse) - ? [...contentsForPreviewModel] - : [...requestContents]; + let contentsToUse: Content[] = + supportsModernFeatures(modelToUse) || isGemini2Model(modelToUse) + ? [...contentsForPreviewModel] + : [...requestContents]; const hookSystem = this.context.config.getHookSystem(); if (hookSystem) { @@ -810,9 +815,10 @@ export class GeminiChat { ); lastModelToUse = modelToUse; // Re-evaluate contentsToUse based on the new model's feature support - contentsToUse = supportsModernFeatures(modelToUse) - ? [...contentsForPreviewModel] - : [...requestContents]; + contentsToUse = + supportsModernFeatures(modelToUse) || isGemini2Model(modelToUse) + ? [...contentsForPreviewModel] + : [...requestContents]; } if (beforeModelResult.modifiedConfig) { Object.assign(config, beforeModelResult.modifiedConfig); @@ -956,9 +962,16 @@ export class GeminiChat { ? extractCuratedHistory(this.agentHistory.get()) : [...this.agentHistory.get()]; - return this.context.config.isContextManagementEnabled() - ? scrubHistory(history) - : history; + if (this.context.config.isContextManagementEnabled()) { + return scrubHistory(history); + } + + const model = this.context.config.getModel(); + if (isGemini2Model(model) || supportsModernFeatures(model)) { + return stripThoughts(history); + } + + return history; } /** @@ -1503,3 +1516,20 @@ export function coalesceConsecutiveRoles( } return result; } + +export function stripThoughts(history: HistoryTurn[]): HistoryTurn[] { + return history.map((turn) => { + if (!turn.content.parts) return turn; + const hasThought = turn.content.parts.some((p) => p && p.thought); + if (!hasThought) return turn; + + const nonThoughtParts = turn.content.parts.filter((p) => p && !p.thought); + return { + id: turn.id, + content: { + ...turn.content, + parts: nonThoughtParts, + }, + }; + }); +}