Skip to content

feat(core): configure stream rate-limit retry delays - #7674

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
jay666mnj:feat-welcome-pr-delays
Jul 25, 2026
Merged

feat(core): configure stream rate-limit retry delays#7674
wenshao merged 1 commit into
QwenLM:mainfrom
jay666mnj:feat-welcome-pr-delays

Conversation

@jay666mnj

Copy link
Copy Markdown
Contributor

What this PR does

Adds retryInitialDelayMs and retryMaxDelayMs to generationConfig so stream-side rate-limit retries can use provider-appropriate delays instead of always starting at 60 seconds and backing off up to 5 minutes. The existing defaults are preserved when these options are not set.

Why it's needed

Some OpenAI-compatible providers return short-lived 429 throttling errors during SSE streaming, where waiting 60 seconds before the first retry is unnecessarily slow. Making the stream retry delay configurable lets users tune the retry cadence for their provider without patching source code.

Reviewer Test Plan

How to verify

Configure an OpenAI-compatible provider with generationConfig.retryInitialDelayMs and generationConfig.retryMaxDelayMs, then trigger a stream-side 429 retry. The retry event should report and wait for the configured delay values while preserving the prior default behavior when the fields are omitted.

Evidence (Before & After)

N/A — configuration and retry behavior change covered by focused unit tests.

Tested on

OS Status
🍏 macOS ⚠️ not tested
🪟 Windows ✅ tested
🐧 Linux ⚠️ not tested

Environment (optional)

Windows, Node v22.13.1. npm run build reached the web-shell build stage and timed out after 900 seconds in this local Windows environment without an error; relevant package builds and full typecheck completed successfully.

Risk & Scope

  • Main risk or tradeoff: Adds two optional generation config fields that affect only stream-side rate-limit retry delays; default behavior is unchanged when they are unset.
  • Not validated / out of scope: End-to-end provider throttling against a real external API was not run locally.
  • Breaking changes / migration notes: None.

Linked Issues

Fixes #7658

中文说明

What this PR does

generationConfig 中新增 retryInitialDelayMsretryMaxDelayMs,让流式 429 限流重试可以使用适合具体 provider 的延迟,而不是始终从 60 秒开始并最多退避到 5 分钟。未配置这些字段时,现有默认行为保持不变。

Why it's needed

一些 OpenAI 兼容 provider 会在 SSE 流式响应期间返回短暂的 429 限流错误,此时第一次重试等待 60 秒会显得过长。将流式重试延迟配置化后,用户可以按 provider 的限流特征调整重试节奏,而不需要修改源码。

Reviewer Test Plan

How to verify

为 OpenAI 兼容 provider 配置 generationConfig.retryInitialDelayMsgenerationConfig.retryMaxDelayMs,然后触发一次流式 429 重试。重试事件应报告并等待配置的延迟值;未设置这些字段时应继续保持原有默认行为。

Evidence (Before & After)

N/A —— 配置和重试行为变更已由聚焦单元测试覆盖。

Tested on

OS Status
🍏 macOS ⚠️ not tested
🪟 Windows ✅ tested
🐧 Linux ⚠️ not tested

Environment (optional)

Windows,Node v22.13.1。本地 Windows 环境中 npm run build 运行到 web-shell build 阶段后 900 秒超时,未出现错误输出;本次相关 package build 与完整 typecheck 已成功完成。

Risk & Scope

  • Main risk or tradeoff: 新增两个可选 generation config 字段,只影响流式限流重试延迟;字段未设置时默认行为不变。
  • Not validated / out of scope: 未对真实外部 API provider 执行端到端限流验证。
  • Breaking changes / migration notes: 无。

Linked Issues

Fixes #7658

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments above for the result.

Qwen Triage 已完成 —— 查看运行。结果见上方各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — this is a clean, well-scoped one.

Template looks good ✓

Problem: observed, not theoretical. Linked issue #7658 documents the hardcoded initialDelayMs: 60000 / maxDelayMs: 300000 in RATE_LIMIT_RETRY_OPTIONS (only maxRetries is configurable today), with a concrete pain point — staring at "Retrying in 43s…" for what could be a 3-second wait on short-lived per-minute quotas. The issue was already triaged and labeled welcome-pr.

Direction: aligned. Qwen Code targets diverse OpenAI-compatible providers with very different rate-limit characteristics, and the proposed retryInitialDelayMs / retryMaxDelayMs fields follow the existing maxRetries / retryErrorCodes precedent in generationConfig exactly. CHANGELOG has no direct reference, but the issue's own triage already confirmed the area is wanted.

Size: touches core paths (packages/core/src/core, packages/core/src/models). Production logic ≈ 35 lines (settingsSchema 22, geminiChat 7, contentGenerator 2, constants 2, types 2); tests 113 lines; schema 8; docs 44 (a markdown table re-wrap). Well under every threshold — no maintainer escalation needed on size.

Approach: the scope feels right. A config field needs the full type → resolver → schema → consumer wiring, and every edit here is part of that chain — no drive-by refactors or unrelated churn. The docs delta looks large but is just the model settings table re-aligning after the generationConfig description grew. Nothing I'd cut.

Moving on to code review. 🔍

中文说明

感谢贡献——这是一个干净、范围明确的 PR。

模板完整 ✓

问题: 已观测到,非理论性问题。关联 issue #7658 记录了 RATE_LIMIT_RETRY_OPTIONS 中硬编码的 initialDelayMs: 60000 / maxDelayMs: 300000(目前只有 maxRetries 可配置),痛点具体——短暂的分钟级配额本可 3 秒恢复,用户却要盯着"Retrying in 43s…"。该 issue 已被 triage 并标记为 welcome-pr

方向: 对齐。Qwen Code 面向限流特征各异的 OpenAI 兼容 provider,提议的 retryInitialDelayMs / retryMaxDelayMs 字段完全遵循 generationConfig 中已有的 maxRetries / retryErrorCodes 先例。CHANGELOG 无直接引用,但 issue 自身的 triage 已确认这个方向是被需要的。

规模: 触及核心路径(packages/core/src/corepackages/core/src/models)。生产逻辑约 35 行(settingsSchema 22、geminiChat 7、contentGenerator 2、constants 2、types 2);测试 113 行;schema 8 行;文档 44 行(markdown 表格重排)。远低于所有阈值——无需因规模转交维护者。

方案: 范围合理。一个配置字段需要完整的 类型 → 解析器 → schema → 消费方 链路,这里每处改动都属于该链路——没有顺手重构或无关改动。文档改动看起来大,只是因为 generationConfig 描述变长后 model 设置表格重新对齐。没有需要砍掉的部分。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

Reviewed at 2d363600cf9993eaa48708dac51eaa3089159283 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review

My independent take before reading the diff: make the two delays optional fields on ContentGeneratorConfig, add them to ModelGenerationConfig and the MODEL_GENERATION_CONFIG_FIELDS allowlist so they resolve from settings, register them in the settings/VS Code schemas, and in geminiChat.ts read cgConfig?.retryInitialDelayMs ?? RATE_LIMIT_RETRY_OPTIONS.initialDelayMs and pass it into getRateLimitRetryDelayMs. The PR does exactly this — no simpler path exists for a config field, and it mirrors the existing maxRetries / retryErrorCodes wiring precisely.

What I verified by reading the code:

  • Wiring is complete and correct. The fields flow settings → modelConfigResolver (iterates MODEL_GENERATION_CONFIG_FIELDS) → ModelGenerationConfig → the generic by-name copy in content-generator-config.tsContentGeneratorConfig (cgConfig) → geminiChat.ts. Same mechanism as maxRetries; nothing hand-rolled.
  • Defaults preserved. geminiChat.ts computes retryInitialDelayMs / retryMaxDelayMs once (outside the retry loop) via ?? RATE_LIMIT_RETRY_OPTIONS.*, then overrides them after spreading the defaults into getRateLimitRetryDelayMs. Unset → the existing 60s / 300s behavior is unchanged.
  • No missed read sites. The only production caller of getRateLimitRetryDelayMs is geminiChat.ts:2392, and the override is applied there. getRateLimitRetryDelayMs consumes options.initialDelayMs / maxDelayMs, and the Retry-After cap (retryAfterMaxDelayMs) also picks up the configured max.
  • Tests target the real behavior. geminiChat.test.ts uses fake timers to throw two 429 stream errors and asserts the RETRY events report 3000ms then 5000ms (backoff capped at the configured max) before recovering; modelConfigResolver.test.ts asserts the fields resolve from settings and stay undefined when omitted.
  • House style: ESM, no new any, collocated tests, kebab-case files. Clean.

Two non-blocking observations:

  • The schema declares type: 'number' with no minimum, so a zero/negative delay is technically possible — but this matches the existing maxRetries field, so it's consistent rather than a defect.
  • With a low configured retryMaxDelayMs, a larger server Retry-After is capped at the configured max, so the client may retry sooner than the server suggested. That's the user's explicit intent (shorter waits) and the point of the issue — worth knowing, not a blocker.

Test evidence (the PR's own CI)

  • Qwen Code CI (event pull_request): status completed, conclusion action_required — GitHub is holding this first-time fork contributor's workflow run for maintainer approval, so the unit test suite has not executed yet. The tests above have not run green in CI at review time.
  • precheck-pr / precheck: ✅ pass (10s).
  • label / authorize / review-config: ✅ pass.
  • tmux-testing: skipped (not triggered).

Real-scenario testing: N/A. This is config plumbing whose observable effect (stream-side 429 retry timing) only manifests against a rate-limiting provider and can't be reliably reproduced in a local TUI session — the fake-timer unit test is the right oracle. Per the static-review rule, PR code was not executed.

Not verified: the unit tests passing in CI (blocked on a maintainer approving the Qwen Code CI workflow run); end-to-end against a real throttling provider (author also flagged this as out of scope).

中文说明

代码审查

读 diff 前我的独立方案:把两个延迟做成 ContentGeneratorConfig 的可选字段,加入 ModelGenerationConfigMODEL_GENERATION_CONFIG_FIELDS 白名单以便从 settings 解析,在 settings / VS Code schema 中注册,并在 geminiChat.ts 中读取 cgConfig?.retryInitialDelayMs ?? RATE_LIMIT_RETRY_OPTIONS.initialDelayMs 传给 getRateLimitRetryDelayMs。PR 正是这么做的——配置字段没有更简路径,且完全复刻了现有 maxRetries / retryErrorCodes 的接线。

通过阅读代码确认:

  • 接线完整且正确。 字段流向 settings → modelConfigResolver(遍历 MODEL_GENERATION_CONFIG_FIELDS)→ ModelGenerationConfigcontent-generator-config.ts 中按名的通用拷贝 → ContentGeneratorConfigcgConfig)→ geminiChat.ts。与 maxRetries 同一机制,无手写逻辑。
  • 默认行为保留。 geminiChat.ts 在重试循环外通过 ?? RATE_LIMIT_RETRY_OPTIONS.* 计算一次,再在 spread 默认值后覆盖传入 getRateLimitRetryDelayMs。未设置时维持现有 60s / 300s 行为。
  • 无遗漏读取点。 getRateLimitRetryDelayMs 唯一的生产调用方是 geminiChat.ts:2392,覆盖已应用于此。该函数消费 options.initialDelayMs / maxDelayMs,Retry-After 上限(retryAfterMaxDelayMs)也会采用配置的 max。
  • 测试针对真实行为。 geminiChat.test.ts 用 fake timers 抛出两次 429 流错误,断言 RETRY 事件依次报告 3000ms5000ms(退避受配置 max 封顶)后恢复;modelConfigResolver.test.ts 断言字段从 settings 解析、未设置时保持 undefined
  • 代码风格: ESM、无新增 any、测试同目录、kebab-case 文件名。干净。

两点非阻塞观察:

  • schema 声明 type: 'number' 但无最小值,理论上可设零/负延迟——但与现有 maxRetries 字段一致,属一致性而非缺陷。
  • 当配置的 retryMaxDelayMs 较低时,更大的服务端 Retry-After 会被配置 max 封顶,客户端可能比服务端建议更早重试。这是用户的明确意图(更短等待),也是 issue 的目标——值得了解,非阻塞。

测试证据(PR 自身 CI)

  • Qwen Code CI(event pull_request):状态 completed,结论 action_required——GitHub 正在等待维护者批准这位首次贡献的 fork 作者的工作流运行,因此单元测试套件尚未执行。审查时上述测试尚未在 CI 中跑绿。
  • precheck-pr / precheck:✅ 通过(10s)。
  • label / authorize / review-config:✅ 通过。
  • tmux-testing:跳过(未触发)。

真实场景测试:N/A。 这是配置接线,其可观测效果(流式 429 重试时序)只在限流 provider 下显现,无法在本地 TUI 会话中可靠复现——fake-timer 单元测试是正确的验证手段。按静态审查规则,未执行 PR 代码。

未验证: 单元测试在 CI 中通过(需维护者批准 Qwen Code CI 工作流运行);对真实限流 provider 的端到端验证(作者也标注为超出范围)。

Qwen Code · qwen3.8-max-preview

Reviewed at 2d363600cf9993eaa48708dac51eaa3089159283 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal, precedent-following change that does exactly what the welcomed issue asked; the only thing keeping this from a 5 is that the CI test suite hasn't executed yet (held for maintainer approval), so the green-tests confirmation is still pending.

Stepping back: this is the kind of PR that's easy to say yes to on merit, not because I ran out of reasons to say no. The problem is real and was already welcomed by maintainers (#7658), the implementation is the minimal correct wiring — the same path maxRetries already travels — defaults are preserved when the fields are unset, and the tests genuinely exercise the new behavior rather than asserting trivia. My independent proposal matched the diff line-for-line; I didn't find a simpler path it missed.

The one reservation is process, not code: the Qwen Code CI suite is in action_required and won't run until a maintainer approves the workflow run for this first-time fork contributor. I'm approving on the strength of the static review plus the included tests, but a maintainer should still approve that CI run and confirm it's green before merging (branch protection will require it anyway). If I had to maintain this in six months, I'd thank the author — it's small, obvious, and well-tested.

中文说明

置信度:4/5 —— 干净、最小化、遵循先例的改动,完全做到了被欢迎的 issue 所要求的内容;唯一让它达不到 5 分的是 CI 测试套件尚未执行(等待维护者批准),因此"测试跑绿"的确认仍待完成。

退一步看:这个 PR 之所以容易肯定,是因为它本身过硬,而不是因为我找不到拒绝的理由。问题真实且已被维护者欢迎(#7658),实现是最小的正确接线——与 maxRetries 已走过的路径完全一致——字段未设置时默认行为保留,测试也真正验证了新行为而非断言琐碎内容。我的独立方案与 diff 逐行吻合;没有找到它遗漏的更简路径。

唯一的保留是流程层面,而非代码:Qwen Code CI 套件处于 action_required,在维护者批准这位首次贡献的 fork 作者的工作流运行之前不会执行。我基于静态审查加上随附测试给出批准,但维护者在合并前仍应批准该 CI 运行并确认其跑绿(分支保护本来也会要求)。如果六个月后由我维护这段代码,我会感谢作者——它小而清晰、测试充分。

Qwen Code · qwen3.8-max-preview

Reviewed at 2d363600cf9993eaa48708dac51eaa3089159283 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Welcome and thanks for your first contribution!

Clean, well-structured PR. The retryInitialDelayMs / retryMaxDelayMs fields are correctly wired through the full stack (types → constants → contentGenerator → geminiChat → settings schema), defaults fall back correctly, and tests cover both the configured and resolver paths. Docs updated too. 👍

Two non-blocking suggestions posted as inline comments:

  1. Spread-then-override pattern in geminiChat.ts works but could be cleaner
  2. No validation on delay values (negative/zero/max < initial) — downstream Math.min/max prevents crashes but a debugLogger.warn would help debuggability

Both are non-blocking. Great first contribution — looking forward to more! 🎉

— qwen3.8-max-preview via Qwen Code /review

@wenshao
wenshao added this pull request to the merge queue Jul 25, 2026
Merged via the queue into QwenLM:main with commit 0b5116a Jul 25, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stream rate-limit retry delay is hardcoded to 60s/120s/240s — not configurable

4 participants