React to Gemini API break - Thought Inclusion (#705)

This commit is contained in:
N. Taylor Mullen 2025-06-02 16:32:45 -07:00 committed by GitHub
parent 6fb07f0b50
commit 8563e46ade
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 0 deletions

View File

@ -298,5 +298,79 @@ describe('GeminiChat', () => {
expect(history[0]).toEqual(userInput);
expect(history[1]).toEqual(modelOutput[0]);
});
it('should skip "thought" content from modelOutput', () => {
const modelOutputWithThought: Content[] = [
{ role: 'model', parts: [{ thought: true }, { text: 'Visible text' }] },
{ role: 'model', parts: [{ text: 'Another visible text' }] },
];
// @ts-expect-error Accessing private method for testing purposes
chat.recordHistory(userInput, modelOutputWithThought);
const history = chat.getHistory();
expect(history.length).toBe(2); // User input + consolidated model output
expect(history[0]).toEqual(userInput);
expect(history[1].role).toBe('model');
// The 'thought' part is skipped, 'Another visible text' becomes the first part.
expect(history[1].parts).toEqual([{ text: 'Another visible text' }]);
});
it('should skip "thought" content even if it is the only content', () => {
const modelOutputOnlyThought: Content[] = [
{ role: 'model', parts: [{ thought: true }] },
];
// @ts-expect-error Accessing private method for testing purposes
chat.recordHistory(userInput, modelOutputOnlyThought);
const history = chat.getHistory();
expect(history.length).toBe(1); // User input + default empty model part
expect(history[0]).toEqual(userInput);
});
it('should correctly consolidate text parts when a thought part is in between', () => {
const modelOutputMixed: Content[] = [
{ role: 'model', parts: [{ text: 'Part 1.' }] },
{
role: 'model',
parts: [{ thought: true }, { text: 'Should be skipped' }],
},
{ role: 'model', parts: [{ text: 'Part 2.' }] },
];
// @ts-expect-error Accessing private method for testing purposes
chat.recordHistory(userInput, modelOutputMixed);
const history = chat.getHistory();
expect(history.length).toBe(2);
expect(history[0]).toEqual(userInput);
expect(history[1].role).toBe('model');
expect(history[1].parts).toEqual([{ text: 'Part 1.Part 2.' }]);
});
it('should handle multiple thought parts correctly', () => {
const modelOutputMultipleThoughts: Content[] = [
{ role: 'model', parts: [{ thought: true }] },
{ role: 'model', parts: [{ text: 'Visible 1' }] },
{ role: 'model', parts: [{ thought: true }] },
{ role: 'model', parts: [{ text: 'Visible 2' }] },
];
// @ts-expect-error Accessing private method for testing purposes
chat.recordHistory(userInput, modelOutputMultipleThoughts);
const history = chat.getHistory();
expect(history.length).toBe(2);
expect(history[0]).toEqual(userInput);
expect(history[1].role).toBe('model');
expect(history[1].parts).toEqual([{ text: 'Visible 1Visible 2' }]);
});
it('should handle thought part at the end of outputContents', () => {
const modelOutputThoughtAtEnd: Content[] = [
{ role: 'model', parts: [{ text: 'Visible text' }] },
{ role: 'model', parts: [{ thought: true }] },
];
// @ts-expect-error Accessing private method for testing purposes
chat.recordHistory(userInput, modelOutputThoughtAtEnd);
const history = chat.getHistory();
expect(history.length).toBe(2);
expect(history[0]).toEqual(userInput);
expect(history[1].role).toBe('model');
expect(history[1].parts).toEqual([{ text: 'Visible text' }]);
});
});
});

View File

@ -342,6 +342,10 @@ export class GeminiChat {
// Consolidate adjacent model roles in outputContents
const consolidatedOutputContents: Content[] = [];
for (const content of outputContents) {
if (this.isThoughtContent(content)) {
continue;
}
const lastContent =
consolidatedOutputContents[consolidatedOutputContents.length - 1];
if (this.isTextContent(lastContent) && this.isTextContent(content)) {
@ -394,4 +398,17 @@ export class GeminiChat {
content.parts[0].text !== ''
);
}
private isThoughtContent(
content: Content | undefined,
): content is Content & { parts: [{ thought: boolean }, ...Part[]] } {
return !!(
content &&
content.role === 'model' &&
content.parts &&
content.parts.length > 0 &&
typeof content.parts[0].thought === 'boolean' &&
content.parts[0].thought === true
);
}
}