From 2d363600cf9993eaa48708dac51eaa3089159283 Mon Sep 17 00:00:00 2001 From: yj-z <1368334952@qq.com> Date: Fri, 24 Jul 2026 21:32:14 +0800 Subject: [PATCH] feat(core): configure stream rate-limit retry delays --- docs/users/configuration/settings.md | 44 ++++----- packages/cli/src/config/settingsSchema.ts | 22 +++++ packages/core/src/core/contentGenerator.ts | 2 + packages/core/src/core/geminiChat.test.ts | 89 +++++++++++++++++++ packages/core/src/core/geminiChat.ts | 7 ++ packages/core/src/models/constants.ts | 2 + .../src/models/modelConfigResolver.test.ts | 24 +++++ packages/core/src/models/types.ts | 2 + .../schemas/settings.schema.json | 8 ++ 9 files changed, 178 insertions(+), 22 deletions(-) diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index df675998775..79801afc8c3 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -157,28 +157,28 @@ Settings are organized into categories. Most settings should be placed within th #### model -| Setting | Type | Description | Default | -| -------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `model.name` | string | The Qwen model to use for conversations. | `undefined` | -| `model.reasoningEffort` | enum | How hard reasoning-capable models think, applied across all providers. Set with the [`/effort`](../features/commands) command (`low`, `medium`, `high`, `xhigh`, `max`). Each provider maps and clamps this to what the active model supports (e.g. Gemini caps at `high`; Anthropic clamps tiers a model lacks). Leave unset to use the model/provider default. | `undefined` | -| `model.baseUrl` | string | Persisted automatically by the model picker to disambiguate when multiple `modelProviders` entries share the same model id. Not intended to be set by hand — use the `/model` picker or a `modelProviders` entry instead; a stale hand-edited value can silently route requests to a different same-id provider. | `undefined` | -| `model.sessionTokenLimit` | number | Maximum recorded prompt token count allowed before sending the next message. `-1` means unlimited; `0` is also treated as unlimited (unlike `model.maxToolCalls`, where `0` disallows all calls). When the recorded prompt count exceeds the limit, the next send is dropped (the session is not aborted). | `-1` | -| `model.maxSessionTurns` | integer | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` | -| `model.maxWallTimeSeconds` | number | Wall-clock budget for headless / unattended runs, in seconds. `-1` means unlimited. Overridable per-invocation via `--max-wall-time`, which requires a positive duration (`90`, `30s`, `5m`, `1h`, `1.5h`); the minimum is 1 second — sub-second values (`500ms`, `0.5`) are rejected as typos. Omit the flag to fall back to this setting. Aborts with exit code 55 when exceeded. | `-1` | -| `model.maxToolCalls` | number | Cumulative tool-call budget for a run (counts every executed tool, success or failure; `structured_output` under `--json-schema` is exempt). `-1` means unlimited; `0` means "no tool calls allowed". Capped at 1,000,000 to catch typos. Overridable via `--max-tool-calls`. Aborts with exit code 55 when exceeded. | `-1` | -| `model.maxSubagentDepth` | number | Maximum sub-agent nesting depth (1-based levels: a top-level sub-agent is level 1). `1` keeps sub-agents available but disables nesting — the pre-nesting behavior. Values clamp to the range 1–100; non-finite values fall back to the default. Teammates, forks, and workflow-spawned agents never nest regardless of this setting. Overridable via `--max-subagent-depth`. | `5` | -| `model.generationConfig` | object | Advanced overrides passed to the underlying content generator. Supports request controls such as `timeout`, `maxRetries`, `enableCacheControl`, `splitToolMedia` (default `true`; splits tool-returned media — including images read by the built-in read_file — into a follow-up user message instead of the spec-violating `role: "tool"` message, so strict OpenAI-compatible servers like doubao / new-api / LM Studio can see it; set `false` to restore the legacy embed-in-tool behavior), `toolResultContentFormat` (default `"parts"`; set `"string"` only for legacy OpenAI-compatible runtimes whose tool templates ignore text content parts), `contextWindowSize` (override model's context window size), `modalities` (override auto-detected input modalities), `customHeaders` (custom HTTP headers for API requests), and `extra_body` (additional body parameters for OpenAI-compatible API requests only), along with fine-tuning knobs under `samplingParams` (for example `temperature`, `top_p`, `max_tokens`). Leave unset to rely on provider defaults. | `undefined` | -| `model.chatCompression.contextPercentageThreshold` | number | **REMOVED.** Replaced by `context.autoCompactThreshold` (see `#### context` section below). Auto-compaction now uses a three-tier threshold ladder (warn / auto / hard) computed internally from the model's context window via the `computeThresholds()` function. The old setting is silently ignored (no startup warning). See PR #4345 / `docs/design/auto-compaction-threshold-redesign.md` for the redesign rationale. | `N/A` | -| `model.chatCompression.maxRecentFilesToRetain` | number | Number of most-recently-touched files whose current content is restored (embedded if small, otherwise referenced by path) into history after auto-compaction. `0` restores none. Env override: `QWEN_COMPACT_MAX_RECENT_FILES`. | `5` | -| `model.chatCompression.maxRecentImagesToRetain` | number | Number of most-recent images (tool screenshots / user pastes) restored into history after auto-compaction. `0` restores none. Env override: `QWEN_COMPACT_MAX_RECENT_IMAGES`. | `3` | -| `model.chatCompression.enableScreenshotTrigger` | boolean | When `true`, auto-compaction also fires once the number of tool-returned images accumulated in history reaches `screenshotTriggerThreshold`, independent of token usage — aimed at computer-use sessions where frequent screenshots dilute model attention. Counts only images returned inside tool results, not user-pasted images. Env override: `QWEN_COMPACT_SCREENSHOT_TRIGGER` (`1`/`true`/`0`/`false`). | `true` | -| `model.chatCompression.screenshotTriggerThreshold` | number | Tool-returned image count at or above which the screenshot trigger fires (only when `enableScreenshotTrigger`). Compaction resets the count — surviving images are re-embedded as top-level parts, which the trigger doesn't count — so it won't immediately re-fire. Env override: `QWEN_COMPACT_SCREENSHOT_THRESHOLD`. | `20` | -| `model.skipNextSpeakerCheck` | boolean | Skip the next speaker check. | `true` | -| `model.skipLoopDetection` | boolean | Disables streaming loop detection checks. Defaults to `true` (loop detection is skipped) to avoid false positives interrupting legitimate workflows. Set to `false` to re-enable streaming loop detection — useful as a guardrail in headless / non-interactive runs where stuck repetition can otherwise waste budget. | `true` | -| `model.maxToolCallsPerTurn` | integer | Per-turn tool-call cap (one model turn plus its tool-result continuations; blocking Stop-hook continuations such as `/goal` iterations start a fresh budget). When set explicitly, this value is a hard cap: the turn halts on the next tool call after it is reached (the released behavior). When left unset (default 100), the cap is adaptive: once the turn exceeds 100 it halts only when the model keeps repeating the same call (a stuck loop); a productive turn (diverse calls) continues up to a hard backstop of 1000, which always halts. The adaptive default applies to both the interactive TUI and non-interactive (`-p` / JSON / stream-JSON) core-client runs; the daemon/ACP path always treats the value as a hard cap. Always-on circuit breaker against runaway turns, independent of `model.skipLoopDetection`. Set to `0` or a negative value to disable the cap. Choosing "Disable loop detection for this session" in the loop-detected dialog also suppresses it for the rest of the session. | `100` | -| `model.skipStartupContext` | boolean | Skips sending the startup workspace context (environment summary and acknowledgement) at the beginning of each session. Enable this if you prefer to provide context manually or want to save tokens on startup. | `false` | -| `model.enableOpenAILogging` | boolean | Enables logging of OpenAI API calls for debugging and analysis. When enabled, API requests and responses are logged to JSON files. | `false` | -| `model.openAILoggingDir` | string | Custom directory path for OpenAI API logs. If not specified, defaults to `logs/openai` in the current working directory. Supports absolute paths, relative paths (resolved from current working directory), and `~` expansion (home directory). | `undefined` | +| Setting | Type | Description | Default | +| -------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `model.name` | string | The Qwen model to use for conversations. | `undefined` | +| `model.reasoningEffort` | enum | How hard reasoning-capable models think, applied across all providers. Set with the [`/effort`](../features/commands) command (`low`, `medium`, `high`, `xhigh`, `max`). Each provider maps and clamps this to what the active model supports (e.g. Gemini caps at `high`; Anthropic clamps tiers a model lacks). Leave unset to use the model/provider default. | `undefined` | +| `model.baseUrl` | string | Persisted automatically by the model picker to disambiguate when multiple `modelProviders` entries share the same model id. Not intended to be set by hand — use the `/model` picker or a `modelProviders` entry instead; a stale hand-edited value can silently route requests to a different same-id provider. | `undefined` | +| `model.sessionTokenLimit` | number | Maximum recorded prompt token count allowed before sending the next message. `-1` means unlimited; `0` is also treated as unlimited (unlike `model.maxToolCalls`, where `0` disallows all calls). When the recorded prompt count exceeds the limit, the next send is dropped (the session is not aborted). | `-1` | +| `model.maxSessionTurns` | integer | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` | +| `model.maxWallTimeSeconds` | number | Wall-clock budget for headless / unattended runs, in seconds. `-1` means unlimited. Overridable per-invocation via `--max-wall-time`, which requires a positive duration (`90`, `30s`, `5m`, `1h`, `1.5h`); the minimum is 1 second — sub-second values (`500ms`, `0.5`) are rejected as typos. Omit the flag to fall back to this setting. Aborts with exit code 55 when exceeded. | `-1` | +| `model.maxToolCalls` | number | Cumulative tool-call budget for a run (counts every executed tool, success or failure; `structured_output` under `--json-schema` is exempt). `-1` means unlimited; `0` means "no tool calls allowed". Capped at 1,000,000 to catch typos. Overridable via `--max-tool-calls`. Aborts with exit code 55 when exceeded. | `-1` | +| `model.maxSubagentDepth` | number | Maximum sub-agent nesting depth (1-based levels: a top-level sub-agent is level 1). `1` keeps sub-agents available but disables nesting — the pre-nesting behavior. Values clamp to the range 1–100; non-finite values fall back to the default. Teammates, forks, and workflow-spawned agents never nest regardless of this setting. Overridable via `--max-subagent-depth`. | `5` | +| `model.generationConfig` | object | Advanced overrides passed to the underlying content generator. Supports request controls such as `timeout`, `maxRetries`, `retryInitialDelayMs`, `retryMaxDelayMs`, `enableCacheControl`, `splitToolMedia` (default `true`; splits tool-returned media — including images read by the built-in read_file — into a follow-up user message instead of the spec-violating `role: "tool"` message, so strict OpenAI-compatible servers like doubao / new-api / LM Studio can see it; set `false` to restore the legacy embed-in-tool behavior), `toolResultContentFormat` (default `"parts"`; set `"string"` only for legacy OpenAI-compatible runtimes whose tool templates ignore text content parts), `contextWindowSize` (override model's context window size), `modalities` (override auto-detected input modalities), `customHeaders` (custom HTTP headers for API requests), and `extra_body` (additional body parameters for OpenAI-compatible API requests only), along with fine-tuning knobs under `samplingParams` (for example `temperature`, `top_p`, `max_tokens`). Leave unset to rely on provider defaults. | `undefined` | +| `model.chatCompression.contextPercentageThreshold` | number | **REMOVED.** Replaced by `context.autoCompactThreshold` (see `#### context` section below). Auto-compaction now uses a three-tier threshold ladder (warn / auto / hard) computed internally from the model's context window via the `computeThresholds()` function. The old setting is silently ignored (no startup warning). See PR #4345 / `docs/design/auto-compaction-threshold-redesign.md` for the redesign rationale. | `N/A` | +| `model.chatCompression.maxRecentFilesToRetain` | number | Number of most-recently-touched files whose current content is restored (embedded if small, otherwise referenced by path) into history after auto-compaction. `0` restores none. Env override: `QWEN_COMPACT_MAX_RECENT_FILES`. | `5` | +| `model.chatCompression.maxRecentImagesToRetain` | number | Number of most-recent images (tool screenshots / user pastes) restored into history after auto-compaction. `0` restores none. Env override: `QWEN_COMPACT_MAX_RECENT_IMAGES`. | `3` | +| `model.chatCompression.enableScreenshotTrigger` | boolean | When `true`, auto-compaction also fires once the number of tool-returned images accumulated in history reaches `screenshotTriggerThreshold`, independent of token usage — aimed at computer-use sessions where frequent screenshots dilute model attention. Counts only images returned inside tool results, not user-pasted images. Env override: `QWEN_COMPACT_SCREENSHOT_TRIGGER` (`1`/`true`/`0`/`false`). | `true` | +| `model.chatCompression.screenshotTriggerThreshold` | number | Tool-returned image count at or above which the screenshot trigger fires (only when `enableScreenshotTrigger`). Compaction resets the count — surviving images are re-embedded as top-level parts, which the trigger doesn't count — so it won't immediately re-fire. Env override: `QWEN_COMPACT_SCREENSHOT_THRESHOLD`. | `20` | +| `model.skipNextSpeakerCheck` | boolean | Skip the next speaker check. | `true` | +| `model.skipLoopDetection` | boolean | Disables streaming loop detection checks. Defaults to `true` (loop detection is skipped) to avoid false positives interrupting legitimate workflows. Set to `false` to re-enable streaming loop detection — useful as a guardrail in headless / non-interactive runs where stuck repetition can otherwise waste budget. | `true` | +| `model.maxToolCallsPerTurn` | integer | Per-turn tool-call cap (one model turn plus its tool-result continuations; blocking Stop-hook continuations such as `/goal` iterations start a fresh budget). When set explicitly, this value is a hard cap: the turn halts on the next tool call after it is reached (the released behavior). When left unset (default 100), the cap is adaptive: once the turn exceeds 100 it halts only when the model keeps repeating the same call (a stuck loop); a productive turn (diverse calls) continues up to a hard backstop of 1000, which always halts. The adaptive default applies to both the interactive TUI and non-interactive (`-p` / JSON / stream-JSON) core-client runs; the daemon/ACP path always treats the value as a hard cap. Always-on circuit breaker against runaway turns, independent of `model.skipLoopDetection`. Set to `0` or a negative value to disable the cap. Choosing "Disable loop detection for this session" in the loop-detected dialog also suppresses it for the rest of the session. | `100` | +| `model.skipStartupContext` | boolean | Skips sending the startup workspace context (environment summary and acknowledgement) at the beginning of each session. Enable this if you prefer to provide context manually or want to save tokens on startup. | `false` | +| `model.enableOpenAILogging` | boolean | Enables logging of OpenAI API calls for debugging and analysis. When enabled, API requests and responses are logged to JSON files. | `false` | +| `model.openAILoggingDir` | string | Custom directory path for OpenAI API logs. If not specified, defaults to `logs/openai` in the current working directory. Supports absolute paths, relative paths (resolved from current working directory), and `~` expansion (home directory). | `undefined` | **Example model.generationConfig:** diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 800a7170ee4..d3e21b95c80 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1557,6 +1557,28 @@ const SETTINGS_SCHEMA = { parentKey: 'generationConfig', showInDialog: false, }, + retryInitialDelayMs: { + type: 'number', + label: 'Retry Initial Delay', + category: 'Generation Configuration', + requiresRestart: false, + default: undefined as number | undefined, + description: + 'Initial delay in milliseconds for stream rate-limit retries.', + parentKey: 'generationConfig', + showInDialog: false, + }, + retryMaxDelayMs: { + type: 'number', + label: 'Retry Max Delay', + category: 'Generation Configuration', + requiresRestart: false, + default: undefined as number | undefined, + description: + 'Maximum delay in milliseconds for stream rate-limit retries.', + parentKey: 'generationConfig', + showInDialog: false, + }, enableCacheControl: { type: 'boolean', label: 'Enable Cache Control', diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 44ef79d5a79..6d374ddd75a 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -88,6 +88,8 @@ export type ContentGeneratorConfig = { // returns 200 then goes silent is otherwise unbounded. `<= 0` disables it. streamIdleTimeoutMs?: number; maxRetries?: number; // Maximum retries for rate-limit errors + retryInitialDelayMs?: number; // Initial delay for stream rate-limit retries + retryMaxDelayMs?: number; // Maximum delay for stream rate-limit retries retryErrorCodes?: number[]; // Additional error codes that trigger rate-limit retry enableCacheControl?: boolean; // Enable cache control for DashScope providers // Force `scope: 'global'` on Anthropic cache_control entries even when the diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index be03c903969..d1355a23b74 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -7977,6 +7977,95 @@ describe('GeminiChat', async () => { } }); + it('uses configured stream rate-limit retry delays', async () => { + vi.useFakeTimers(); + + try { + vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({ + authType: AuthType.USE_OPENAI, + model: 'test-model', + maxRetries: 2, + retryInitialDelayMs: 3_000, + retryMaxDelayMs: 5_000, + }); + const firstError = new StreamContentError( + 'id:1\nevent:error\n:HTTP_STATUS/429\ndata:{"request_id":"req-1","code":"Throttling.AllocationQuota","message":"Allocated quota exceeded"}', + ); + const secondError = new StreamContentError( + 'id:2\nevent:error\n:HTTP_STATUS/429\ndata:{"request_id":"req-2","code":"Throttling.AllocationQuota","message":"Allocated quota exceeded"}', + ); + + vi.mocked(mockContentGenerator.generateContentStream) + .mockResolvedValueOnce( + (async function* () { + throw firstError; + + yield {} as GenerateContentResponse; + })(), + ) + .mockResolvedValueOnce( + (async function* () { + throw secondError; + + yield {} as GenerateContentResponse; + })(), + ) + .mockResolvedValueOnce( + (async function* () { + yield { + candidates: [ + { + content: { parts: [{ text: 'Recovered' }] }, + finishReason: 'STOP', + }, + ], + } as unknown as GenerateContentResponse; + })(), + ); + + const stream = await chat.sendMessageStream( + 'test-model', + { message: 'test' }, + 'prompt-id-configured-rate-limit-delay', + ); + const iterator = stream[Symbol.asyncIterator](); + + const first = await iterator.next(); + expect(first.value.type).toBe(StreamEventType.RETRY); + expect(first.value.retryInfo?.delayMs).toBe(3_000); + + let nextPromise = iterator.next(); + await vi.advanceTimersByTimeAsync(3_000); + await nextPromise; + + const second = await iterator.next(); + expect(second.value.type).toBe(StreamEventType.RETRY); + expect(second.value.retryInfo?.delayMs).toBe(5_000); + + nextPromise = iterator.next(); + await vi.advanceTimersByTimeAsync(5_000); + await nextPromise; + + const events: StreamEvent[] = []; + for (;;) { + const next = await iterator.next(); + if (next.done) break; + events.push(next.value); + } + + expect( + events.some( + (e) => + e.type === StreamEventType.CHUNK && + e.value.candidates?.[0]?.content?.parts?.[0]?.text === + 'Recovered', + ), + ).toBe(true); + } finally { + vi.useRealTimers(); + } + }); + describe('API error retry behavior', () => { beforeEach(() => { // Use a more direct mock for retry testing diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index b9d497717aa..fcd184458ac 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -2297,6 +2297,11 @@ export class GeminiChat { : undefined; const maxRateLimitRetries = cgConfig?.maxRetries ?? RATE_LIMIT_RETRY_OPTIONS.maxRetries; + const retryInitialDelayMs = + cgConfig?.retryInitialDelayMs ?? + RATE_LIMIT_RETRY_OPTIONS.initialDelayMs; + const retryMaxDelayMs = + cgConfig?.retryMaxDelayMs ?? RATE_LIMIT_RETRY_OPTIONS.maxDelayMs; const extraRetryErrorCodes = cgConfig?.retryErrorCodes; // Max output tokens escalation: when no user/env override is set and @@ -2391,6 +2396,8 @@ export class GeminiChat { rateLimitRetryCount++; const delayMs = getRateLimitRetryDelayMs(rateLimitRetryCount, { ...RATE_LIMIT_RETRY_OPTIONS, + initialDelayMs: retryInitialDelayMs, + maxDelayMs: retryMaxDelayMs, error, }); const message = parseAndFormatApiError( diff --git a/packages/core/src/models/constants.ts b/packages/core/src/models/constants.ts index d08a19cc328..31ea00e70cf 100644 --- a/packages/core/src/models/constants.ts +++ b/packages/core/src/models/constants.ts @@ -22,6 +22,8 @@ export const MODEL_GENERATION_CONFIG_FIELDS = [ 'samplingParams', 'timeout', 'maxRetries', + 'retryInitialDelayMs', + 'retryMaxDelayMs', 'retryErrorCodes', 'enableCacheControl', 'forceGlobalCacheScope', diff --git a/packages/core/src/models/modelConfigResolver.test.ts b/packages/core/src/models/modelConfigResolver.test.ts index dd70adda4ad..65f1b776f80 100644 --- a/packages/core/src/models/modelConfigResolver.test.ts +++ b/packages/core/src/models/modelConfigResolver.test.ts @@ -339,6 +339,8 @@ describe('modelConfigResolver', () => { expect(result.config.timeout).toBe(60000); expect(result.config.maxRetries).toBe(5); + expect(result.config.retryInitialDelayMs).toBeUndefined(); + expect(result.config.retryMaxDelayMs).toBeUndefined(); expect(result.config.samplingParams?.temperature).toBe(0.7); expect(result.sources['timeout'].kind).toBe('settings'); @@ -372,6 +374,28 @@ describe('modelConfigResolver', () => { expect(result.sources['timeout'].kind).toBe('modelProviders'); }); + it('resolves stream retry delay config from settings', () => { + const result = resolveModelConfig({ + authType: AuthType.USE_OPENAI, + cli: {}, + settings: { + apiKey: 'key', + generationConfig: { + maxRetries: 4, + retryInitialDelayMs: 3000, + retryMaxDelayMs: 30000, + }, + }, + env: {}, + }); + + expect(result.config.maxRetries).toBe(4); + expect(result.config.retryInitialDelayMs).toBe(3000); + expect(result.config.retryMaxDelayMs).toBe(30000); + expect(result.sources['retryInitialDelayMs'].kind).toBe('settings'); + expect(result.sources['retryMaxDelayMs'].kind).toBe('settings'); + }); + it('QWEN_CODE_API_TIMEOUT_MS env var overrides settings timeout', () => { const result = resolveModelConfig({ authType: AuthType.USE_OPENAI, diff --git a/packages/core/src/models/types.ts b/packages/core/src/models/types.ts index cd77e58fcf0..dcd9d0ae33e 100644 --- a/packages/core/src/models/types.ts +++ b/packages/core/src/models/types.ts @@ -32,6 +32,8 @@ export type ModelGenerationConfig = Pick< | 'samplingParams' | 'timeout' | 'maxRetries' + | 'retryInitialDelayMs' + | 'retryMaxDelayMs' | 'retryErrorCodes' | 'enableCacheControl' | 'forceGlobalCacheScope' diff --git a/packages/vscode-ide-companion/schemas/settings.schema.json b/packages/vscode-ide-companion/schemas/settings.schema.json index 383305b2d66..61f93bb5ef5 100644 --- a/packages/vscode-ide-companion/schemas/settings.schema.json +++ b/packages/vscode-ide-companion/schemas/settings.schema.json @@ -681,6 +681,14 @@ "description": "Maximum number of retries for failed requests.", "type": "number" }, + "retryInitialDelayMs": { + "description": "Initial delay in milliseconds for stream rate-limit retries.", + "type": "number" + }, + "retryMaxDelayMs": { + "description": "Maximum delay in milliseconds for stream rate-limit retries.", + "type": "number" + }, "enableCacheControl": { "description": "Enable cache control for DashScope providers.", "type": "boolean",