diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 111728ea59..aeba97d568 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -29,6 +29,7 @@ they appear in the UI. | Enable Auto Update | `general.enableAutoUpdate` | Enable automatic updates. | `true` | | 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` | +| 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 | `false` | | Keep chat history | `general.sessionRetention.maxAge` | Automatically delete chats older than this time period (e.g., "30d", "7d", "24h", "1w") | `undefined` | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b069b03fc2..ca71753e60 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -142,6 +142,11 @@ their corresponding top-level category object in your `settings.json` file. request" errors. - **Default:** `false` +- **`general.maxAttempts`** (number): + - **Description:** Maximum number of attempts for requests to the main chat + model. Cannot exceed 10. + - **Default:** `10` + - **`general.debugKeystrokeLogging`** (boolean): - **Description:** Enable debug logging of keystrokes to the console. - **Default:** `false` diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 17c51d4e21..89cecd8f59 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -297,6 +297,16 @@ const SETTINGS_SCHEMA = { 'Retry on "exception TypeError: fetch failed sending request" errors.', showInDialog: false, }, + maxAttempts: { + type: 'number', + label: 'Max Chat Model Attempts', + category: 'General', + requiresRestart: false, + default: 10, + description: + 'Maximum number of attempts for requests to the main chat model. Cannot exceed 10.', + showInDialog: true, + }, debugKeystrokeLogging: { type: 'boolean', label: 'Debug Keystroke Logging', 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 09b45dfec7..e5a2a10cd6 100644 --- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap @@ -25,15 +25,15 @@ exports[`SettingsDialog > Initial Rendering > should render settings list with v │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -72,15 +72,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -119,15 +119,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings d │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -166,15 +166,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'default state' correct │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -213,15 +213,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'file filtering setting │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -260,15 +260,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selec │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ > Apply To │ @@ -307,15 +307,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and numb │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -354,15 +354,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ @@ -401,15 +401,15 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin │ Plan Directory undefined │ │ The directory where planning artifacts are stored. If not specified, defaults t… │ │ │ +│ 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. │ │ │ │ Enable Session Cleanup false │ │ Enable automatic session cleanup │ │ │ -│ Keep chat history undefined │ -│ Automatically delete chats older than this time period (e.g., "30d", "7d", "24h… │ -│ │ │ ▼ │ │ │ │ Apply To │ diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 7a008f12a6..b62b1a1fc7 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -8,6 +8,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import type { Mock } from 'vitest'; import type { ConfigParameters, SandboxConfig } from './config.js'; import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from './config.js'; +import { DEFAULT_MAX_ATTEMPTS } from '../utils/retry.js'; import { ExperimentFlags } from '../code_assist/experiments/flagNames.js'; import { debugLogger } from '../utils/debugLogger.js'; import { ApprovalMode } from '../policy/types.js'; @@ -259,6 +260,29 @@ describe('Server Config (config.ts)', () => { usageStatisticsEnabled: false, }; + describe('maxAttempts', () => { + it('should default to DEFAULT_MAX_ATTEMPTS', () => { + const config = new Config(baseParams); + expect(config.getMaxAttempts()).toBe(DEFAULT_MAX_ATTEMPTS); + }); + + it('should use provided maxAttempts if <= DEFAULT_MAX_ATTEMPTS', () => { + const config = new Config({ + ...baseParams, + maxAttempts: 5, + }); + expect(config.getMaxAttempts()).toBe(5); + }); + + it('should cap maxAttempts at DEFAULT_MAX_ATTEMPTS', () => { + const config = new Config({ + ...baseParams, + maxAttempts: 20, + }); + expect(config.getMaxAttempts()).toBe(DEFAULT_MAX_ATTEMPTS); + }); + }); + beforeEach(() => { // Reset mocks if necessary vi.clearAllMocks(); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 0f03c03db0..2a73610118 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -291,6 +291,7 @@ export interface ExtensionInstallMetadata { allowPreRelease?: boolean; } +import { DEFAULT_MAX_ATTEMPTS } from '../utils/retry.js'; import type { FileFilteringOptions } from './constants.js'; import { DEFAULT_FILE_FILTERING_OPTIONS, @@ -476,6 +477,7 @@ export interface ConfigParameters { disableModelRouterForAuth?: AuthType[]; continueOnFailedApiCall?: boolean; retryFetchErrors?: boolean; + maxAttempts?: number; enableShellOutputEfficiency?: boolean; shellToolInactivityTimeout?: number; fakeResponses?: string; @@ -657,6 +659,7 @@ export class Config { private readonly outputSettings: OutputSettings; private readonly continueOnFailedApiCall: boolean; private readonly retryFetchErrors: boolean; + private readonly maxAttempts: number; private readonly enableShellOutputEfficiency: boolean; private readonly shellToolInactivityTimeout: number; readonly fakeResponses?: string; @@ -879,6 +882,10 @@ export class Config { format: params.output?.format ?? OutputFormat.TEXT, }; this.retryFetchErrors = params.retryFetchErrors ?? false; + this.maxAttempts = Math.min( + params.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, + DEFAULT_MAX_ATTEMPTS, + ); this.disableYoloMode = params.disableYoloMode ?? false; this.rawOutput = params.rawOutput ?? false; this.acceptRawOutputRisk = params.acceptRawOutputRisk ?? false; @@ -2415,6 +2422,10 @@ export class Config { return this.retryFetchErrors; } + getMaxAttempts(): number { + return this.maxAttempts; + } + getEnableShellOutputEfficiency(): boolean { return this.enableShellOutputEfficiency; } diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index 8a6b3f8bc8..bfcb803a95 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -153,6 +153,7 @@ describe('GeminiChat', () => { }), getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator), getRetryFetchErrors: vi.fn().mockReturnValue(false), + getMaxAttempts: vi.fn().mockReturnValue(10), getUserTier: vi.fn().mockReturnValue(undefined), modelConfigService: { getResolvedConfig: vi.fn().mockImplementation((modelConfigKey) => { diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index c9cb6cf8f2..b7319c8afd 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -18,11 +18,7 @@ import type { } from '@google/genai'; import { toParts } from '../code_assist/converter.js'; import { createUserContent, FinishReason } from '@google/genai'; -import { - retryWithBackoff, - isRetryableError, - DEFAULT_MAX_ATTEMPTS, -} from '../utils/retry.js'; +import { retryWithBackoff, isRetryableError } from '../utils/retry.js'; import type { ValidationRequiredError } from '../utils/googleQuotaErrors.js'; import type { Config } from '../config/config.js'; import { @@ -635,12 +631,12 @@ export class GeminiChat { authType: this.config.getContentGeneratorConfig()?.authType, retryFetchErrors: this.config.getRetryFetchErrors(), signal: abortSignal, - maxAttempts: availabilityMaxAttempts, + maxAttempts: availabilityMaxAttempts ?? this.config.getMaxAttempts(), getAvailabilityContext, onRetry: (attempt, error, delayMs) => { coreEvents.emitRetryAttempt({ attempt, - maxAttempts: availabilityMaxAttempts ?? DEFAULT_MAX_ATTEMPTS, + maxAttempts: availabilityMaxAttempts ?? this.config.getMaxAttempts(), delayMs, error: error instanceof Error ? error.message : String(error), model: lastModelToUse, diff --git a/packages/core/src/core/geminiChat_network_retry.test.ts b/packages/core/src/core/geminiChat_network_retry.test.ts index 519ef3ee14..161cadaf52 100644 --- a/packages/core/src/core/geminiChat_network_retry.test.ts +++ b/packages/core/src/core/geminiChat_network_retry.test.ts @@ -94,6 +94,7 @@ describe('GeminiChat Network Retries', () => { getToolRegistry: vi.fn().mockReturnValue({ getTool: vi.fn() }), getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator), getRetryFetchErrors: vi.fn().mockReturnValue(false), // Default false + getMaxAttempts: vi.fn().mockReturnValue(10), modelConfigService: { getResolvedConfig: vi.fn().mockImplementation((modelConfigKey) => ({ model: modelConfigKey.model, diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 9cc8f1f71b..838db4736f 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -128,6 +128,13 @@ "default": false, "type": "boolean" }, + "maxAttempts": { + "title": "Max Chat Model Attempts", + "description": "Maximum number of attempts for requests to the main chat model. Cannot exceed 10.", + "markdownDescription": "Maximum number of attempts for requests to the main chat model. Cannot exceed 10.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `10`", + "default": 10, + "type": "number" + }, "debugKeystrokeLogging": { "title": "Debug Keystroke Logging", "description": "Enable debug logging of keystrokes to the console.",