From 0486a1675a19d89d5e1369e1b71fd80152c936ba Mon Sep 17 00:00:00 2001 From: Yuna Seol Date: Tue, 10 Mar 2026 10:29:35 -0400 Subject: [PATCH] refactor(core): improve API response error logging when retry (#21784) --- packages/core/src/core/geminiChat.test.ts | 1 + packages/core/src/core/geminiChat.ts | 31 +++++++------------ .../src/core/geminiChat_network_retry.test.ts | 1 + 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index 105d70e49f..9c527dbc52 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -1154,6 +1154,7 @@ describe('GeminiChat', () => { 1, ); expect(mockLogContentRetry).not.toHaveBeenCalled(); + expect(mockLogContentRetryFailure).toHaveBeenCalledTimes(1); }); it('should yield a RETRY event when an invalid stream is encountered', async () => { diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index 1c0f1a5685..44a28c83a5 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -344,8 +344,6 @@ export class GeminiChat { this: GeminiChat, ): AsyncGenerator { try { - let lastError: unknown = new Error('Request failed after all retries.'); - const maxAttempts = INVALID_CONTENT_RETRY_OPTIONS.maxAttempts; for (let attempt = 0; attempt < maxAttempts; attempt++) { @@ -374,15 +372,13 @@ export class GeminiChat { yield { type: StreamEventType.CHUNK, value: chunk }; } - lastError = null; - break; + return; } catch (error) { if (error instanceof AgentExecutionStoppedError) { yield { type: StreamEventType.AGENT_EXECUTION_STOPPED, reason: error.reason, }; - lastError = null; // Clear error as this is an expected stop return; // Stop the generator } @@ -397,7 +393,6 @@ export class GeminiChat { value: error.syntheticResponse, }; } - lastError = null; // Clear error as this is an expected stop return; // Stop the generator } @@ -415,8 +410,9 @@ export class GeminiChat { } // Fall through to retry logic for retryable connection errors } - lastError = error; + const isContentError = error instanceof InvalidStreamError; + const errorType = isContentError ? error.type : 'NETWORK_ERROR'; if ( (isContentError && isGemini2Model(model)) || @@ -425,11 +421,10 @@ export class GeminiChat { // Check if we have more attempts left. if (attempt < maxAttempts - 1) { const delayMs = INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs; - const retryType = isContentError ? error.type : 'NETWORK_ERROR'; logContentRetry( this.config, - new ContentRetryEvent(attempt, retryType, delayMs, model), + new ContentRetryEvent(attempt, errorType, delayMs, model), ); coreEvents.emitRetryAttempt({ attempt: attempt + 1, @@ -444,21 +439,19 @@ export class GeminiChat { continue; } } - break; - } - } - if (lastError) { - if ( - lastError instanceof InvalidStreamError && - isGemini2Model(model) - ) { + // If we've aborted, we throw without logging a failure. + if (signal.aborted) { + throw error; + } + logContentRetryFailure( this.config, - new ContentRetryFailureEvent(maxAttempts, lastError.type, model), + new ContentRetryFailureEvent(attempt + 1, errorType, model), ); + + throw error; } - throw lastError; } } finally { streamDoneResolver!(); diff --git a/packages/core/src/core/geminiChat_network_retry.test.ts b/packages/core/src/core/geminiChat_network_retry.test.ts index 1a73b236a2..78b23d54f6 100644 --- a/packages/core/src/core/geminiChat_network_retry.test.ts +++ b/packages/core/src/core/geminiChat_network_retry.test.ts @@ -401,6 +401,7 @@ describe('GeminiChat Network Retries', () => { // Should only be called once (no retry) expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(1); + expect(mockLogContentRetryFailure).not.toHaveBeenCalled(); }); it('should retry on SSL error during stream iteration (mid-stream failure)', async () => {