Do not call nextSpeakerCheck if there was an error processing the stream. (#7048)

Co-authored-by: christine betts <chrstn@uw.edu>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Arya Gummadi <aryagummadi@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Shreya Keshive <skeshive@gmail.com>
Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
Co-authored-by: shishu314 <shishu_1998@yahoo.com>
Co-authored-by: Shi Shu <shii@google.com>
Co-authored-by: Steven <steventohme59@gmail.com>
Co-authored-by: Pascal Birchler <pascalb@google.com>
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
Silvio Junior
2025-08-26 00:10:53 -04:00
committed by GitHub
parent 45fff8f9f7
commit 415a36a195
2 changed files with 88 additions and 0 deletions

View File

@@ -51,6 +51,8 @@ vi.mock('./turn', () => {
GeminiEventType: { GeminiEventType: {
MaxSessionTurns: 'MaxSessionTurns', MaxSessionTurns: 'MaxSessionTurns',
ChatCompressed: 'ChatCompressed', ChatCompressed: 'ChatCompressed',
Error: 'error',
Content: 'content',
}, },
}; };
}); });
@@ -1887,6 +1889,89 @@ ${JSON.stringify(
expect(JSON.stringify(finalCall)).toContain('fileC.ts'); expect(JSON.stringify(finalCall)).toContain('fileC.ts');
}); });
}); });
it('should not call checkNextSpeaker when turn.run() yields an error', async () => {
// Arrange
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
const mockStream = (async function* () {
yield {
type: GeminiEventType.Error,
value: { error: { message: 'test error' } },
};
})();
mockTurnRunFn.mockReturnValue(mockStream);
const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
};
client['chat'] = mockChat as GeminiChat;
const mockGenerator: Partial<ContentGenerator> = {
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
generateContent: mockGenerateContentFn,
};
client['contentGenerator'] = mockGenerator as ContentGenerator;
// Act
const stream = client.sendMessageStream(
[{ text: 'Hi' }],
new AbortController().signal,
'prompt-id-error',
);
for await (const _ of stream) {
// consume stream
}
// Assert
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
});
it('should not call checkNextSpeaker when turn.run() yields a value then an error', async () => {
// Arrange
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
const mockStream = (async function* () {
yield { type: GeminiEventType.Content, value: 'some content' };
yield {
type: GeminiEventType.Error,
value: { error: { message: 'test error' } },
};
})();
mockTurnRunFn.mockReturnValue(mockStream);
const mockChat: Partial<GeminiChat> = {
addHistory: vi.fn(),
getHistory: vi.fn().mockReturnValue([]),
};
client['chat'] = mockChat as GeminiChat;
const mockGenerator: Partial<ContentGenerator> = {
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
generateContent: mockGenerateContentFn,
};
client['contentGenerator'] = mockGenerator as ContentGenerator;
// Act
const stream = client.sendMessageStream(
[{ text: 'Hi' }],
new AbortController().signal,
'prompt-id-error',
);
for await (const _ of stream) {
// consume stream
}
// Assert
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
});
}); });
describe('generateContent', () => { describe('generateContent', () => {

View File

@@ -513,6 +513,9 @@ export class GeminiClient {
return turn; return turn;
} }
yield event; yield event;
if (event.type === GeminiEventType.Error) {
return turn;
}
} }
if (!turn.pendingToolCalls.length && signal && !signal.aborted) { if (!turn.pendingToolCalls.length && signal && !signal.aborted) {
// Check if model was switched during the call (likely due to quota error) // Check if model was switched during the call (likely due to quota error)