diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 33f585ca2a..2e626c9101 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -30,6 +30,7 @@ they appear in the UI. | Enable Notifications | `general.enableNotifications` | Enable run-event notifications for action-required prompts and session completion. Currently macOS only. | `false` | | Plan Directory | `general.plan.directory` | The directory where planning artifacts are stored. If not specified, defaults to the system temporary directory. | `undefined` | | Plan Model Routing | `general.plan.modelRouting` | Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pro for the planning phase and Flash for the implementation phase. | `true` | +| Retry Fetch Errors | `general.retryFetchErrors` | Retry on "exception TypeError: fetch failed sending request" errors. | `true` | | Max Chat Model Attempts | `general.maxAttempts` | Maximum number of attempts for requests to the main chat model. Cannot exceed 10. | `10` | | Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` | | Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `true` | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 7d0febcf40..55571678de 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -146,7 +146,7 @@ their corresponding top-level category object in your `settings.json` file. - **`general.retryFetchErrors`** (boolean): - **Description:** Retry on "exception TypeError: fetch failed sending request" errors. - - **Default:** `false` + - **Default:** `true` - **`general.maxAttempts`** (number): - **Description:** Maximum number of attempts for requests to the main chat diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index facded0e6f..597aba3969 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -306,10 +306,10 @@ const SETTINGS_SCHEMA = { label: 'Retry Fetch Errors', category: 'General', requiresRestart: false, - default: false, + default: true, description: 'Retry on "exception TypeError: fetch failed sending request" errors.', - showInDialog: false, + showInDialog: true, }, maxAttempts: { type: 'number', diff --git a/packages/cli/src/test-utils/mockConfig.ts b/packages/cli/src/test-utils/mockConfig.ts index c8ab45a35d..86479dda89 100644 --- a/packages/cli/src/test-utils/mockConfig.ts +++ b/packages/cli/src/test-utils/mockConfig.ts @@ -125,7 +125,7 @@ export const createMockConfig = (overrides: Partial = {}): Config => getEnableInteractiveShell: vi.fn().mockReturnValue(false), getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false), getContinueOnFailedApiCall: vi.fn().mockReturnValue(false), - getRetryFetchErrors: vi.fn().mockReturnValue(false), + getRetryFetchErrors: vi.fn().mockReturnValue(true), getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true), getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000), getShellExecutionConfig: vi.fn().mockReturnValue({}), diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Initial-Rendering-should-render-settings-list-with-visual-indicators.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Initial-Rendering-should-render-settings-list-with-visual-indicators.snap.svg index 96ac9e7621..fc567671b8 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Initial-Rendering-should-render-settings-list-with-visual-indicators.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Initial-Rendering-should-render-settings-list-with-visual-indicators.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-accessibility-settings-enabled-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-accessibility-settings-enabled-correctly.snap.svg index 7a35e051b2..a01eae091d 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-accessibility-settings-enabled-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-accessibility-settings-enabled-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-all-boolean-settings-disabled-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-all-boolean-settings-disabled-correctly.snap.svg index 9c01031ebe..d777591e70 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-all-boolean-settings-disabled-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-all-boolean-settings-disabled-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false* + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-default-state-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-default-state-correctly.snap.svg index 96ac9e7621..fc567671b8 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-default-state-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-default-state-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-file-filtering-settings-configured-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-file-filtering-settings-configured-correctly.snap.svg index 96ac9e7621..fc567671b8 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-file-filtering-settings-configured-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-file-filtering-settings-configured-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-focused-on-scope-selector-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-focused-on-scope-selector-correctly.snap.svg index f9cf782f72..3d11268eff 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-focused-on-scope-selector-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-focused-on-scope-selector-correctly.snap.svg @@ -83,20 +83,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-mixed-boolean-and-number-settings-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-mixed-boolean-and-number-settings-correctly.snap.svg index 1866d1ab67..0f619971c1 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-mixed-boolean-and-number-settings-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-mixed-boolean-and-number-settings-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-tools-and-security-settings-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-tools-and-security-settings-correctly.snap.svg index 96ac9e7621..fc567671b8 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-tools-and-security-settings-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-tools-and-security-settings-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - false + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-various-boolean-settings-enabled-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-various-boolean-settings-enabled-correctly.snap.svg index 739a96cf09..3a7a0580ff 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-various-boolean-settings-enabled-correctly.snap.svg +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog-SettingsDialog-Snapshot-Tests-should-render-various-boolean-settings-enabled-correctly.snap.svg @@ -94,20 +94,20 @@ - Max Chat Model Attempts - 10 + Retry Fetch Errors + true - Maximum number of attempts for requests to the main chat model. Cannot exceed 10. + Retry on "exception TypeError: fetch failed sending request" errors. - Debug Keystroke Logging - true* + Max Chat Model Attempts + 10 - Enable debug logging of keystrokes to the console. + Maximum number of attempts for requests to the main chat model. Cannot exceed 10. diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap index be2dd8d9a2..19158681b2 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap @@ -28,12 +28,12 @@ exports[`SettingsDialog > Initial Rendering > should render settings list with v │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -74,12 +74,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -120,12 +120,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings d │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false* │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -166,12 +166,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'default state' correct │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -212,12 +212,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'file filtering setting │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -258,12 +258,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selec │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ > Apply To │ @@ -304,12 +304,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and numb │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -350,12 +350,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging false │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -396,12 +396,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin │ Plan Model Routing true │ │ Automatically switch between Pro and Flash models based on Plan Mode status. Uses Pr… │ │ │ +│ Retry Fetch Errors true │ +│ Retry on "exception TypeError: fetch failed sending request" errors. │ +│ │ │ Max Chat Model Attempts 10 │ │ Maximum number of attempts for requests to the main chat model. Cannot exceed 10. │ │ │ -│ Debug Keystroke Logging true* │ -│ Enable debug logging of keystrokes to the console. │ -│ │ │ ▼ │ │ │ │ Apply To │ diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 36b463495b..bc52050286 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1024,7 +1024,7 @@ export class Config implements McpContext, AgentLoopContext { params.gemmaModelRouter?.classifier?.model ?? 'gemma3-1b-gpu-custom', }, }; - this.retryFetchErrors = params.retryFetchErrors ?? false; + this.retryFetchErrors = params.retryFetchErrors ?? true; this.maxAttempts = Math.min( params.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS, diff --git a/packages/core/src/core/baseLlmClient.test.ts b/packages/core/src/core/baseLlmClient.test.ts index b9711608a7..a35096f528 100644 --- a/packages/core/src/core/baseLlmClient.test.ts +++ b/packages/core/src/core/baseLlmClient.test.ts @@ -118,6 +118,8 @@ describe('BaseLlmClient', () => { .mockReturnValue(createAvailabilityServiceMock()), setActiveModel: vi.fn(), getUserTier: vi.fn().mockReturnValue(undefined), + getRetryFetchErrors: vi.fn().mockReturnValue(true), + getMaxAttempts: vi.fn().mockReturnValue(3), getModel: vi.fn().mockReturnValue('test-model'), getActiveModel: vi.fn().mockReturnValue('test-model'), } as unknown as Mocked; diff --git a/packages/core/src/core/baseLlmClient.ts b/packages/core/src/core/baseLlmClient.ts index 0de4dd1e20..8888a41bbf 100644 --- a/packages/core/src/core/baseLlmClient.ts +++ b/packages/core/src/core/baseLlmClient.ts @@ -21,6 +21,8 @@ import { getErrorMessage } from '../utils/errors.js'; import { logMalformedJsonResponse } from '../telemetry/loggers.js'; import { MalformedJsonResponseEvent, LlmRole } from '../telemetry/types.js'; import { retryWithBackoff } from '../utils/retry.js'; +import { coreEvents } from '../utils/events.js'; +import { getDisplayString } from '../config/models.js'; import type { ModelConfigKey } from '../services/modelConfigService.js'; import { applyModelSelection, @@ -327,6 +329,17 @@ export class BaseLlmClient { : undefined, authType: this.authType ?? this.config.getContentGeneratorConfig()?.authType, + retryFetchErrors: this.config.getRetryFetchErrors(), + onRetry: (attempt, error, delayMs) => { + coreEvents.emitRetryAttempt({ + attempt, + maxAttempts: + availabilityMaxAttempts ?? maxAttempts ?? DEFAULT_MAX_ATTEMPTS, + delayMs, + error: error instanceof Error ? error.message : String(error), + model: getDisplayString(currentModel), + }); + }, }); } catch (error) { if (abortSignal?.aborted) { diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 31e9a0b38d..bd75382095 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -235,6 +235,8 @@ describe('Gemini Client (client.ts)', () => { getDirectories: vi.fn().mockReturnValue(['/test/dir']), }), getGeminiClient: vi.fn(), + getRetryFetchErrors: vi.fn().mockReturnValue(true), + getMaxAttempts: vi.fn().mockReturnValue(3), getModelRouterService: vi .fn() .mockReturnValue(mockRouterService as unknown as ModelRouterService), diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 2a64050dda..49956b4d0d 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -30,6 +30,12 @@ import { getCoreSystemPrompt } from './prompts.js'; import { checkNextSpeaker } from '../utils/nextSpeakerChecker.js'; import { reportError } from '../utils/errorReporting.js'; import { GeminiChat } from './geminiChat.js'; +import { coreEvents, CoreEvent } from '../utils/events.js'; +import { + getDisplayString, + resolveModel, + isGemini2Model, +} from '../config/models.js'; import { retryWithBackoff, type RetryAvailabilityContext, @@ -70,9 +76,7 @@ import { applyModelSelection, createAvailabilityContextProvider, } from '../availability/policyHelpers.js'; -import { resolveModel, isGemini2Model } from '../config/models.js'; import { partToString } from '../utils/partUtils.js'; -import { coreEvents, CoreEvent } from '../utils/events.js'; const MAX_TURNS = 100; @@ -1093,7 +1097,18 @@ export class GeminiClient { onValidationRequired: onValidationRequiredCallback, authType: this.config.getContentGeneratorConfig()?.authType, maxAttempts: availabilityMaxAttempts, + retryFetchErrors: this.config.getRetryFetchErrors(), getAvailabilityContext, + onRetry: (attempt, error, delayMs) => { + coreEvents.emitRetryAttempt({ + attempt, + maxAttempts: + availabilityMaxAttempts ?? this.config.getMaxAttempts(), + delayMs, + error: error instanceof Error ? error.message : String(error), + model: getDisplayString(currentAttemptModel), + }); + }, }); return result; diff --git a/packages/core/src/tools/web-fetch.test.ts b/packages/core/src/tools/web-fetch.test.ts index 92ba4076b2..0db08c43e0 100644 --- a/packages/core/src/tools/web-fetch.test.ts +++ b/packages/core/src/tools/web-fetch.test.ts @@ -248,6 +248,7 @@ describe('WebFetchTool', () => { getProxy: vi.fn(), getGeminiClient: mockGetGeminiClient, getRetryFetchErrors: vi.fn().mockReturnValue(false), + getMaxAttempts: vi.fn().mockReturnValue(3), getDirectWebFetch: vi.fn().mockReturnValue(false), modelConfigService: { getResolvedConfig: vi.fn().mockImplementation(({ model }) => ({ diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts index 50960a9f7f..a670ebec50 100644 --- a/packages/core/src/tools/web-fetch.ts +++ b/packages/core/src/tools/web-fetch.ts @@ -31,6 +31,7 @@ import { import { LlmRole } from '../telemetry/llmRole.js'; import { WEB_FETCH_TOOL_NAME } from './tool-names.js'; import { debugLogger } from '../utils/debugLogger.js'; +import { coreEvents } from '../utils/events.js'; import { retryWithBackoff } from '../utils/retry.js'; import { WEB_FETCH_DEFINITION } from './definitions/coreTools.js'; import { resolveToolDeclaration } from './definitions/resolver.js'; @@ -186,6 +187,16 @@ class WebFetchToolInvocation extends BaseToolInvocation< super(params, messageBus, _toolName, _toolDisplayName); } + private handleRetry(attempt: number, error: unknown, delayMs: number): void { + coreEvents.emitRetryAttempt({ + attempt, + maxAttempts: this.config.getMaxAttempts(), + delayMs, + error: error instanceof Error ? error.message : String(error), + model: 'Web Fetch', + }); + } + private async executeFallback(signal: AbortSignal): Promise { const { validUrls: urls } = parsePrompt(this.params.prompt!); // For now, we only support one URL for fallback @@ -214,6 +225,8 @@ class WebFetchToolInvocation extends BaseToolInvocation< }, { retryFetchErrors: this.config.getRetryFetchErrors(), + onRetry: (attempt, error, delayMs) => + this.handleRetry(attempt, error, delayMs), }, ); @@ -423,6 +436,8 @@ ${textContent} }, { retryFetchErrors: this.config.getRetryFetchErrors(), + onRetry: (attempt, error, delayMs) => + this.handleRetry(attempt, error, delayMs), }, ); diff --git a/packages/core/src/utils/retry.test.ts b/packages/core/src/utils/retry.test.ts index f63a5ed723..77fce2a1cb 100644 --- a/packages/core/src/utils/retry.test.ts +++ b/packages/core/src/utils/retry.test.ts @@ -350,6 +350,25 @@ describe('retryWithBackoff', () => { expect(mockFn).toHaveBeenCalledTimes(2); }); + it("should retry on 'Incomplete JSON segment' when retryFetchErrors is true", async () => { + const mockFn = vi.fn(); + mockFn.mockRejectedValueOnce( + new Error('Incomplete JSON segment at the end'), + ); + mockFn.mockResolvedValueOnce('success'); + + const promise = retryWithBackoff(mockFn, { + retryFetchErrors: true, + initialDelayMs: 10, + }); + + await vi.runAllTimersAsync(); + + const result = await promise; + expect(result).toBe('success'); + expect(mockFn).toHaveBeenCalledTimes(2); + }); + it('should retry on common network error codes (ECONNRESET)', async () => { const mockFn = vi.fn(); const error = new Error('read ECONNRESET'); diff --git a/packages/core/src/utils/retry.ts b/packages/core/src/utils/retry.ts index a16e823e74..8ce8e1934a 100644 --- a/packages/core/src/utils/retry.ts +++ b/packages/core/src/utils/retry.ts @@ -100,6 +100,7 @@ function getNetworkErrorCode(error: unknown): string | undefined { } const FETCH_FAILED_MESSAGE = 'fetch failed'; +const INCOMPLETE_JSON_MESSAGE = 'incomplete json segment'; /** * Default predicate function to determine if a retry should be attempted. @@ -119,8 +120,12 @@ export function isRetryableError( } if (retryFetchErrors && error instanceof Error) { - // Check for generic fetch failed message (case-insensitive) - if (error.message.toLowerCase().includes(FETCH_FAILED_MESSAGE)) { + const lowerMessage = error.message.toLowerCase(); + // Check for generic fetch failed message or incomplete JSON segment (common stream error) + if ( + lowerMessage.includes(FETCH_FAILED_MESSAGE) || + lowerMessage.includes(INCOMPLETE_JSON_MESSAGE) + ) { return true; } } diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 779ac288db..3d7f64c172 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -131,8 +131,8 @@ "retryFetchErrors": { "title": "Retry Fetch Errors", "description": "Retry on \"exception TypeError: fetch failed sending request\" errors.", - "markdownDescription": "Retry on \"exception TypeError: fetch failed sending request\" errors.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", - "default": false, + "markdownDescription": "Retry on \"exception TypeError: fetch failed sending request\" errors.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `true`", + "default": true, "type": "boolean" }, "maxAttempts": {