diff --git a/src/core/config/ConfigManager.ts b/src/core/config/ConfigManager.ts index dd4262ee2e8..df06a08ea7f 100644 --- a/src/core/config/ConfigManager.ts +++ b/src/core/config/ConfigManager.ts @@ -9,6 +9,9 @@ export interface ApiConfigData { [key: string]: ApiConfiguration } modeApiConfigs?: Partial> + migrations?: { + rateLimitMigrated?: boolean // Flag to track if rate limit migration has been applied + } } export class ConfigManager { @@ -17,8 +20,12 @@ export class ConfigManager { apiConfigs: { default: { id: this.generateId(), + rateLimitSeconds: 0, // Set default rate limit for new installations }, }, + migrations: { + rateLimitMigrated: true, // Mark as migrated for fresh installs + }, } private readonly SCOPE_PREFIX = "roo_cline_config_" @@ -52,8 +59,14 @@ export class ConfigManager { return } - // Migrate: ensure all configs have IDs + // Initialize migrations tracking object if it doesn't exist + if (!config.migrations) { + config.migrations = {} + } + let needsMigration = false + + // Migrate: ensure all configs have IDs for (const [name, apiConfig] of Object.entries(config.apiConfigs)) { if (!apiConfig.id) { apiConfig.id = this.generateId() @@ -61,6 +74,13 @@ export class ConfigManager { } } + // Apply rate limit migration if needed + if (!config.migrations.rateLimitMigrated) { + await this.migrateRateLimit(config) + config.migrations.rateLimitMigrated = true + needsMigration = true + } + if (needsMigration) { await this.writeConfig(config) } @@ -70,6 +90,43 @@ export class ConfigManager { } } + /** + * Migrate rate limit settings from global state to per-profile configuration + */ + private async migrateRateLimit(config: ApiConfigData): Promise { + try { + // Get the global rate limit value from extension state + let rateLimitSeconds: number | undefined + + try { + // Try to get global state rate limit + rateLimitSeconds = await this.context.globalState.get("rateLimitSeconds") + console.log(`[RateLimitMigration] Found global rate limit value: ${rateLimitSeconds}`) + } catch (error) { + console.error("[RateLimitMigration] Error getting global rate limit:", error) + } + + // If no global rate limit, use default value of 5 seconds + if (rateLimitSeconds === undefined) { + rateLimitSeconds = 5 // Default value + console.log(`[RateLimitMigration] Using default rate limit value: ${rateLimitSeconds}`) + } + + // Apply the rate limit to all API configurations + for (const [name, apiConfig] of Object.entries(config.apiConfigs)) { + // Only set if not already configured + if (apiConfig.rateLimitSeconds === undefined) { + console.log(`[RateLimitMigration] Applying rate limit ${rateLimitSeconds}s to profile: ${name}`) + apiConfig.rateLimitSeconds = rateLimitSeconds + } + } + + console.log(`[RateLimitMigration] Migration complete`) + } catch (error) { + console.error(`[RateLimitMigration] Failed to migrate rate limit settings:`, error) + } + } + /** * List all available configs with metadata */ @@ -96,6 +153,48 @@ export class ConfigManager { return await this.lock(async () => { const currentConfig = await this.readConfig() const existingConfig = currentConfig.apiConfigs[name] + + // If this is a new config or doesn't have rate limit, try to apply the global rate limit + if (!existingConfig || config.rateLimitSeconds === undefined) { + // Apply rate limit if not specified explicitly in the config being saved + if (config.rateLimitSeconds === undefined) { + let globalRateLimit: number | undefined + + // First check if we have an existing migrated config to copy from + const anyExistingConfig = Object.values(currentConfig.apiConfigs)[0] + if (anyExistingConfig?.rateLimitSeconds !== undefined) { + globalRateLimit = anyExistingConfig.rateLimitSeconds + console.log( + `[RateLimitMigration] Using existing profile's rate limit value: ${globalRateLimit}s`, + ) + } else { + // Otherwise check global state + try { + globalRateLimit = await this.context.globalState.get("rateLimitSeconds") + console.log( + `[RateLimitMigration] Using global rate limit for new profile: ${globalRateLimit}s`, + ) + } catch (error) { + console.error( + "[RateLimitMigration] Error getting global rate limit for new profile:", + error, + ) + } + + // Use default if not found + if (globalRateLimit === undefined) { + globalRateLimit = 5 // Default value + console.log( + `[RateLimitMigration] Using default rate limit value for new profile: ${globalRateLimit}s`, + ) + } + } + + // Apply the rate limit to the new config + config.rateLimitSeconds = globalRateLimit + } + } + currentConfig.apiConfigs[name] = { ...config, id: existingConfig?.id || this.generateId(), diff --git a/src/core/config/__tests__/ConfigManager.test.ts b/src/core/config/__tests__/ConfigManager.test.ts index 3d65021a8d3..3726af09235 100644 --- a/src/core/config/__tests__/ConfigManager.test.ts +++ b/src/core/config/__tests__/ConfigManager.test.ts @@ -9,8 +9,14 @@ const mockSecrets = { delete: jest.fn(), } +const mockGlobalState = { + get: jest.fn(), + update: jest.fn(), +} + const mockContext = { secrets: mockSecrets, + globalState: mockGlobalState, } as unknown as ExtensionContext describe("ConfigManager", () => { @@ -40,8 +46,12 @@ describe("ConfigManager", () => { default: { config: {}, id: "default", + rateLimitSeconds: 5, }, }, + migrations: { + rateLimitMigrated: true, + }, }), ) @@ -66,6 +76,9 @@ describe("ConfigManager", () => { }), ) + // Mock global state to prevent rate limit errors + mockGlobalState.get.mockResolvedValue(5) + await configManager.initConfig() // Should have written the config with new IDs @@ -141,6 +154,9 @@ describe("ConfigManager", () => { describe("SaveConfig", () => { it("should save new config", async () => { + // Mock globalState to prevent rate limit errors + mockGlobalState.get.mockResolvedValue(5) + mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "default", @@ -162,9 +178,10 @@ describe("ConfigManager", () => { await configManager.saveConfig("test", newConfig) - // Get the actual stored config to check the generated ID + // Get the actual stored config to check the generated ID and rate limit const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) const testConfigId = storedConfig.apiConfigs.test.id + const rateLimitSeconds = storedConfig.apiConfigs.test.rateLimitSeconds const expectedConfig = { currentApiConfigName: "default", @@ -173,6 +190,7 @@ describe("ConfigManager", () => { test: { ...newConfig, id: testConfigId, + rateLimitSeconds, }, }, modeApiConfigs: { @@ -196,6 +214,7 @@ describe("ConfigManager", () => { apiProvider: "anthropic", apiKey: "old-key", id: "test-id", + rateLimitSeconds: 5, }, }, } @@ -209,21 +228,14 @@ describe("ConfigManager", () => { await configManager.saveConfig("test", updatedConfig) - const expectedConfig = { - currentApiConfigName: "default", - apiConfigs: { - test: { - apiProvider: "anthropic", - apiKey: "new-key", - id: "test-id", - }, - }, - } - - expect(mockSecrets.store).toHaveBeenCalledWith( - "roo_cline_config_api_config", - JSON.stringify(expectedConfig, null, 2), - ) + // Check that the last call to store has the correct values + const storedConfig = JSON.parse(mockSecrets.store.mock.calls[mockSecrets.store.mock.calls.length - 1][1]) + expect(storedConfig.apiConfigs.test).toEqual({ + apiProvider: "anthropic", + apiKey: "new-key", + id: "test-id", + rateLimitSeconds: 5, + }) }) it("should throw error if secrets storage fails", async () => { @@ -319,8 +331,9 @@ describe("ConfigManager", () => { id: "test-id", }) - // Get the stored config to check the structure - const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) + // Get the last stored config to check the structure + const lastCallIndex = mockSecrets.store.mock.calls.length - 1 + const storedConfig = JSON.parse(mockSecrets.store.mock.calls[lastCallIndex][1]) expect(storedConfig.currentApiConfigName).toBe("test") expect(storedConfig.apiConfigs.test).toEqual({ apiProvider: "anthropic", @@ -386,8 +399,9 @@ describe("ConfigManager", () => { await configManager.setCurrentConfig("test") - // Get the stored config to check the structure - const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) + // Get the last stored config to check the structure + const lastCallIndex = mockSecrets.store.mock.calls.length - 1 + const storedConfig = JSON.parse(mockSecrets.store.mock.calls[lastCallIndex][1]) expect(storedConfig.currentApiConfigName).toBe("test") expect(storedConfig.apiConfigs.default.id).toBe("default") expect(storedConfig.apiConfigs.test).toEqual({ diff --git a/src/core/config/__tests__/rateLimitMigration.test.ts b/src/core/config/__tests__/rateLimitMigration.test.ts new file mode 100644 index 00000000000..199b95865c5 --- /dev/null +++ b/src/core/config/__tests__/rateLimitMigration.test.ts @@ -0,0 +1,155 @@ +import { ConfigManager } from "../ConfigManager" +import { ExtensionContext } from "vscode" +import path from "path" +import fs from "fs" + +// Mock class to simulate VSCode extension context +class MockExtensionContext { + private _globalState: Record + private _secrets: Record + private _storageUri: { fsPath: string } + private _globalStorageUri: { fsPath: string } + + constructor(initialGlobalState = {}, initialSecrets = {}) { + this._globalState = initialGlobalState + this._secrets = initialSecrets + this._storageUri = { fsPath: path.join(__dirname, "mock-storage") } + this._globalStorageUri = { fsPath: path.join(__dirname, "mock-global-storage") } + } + + get globalState() { + return { + get: jest.fn().mockImplementation((key) => Promise.resolve(this._globalState[key])), + update: jest.fn().mockImplementation((key, value) => { + this._globalState[key] = value + return Promise.resolve() + }), + } + } + + get secrets() { + return { + get: jest.fn().mockImplementation((key) => Promise.resolve(this._secrets[key])), + store: jest.fn().mockImplementation((key, value) => { + this._secrets[key] = value + return Promise.resolve() + }), + delete: jest.fn().mockImplementation((key) => { + delete this._secrets[key] + return Promise.resolve() + }), + } + } + + get storageUri() { + return this._storageUri + } + + get globalStorageUri() { + return this._globalStorageUri + } +} + +describe("Rate Limit Migration Tests", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should migrate existing global rate limit to all profiles", async () => { + // Case 1: Test migration with existing global rate limit + const context1 = new MockExtensionContext( + { rateLimitSeconds: 10 }, // Global state with rateLimitSeconds set to 10 + { + roo_cline_config_api_config: JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { + default: { id: "abc123" }, + profile1: { id: "def456", apiProvider: "anthropic" }, + profile2: { id: "ghi789", apiProvider: "openrouter" }, + }, + }), + }, + ) + + // Use a type assertion to bypass TypeScript's type checking + const configManager1 = new ConfigManager(context1 as unknown as ExtensionContext) + + // Wait for initialization to complete + await new Promise((resolve) => setTimeout(resolve, 100)) + + // Check if the migration was applied + const config1 = JSON.parse((await context1.secrets.get("roo_cline_config_api_config")) as string) + + // Verify migrations flag is set + expect(config1.migrations.rateLimitMigrated).toBeTruthy() + + // Verify rate limits were applied to all profiles + expect(config1.apiConfigs.default.rateLimitSeconds).toBe(10) + expect(config1.apiConfigs.profile1.rateLimitSeconds).toBe(10) + expect(config1.apiConfigs.profile2.rateLimitSeconds).toBe(10) + }) + + it("should use default rate limit when no global rate limit exists", async () => { + // Case 2: Test migration without global rate limit (should use default value) + const context2 = new MockExtensionContext( + {}, // No global state + { + roo_cline_config_api_config: JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { + default: { id: "abc123" }, + profile1: { id: "def456", apiProvider: "anthropic" }, + }, + }), + }, + ) + + // Use a type assertion to bypass TypeScript's type checking + const configManager2 = new ConfigManager(context2 as unknown as ExtensionContext) + + // Wait for initialization to complete + await new Promise((resolve) => setTimeout(resolve, 100)) + + // Check if the migration was applied with default value + const config2 = JSON.parse((await context2.secrets.get("roo_cline_config_api_config")) as string) + + // Verify migrations flag is set + expect(config2.migrations.rateLimitMigrated).toBeTruthy() + + // Verify default rate limits were applied + expect(config2.apiConfigs.default.rateLimitSeconds).toBe(5) // Default is 5 + expect(config2.apiConfigs.profile1.rateLimitSeconds).toBe(5) // Default is 5 + }) + + it("should apply rate limit to newly created profiles", async () => { + // Case 3: Test creating new profile via saveConfig + const context3 = new MockExtensionContext( + { rateLimitSeconds: 15 }, // Global state with rateLimitSeconds set to 15 + { + roo_cline_config_api_config: JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { + default: { id: "abc123", rateLimitSeconds: 8 }, + }, + migrations: { rateLimitMigrated: true }, + }), + }, + ) + + // Use a type assertion to bypass TypeScript's type checking + const configManager3 = new ConfigManager(context3 as unknown as ExtensionContext) + + // Wait for initialization to complete + await new Promise((resolve) => setTimeout(resolve, 100)) + + // Save a new profile + await configManager3.saveConfig("newProfile", { apiProvider: "anthropic" }) + + // Check if the new profile got a rate limit from existing profiles + const config3 = JSON.parse((await context3.secrets.get("roo_cline_config_api_config")) as string) + + // The new profile should inherit rate limit from existing profiles (8s) + expect(config3.apiConfigs.newProfile.rateLimitSeconds).toBe(8) + expect(config3.apiConfigs.newProfile.apiProvider).toBe("anthropic") + }) +}) diff --git a/src/shared/api.ts b/src/shared/api.ts index fa22dd4e00f..7107dbe7ce1 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -27,6 +27,7 @@ export interface ApiHandlerOptions { glamaModelId?: string glamaModelInfo?: ModelInfo glamaApiKey?: string + rateLimitSeconds?: number // Added for per-profile rate limiting openRouterApiKey?: string openRouterModelId?: string openRouterModelInfo?: ModelInfo @@ -136,6 +137,7 @@ export const API_CONFIG_KEYS: GlobalStateKey[] = [ "modelTemperature", "modelMaxTokens", "modelMaxThinkingTokens", + "rateLimitSeconds", // Added for per-profile rate limiting "fakeAi", ] diff --git a/webview-ui/src/components/settings/AdvancedSettings.tsx b/webview-ui/src/components/settings/AdvancedSettings.tsx index e0a909a3732..d2880e5a40e 100644 --- a/webview-ui/src/components/settings/AdvancedSettings.tsx +++ b/webview-ui/src/components/settings/AdvancedSettings.tsx @@ -13,15 +13,13 @@ import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" type AdvancedSettingsProps = HTMLAttributes & { - rateLimitSeconds: number diffEnabled?: boolean fuzzyMatchThreshold?: number - setCachedStateField: SetCachedStateField<"rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold"> + setCachedStateField: SetCachedStateField<"diffEnabled" | "fuzzyMatchThreshold"> experiments: Record setExperimentEnabled: SetExperimentEnabled } export const AdvancedSettings = ({ - rateLimitSeconds, diffEnabled, fuzzyMatchThreshold, setCachedStateField, @@ -44,20 +42,6 @@ export const AdvancedSettings = ({
- {t("settings:advanced.rateLimit.label")} -
- setCachedStateField("rateLimitSeconds", value)} - /> - {rateLimitSeconds}s -
-
-
- {t("settings:advanced.rateLimit.description")}
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index f202d518b13..4bc0754884c 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -51,6 +51,7 @@ import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" import { TemperatureControl } from "./TemperatureControl" +import { RateLimitControl } from "./RateLimitControl" import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@/utils/validate" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" @@ -1526,11 +1527,20 @@ const ApiOptions = ({ )} {!fromWelcomeView && ( - + <> + + + setApiConfigurationField("rateLimitSeconds", value === null ? undefined : value) + } + maxValue={60} + /> + )} ) diff --git a/webview-ui/src/components/settings/RateLimitControl.tsx b/webview-ui/src/components/settings/RateLimitControl.tsx new file mode 100644 index 00000000000..907a512555a --- /dev/null +++ b/webview-ui/src/components/settings/RateLimitControl.tsx @@ -0,0 +1,73 @@ +import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { useEffect, useState, FormEvent } from "react" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { useDebounce } from "react-use" + +interface RateLimitControlProps { + value: number | undefined | null + onChange: (value: number | undefined | null) => void + maxValue?: number +} + +export const RateLimitControl = ({ value, onChange, maxValue = 60 }: RateLimitControlProps) => { + const { t } = useAppTranslation() + const [isCustomRateLimit, setIsCustomRateLimit] = useState(value !== undefined) + const [inputValue, setInputValue] = useState(value) + useDebounce(() => onChange(inputValue), 50, [onChange, inputValue]) + // Sync internal state with prop changes when switching profiles + useEffect(() => { + const hasCustomRateLimit = value !== undefined && value !== null + setIsCustomRateLimit(hasCustomRateLimit) + setInputValue(value) + }, [value]) + + return ( + <> +
+ ) => { + const target = ("target" in e ? e.target : null) as HTMLInputElement | null + if (!target) return + const isChecked = target.checked + setIsCustomRateLimit(isChecked) + if (!isChecked) { + setInputValue(null) // Unset the rate limit, note that undefined is unserializable + } else { + setInputValue(value ?? 0) // Use the value from apiConfiguration, if set + } + }}> + {t("settings:rateLimit.useCustom")} + +
+ {t("settings:advanced.rateLimit.description")} +
+
+ + {isCustomRateLimit && ( +
+
+ setInputValue(parseInt(e.target.value))} + /> + {inputValue}s +
+

+ {t("settings:advanced.rateLimit.description")} +

+
+ )} + + ) +} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 10112d8924e..a5aa21abd7e 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -465,7 +465,6 @@ const SettingsView = forwardRef(({ onDone },
({ })) jest.mock("../TemperatureControl", () => ({ + __esModule: true, TemperatureControl: ({ value, onChange }: any) => (
({ ), })) +jest.mock("../RateLimitControl", () => ({ + __esModule: true, + RateLimitControl: ({ value, onChange, maxValue }: any) => ( +
+ onChange(parseInt(e.target.value))} + min={0} + max={maxValue || 60} + step={1} + /> +
+ ), +})) + // Mock ThinkingBudget component jest.mock("../ThinkingBudget", () => ({ + __esModule: true, ThinkingBudget: ({ apiConfiguration, setApiConfigurationField, modelInfo, provider }: any) => modelInfo?.thinking ? (
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 70382c77b89..7b77bc5f79a 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -277,6 +277,9 @@ "description": "Temps màxim d'espera per a la inicialització de la integració de shell abans d'executar comandes. Per a usuaris amb temps d'inici de shell llargs, aquest valor pot necessitar ser augmentat si veieu errors \"Shell Integration Unavailable\" al terminal." } }, + "rateLimit": { + "useCustom": "Utilitzar límit de freqüència personalitzat" + }, "advanced": { "rateLimit": { "label": "Límit de freqüència", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 72f8410e5a1..9cbfcf2fb28 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -277,6 +277,9 @@ "description": "Maximale Wartezeit für die Shell-Integration, bevor Befehle ausgeführt werden. Für Benutzer mit langen Shell-Startzeiten muss dieser Wert möglicherweise erhöht werden, wenn Sie Fehler vom Typ \"Shell Integration Unavailable\" im Terminal sehen." } }, + "rateLimit": { + "useCustom": "Benutzerdefinierte Ratenbegrenzung verwenden" + }, "advanced": { "rateLimit": { "label": "Ratenbegrenzung", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 4ed928c40c7..de87b9b244e 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -277,6 +277,9 @@ "description": "Maximum time to wait for shell integration to initialize before executing commands. For users with long shell startup times, this value may need to be increased if you see \"Shell Integration Unavailable\" errors in the terminal." } }, + "rateLimit": { + "useCustom": "Use custom rate limit" + }, "advanced": { "rateLimit": { "label": "Rate limit", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 00523ac834e..9a5d062cae5 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -277,6 +277,9 @@ "description": "Tiempo máximo de espera para la inicialización de la integración del shell antes de ejecutar comandos. Para usuarios con tiempos de inicio de shell largos, este valor puede necesitar ser aumentado si ve errores \"Shell Integration Unavailable\" en el terminal." } }, + "rateLimit": { + "useCustom": "Usar límite de tasa personalizado" + }, "advanced": { "rateLimit": { "label": "Límite de tasa", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index d083511449d..2a4e8e236e6 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -277,6 +277,9 @@ "description": "Temps maximum d'attente pour l'initialisation de l'intégration du shell avant d'exécuter des commandes. Pour les utilisateurs avec des temps de démarrage de shell longs, cette valeur peut nécessiter d'être augmentée si vous voyez des erreurs \"Shell Integration Unavailable\" dans le terminal." } }, + "rateLimit": { + "useCustom": "Utiliser une limite de débit personnalisée" + }, "advanced": { "rateLimit": { "label": "Limite de débit", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2b02f742853..e7f404aeb04 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -277,6 +277,9 @@ "description": "कमांड निष्पादित करने से पहले शेल एकीकरण के आरंभ होने के लिए प्रतीक्षा का अधिकतम समय। लंबे शेल स्टार्टअप समय वाले उपयोगकर्ताओं के लिए, यदि आप टर्मिनल में \"Shell Integration Unavailable\" त्रुटियाँ देखते हैं तो इस मान को बढ़ाने की आवश्यकता हो सकती है।" } }, + "rateLimit": { + "useCustom": "कस्टम दर सीमा का उपयोग करें" + }, "advanced": { "rateLimit": { "label": "दर सीमा", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 23b99811fde..2313e25eb1a 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -277,6 +277,9 @@ "description": "Tempo massimo di attesa per l'inizializzazione dell'integrazione della shell prima di eseguire i comandi. Per gli utenti con tempi di avvio della shell lunghi, questo valore potrebbe dover essere aumentato se si vedono errori \"Shell Integration Unavailable\" nel terminale." } }, + "rateLimit": { + "useCustom": "Usa limite di frequenza personalizzato" + }, "advanced": { "rateLimit": { "label": "Limite di frequenza", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 3c7dc351088..38d0079cc00 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -277,6 +277,9 @@ "description": "コマンドを実行する前にシェル統合の初期化を待つ最大時間。シェルの起動時間が長いユーザーの場合、ターミナルで「Shell Integration Unavailable」エラーが表示される場合は、この値を増やす必要があるかもしれません。" } }, + "rateLimit": { + "useCustom": "カスタムレート制限を使用" + }, "advanced": { "rateLimit": { "label": "レート制限", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 2087119d63b..637009b1e4f 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -277,6 +277,9 @@ "description": "명령을 실행하기 전에 쉘 통합이 초기화될 때까지 기다리는 최대 시간. 쉘 시작 시간이 긴 사용자의 경우, 터미널에서 \"Shell Integration Unavailable\" 오류가 표시되면 이 값을 늘려야 할 수 있습니다." } }, + "rateLimit": { + "useCustom": "사용자 정의 속도 제한 사용" + }, "advanced": { "rateLimit": { "label": "속도 제한", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 574c4108ba8..1855254bb1f 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -277,6 +277,9 @@ "description": "Maksymalny czas oczekiwania na inicjalizację integracji powłoki przed wykonaniem poleceń. Dla użytkowników z długim czasem uruchamiania powłoki, ta wartość może wymagać zwiększenia, jeśli widzisz błędy \"Shell Integration Unavailable\" w terminalu." } }, + "rateLimit": { + "useCustom": "Użyj niestandardowego limitu szybkości" + }, "advanced": { "rateLimit": { "label": "Limit szybkości", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index b18be604a23..35f57dbedf6 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -277,6 +277,9 @@ "description": "Tempo máximo de espera para a inicialização da integração do shell antes de executar comandos. Para usuários com tempos de inicialização de shell longos, este valor pode precisar ser aumentado se você vir erros \"Shell Integration Unavailable\" no terminal." } }, + "rateLimit": { + "useCustom": "Usar limite de taxa personalizado" + }, "advanced": { "rateLimit": { "label": "Limite de taxa", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 9a95a578d3f..e3a2a99cebc 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -277,6 +277,9 @@ "description": "Komutları yürütmeden önce kabuk entegrasyonunun başlatılması için beklenecek maksimum süre. Kabuk başlatma süresi uzun olan kullanıcılar için, terminalde \"Shell Integration Unavailable\" hatalarını görürseniz bu değerin artırılması gerekebilir." } }, + "rateLimit": { + "useCustom": "Özel hız sınırı kullan" + }, "advanced": { "rateLimit": { "label": "Hız sınırı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index ac053218346..7d3f6309786 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -277,6 +277,9 @@ "description": "Thời gian tối đa để chờ tích hợp shell khởi tạo trước khi thực hiện lệnh. Đối với người dùng có thời gian khởi động shell dài, giá trị này có thể cần được tăng lên nếu bạn thấy lỗi \"Shell Integration Unavailable\" trong terminal." } }, + "rateLimit": { + "useCustom": "Sử dụng giới hạn tốc độ tùy chỉnh" + }, "advanced": { "rateLimit": { "label": "Giới hạn tốc độ", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 6c590a053d9..0373e24a0e2 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -277,6 +277,9 @@ "description": "执行命令前等待 Shell 集成初始化的最长时间。对于 Shell 启动时间较长的用户,如果在终端中看到\"Shell Integration Unavailable\"错误,可能需要增加此值。" } }, + "rateLimit": { + "useCustom": "使用自定义速率限制" + }, "advanced": { "rateLimit": { "label": "速率限制", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 534cdd81e4b..4c4cfc69ba7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -277,6 +277,9 @@ "description": "執行命令前等待 Shell 整合初始化的最長時間。對於 Shell 啟動時間較長的使用者,如果在終端機中看到\"Shell Integration Unavailable\"錯誤,可能需要增加此值。" } }, + "rateLimit": { + "useCustom": "使用自訂速率限制" + }, "advanced": { "rateLimit": { "label": "速率限制",