Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bc234de
fix(core): filter out thought parts from request history to prevent l…
amelidev Jun 15, 2026
92dc783
fix(core): strip thoughts from scrubbed history turns and add unit tests
amelidev Jun 16, 2026
2d0b4e6
Update packages/core/src/utils/historyHardening.ts
amelidev Jun 16, 2026
e948639
Merge branch 'main' into b_470967254
amelidev Jun 16, 2026
d4307ff
Update packages/core/src/utils/historyHardening.ts
amelidev Jun 16, 2026
cef5800
Update packages/core/src/utils/historyHardening.ts
amelidev Jun 16, 2026
f80df54
Merge branch 'main' into b_470967254
amelidev Jun 22, 2026
729f215
Merge branch 'main' into b_470967254
galdawave Jun 23, 2026
dffbb33
refactor(core): address PR feedback on history hardening allocations …
amelidev Jun 24, 2026
84aadfa
Merge branch 'main' into b_470967254
amelidev Jun 25, 2026
26fc9e4
Update packages/core/src/utils/historyHardening.ts
amelidev Jun 25, 2026
a868d97
fix(core): filter out empty content turns in scrubContents
amelidev Jun 25, 2026
3cfe893
Merge branch 'main' into b_470967254
amelidev Jun 25, 2026
0fa93d8
refactor(core): cast Part to ThoughtPart in isInternalThought for str…
amelidev Jun 25, 2026
98ff56f
Merge branch 'main' into b_470967254
DavidAPierce Jun 26, 2026
2006d25
fix(core): coalesce adjacent same-role turns in scrubContents
amelidev Jun 29, 2026
c1e4e6c
Update packages/core/src/utils/historyHardening.ts
amelidev Jun 30, 2026
e5f9a0f
Apply suggestions from code review
amelidev Jun 30, 2026
e9751e4
Merge branch 'main' into b_470967254
DavidAPierce Jul 1, 2026
4342c23
Merge branch 'main' into b_470967254
DavidAPierce Jul 6, 2026
1cbe049
Merge branch 'main' into b_470967254
DavidAPierce Jul 6, 2026
858710d
Merge branch 'main' into b_470967254
DavidAPierce Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 3 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions packages/core/src/core/geminiChat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,35 @@ describe('GeminiChat', () => {
});
});

describe('thought leakage in getHistoryTurns', () => {
it('should completely filter out thought parts from getHistoryTurns when context management is enabled', () => {
vi.mocked(mockConfig.isContextManagementEnabled).mockReturnValue(true);

chat.setHistory([
{
role: 'user',
parts: [{ text: 'hello' }],
},
{
role: 'model',
Comment thread
amelidev marked this conversation as resolved.
parts: [
{ text: 'internal monologue', thought: true },
{ 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', () => {
it('should add thoughtSignature to the first functionCall in each model turn of the active loop', () => {
const chat = new GeminiChat(mockConfig, '', [], []);
Expand Down
85 changes: 85 additions & 0 deletions packages/core/src/utils/historyHardening.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,89 @@ describe('hardenHistory', () => {
expect(hardened[0].content.parts![0]).not.toHaveProperty('extraProp');
expect(hardened[0].content.parts![0]).toHaveProperty('text', 'hello');
});

it('should completely filter out thought parts from the scrubbed history', () => {
const history: HistoryTurn[] = [
{
id: '1',
content: {
role: 'user',
parts: [{ text: 'User prompt' }],
},
},
{
id: '2',
content: {
role: 'model',
parts: [
{
text: 'Previous model thought...',
thought: true,
} as unknown as Part,
{ text: 'Actual conversational text response' },
],
},
},
{
id: '3',
content: {
role: 'user',
parts: [{ text: 'User follow-up prompt' }],
},
},
];

const hardened = hardenHistory(history);
// Model turn (Turn 2, index 1 in hardened) should only contain the actual conversational text part
const modelTurn = hardened[1];
expect(modelTurn.content.parts).toHaveLength(1);
expect(modelTurn.content.parts![0]).toHaveProperty(
'text',
'Actual conversational text response',
);
expect(modelTurn.content.parts![0]).not.toHaveProperty('thought');
});

it('should remove the entire turn if it only contained thought parts and is now empty', () => {
const history: HistoryTurn[] = [
{
id: '1',
content: {
role: 'user',
parts: [{ text: 'User prompt' }],
},
},
{
id: '2',
content: {
role: 'model',
parts: [
{
text: 'Model is just thinking internally...',
thought: true,
} as unknown as Part,
],
},
},
{
id: '3',
content: {
role: 'user',
parts: [{ text: 'User follow-up prompt' }],
},
},
];

const hardened = hardenHistory(history);
// After scrubbing, Turn 2 should have 0 parts.
// The history mapping filters out empty turns, so the total turns should coalesce and reduce to 1 coalesced user turn.
// Let's inspect the hardened array:
// User prompt (Turn 1) + User follow-up prompt (Turn 3) will be coalesced into 1 User turn.
expect(hardened).toHaveLength(1);
expect(hardened[0].content.role).toBe('user');
expect(hardened[0].content.parts).toEqual([
{ text: 'User prompt' },
{ text: 'User follow-up prompt' },
]);
});
});
Loading