Skip to content

fix(core): support per-provider stream idle timeout - #9795

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
yu-xin-c:codex/per-provider-stream-idle-timeout
Aug 24, 2026
Merged

fix(core): support per-provider stream idle timeout#9795
wenshao merged 1 commit into
QwenLM:mainfrom
yu-xin-c:codex/per-provider-stream-idle-timeout

Conversation

@yu-xin-c

Copy link
Copy Markdown
Contributor

What this PR does

This exposes streamIdleTimeoutMs as a model-scoped generation setting, including modelProviders[].generationConfig, and carries it through model registry resolution and agent configuration. It also adds the generated settings schema entry, regression coverage for provider precedence and provider switching, and documentation for the idle/lifetime guard interaction.

Why it's needed

The global QWEN_STREAM_IDLE_TIMEOUT_MS variable is too broad when one Qwen Code configuration mixes responsive cloud providers with a queued local OpenAI-compatible server. Local queue wait plus prompt prefill can legitimately produce several minutes of SSE silence, while repeated watchdog retries re-enter the queue and add load. A per-provider setting keeps the stricter global default for cloud routes without making a slow local route unusable.

Reviewer Test Plan

How to verify

  1. Configure an OpenAI-compatible provider with generationConfig.streamIdleTimeoutMs: 1000 and set the global QWEN_STREAM_IDLE_TIMEOUT_MS=100.
  2. Point it at an SSE test endpoint that sends one role chunk, waits 350 ms, then sends content and [DONE].
  3. Confirm the provider-backed request completes. Remove the provider field and confirm the same request aborts after roughly 100 ms.
  4. Run npx vitest run src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/content-generator-config.test.ts from packages/core (153 passed), npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream inactivity timeout" from packages/core (34 passed), and npx vitest run src/config/settingsSchema.test.ts from packages/cli (43 passed).
  5. Run npm run preflight with the repository's Node 22 toolchain. Clean/install, formatting, lint, build, and typecheck pass. The parallel full-test stage reaches an existing AuthDialog MiniMax keyboard-navigation failure; it reproduces identically on a clean detached worktree at base commit 431a0bd. The original server failure passes in the three-file rerun, the two transient server assertions from that rerun pass 2/2 when targeted, and workspace-agents.test.ts passes 49/49.

Evidence (Before & After)

N/A — no visual UI change. In the local mock-SSE probe, the control configuration exited 1 with No stream activity for 100ms after 1 chunks; the same request with the provider override exited 0 and printed provider timeout works.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

macOS arm64, Node.js v22.22.2, local source build, and a mock OpenAI-compatible SSE endpoint.

Risk & Scope

  • Main risk or tradeoff: An explicit model value now overrides the global idle-timeout environment variable for that model; validation limits it to an integer from 0 through the platform timer maximum.
  • Not validated / out of scope: Anthropic and Gemini generators remain unchanged; treating raw SSE comments as activity and making the total stream lifetime provider-specific are out of scope.
  • Breaking changes / migration notes: None. Existing configurations continue to use the environment value or the 240-second default when the new field is absent; 0 disables only the idle guard, not the separate total-lifetime guard.

Linked Issues

Addresses the per-provider follow-up in #5975.

中文说明

此 PR 做了什么

此 PR 将 streamIdleTimeoutMs 暴露为模型级生成配置,包括 modelProviders[].generationConfig,并让该字段完整经过模型注册表解析和 Agent 配置链路。同时补充生成的 settings schema、provider 优先级与切换行为的回归测试,以及空闲守卫和总生命周期守卫交互关系的文档。

为什么需要

当同一份 Qwen Code 配置同时包含响应较快的云端 provider 和需要排队的本地 OpenAI-compatible 服务时,全局 QWEN_STREAM_IDLE_TIMEOUT_MS 过于粗粒度。本地服务的排队等待加上长提示词 prefill 可能合理地产生数分钟 SSE 静默,而 watchdog 的重复重试会重新进入队列并增加负载。按 provider 配置后,云端路由仍可保留更严格的全局默认值,同时允许较慢的本地路由正常工作。

审阅者测试计划

验证方法

  1. 配置一个 OpenAI-compatible provider,设置 generationConfig.streamIdleTimeoutMs: 1000,同时设置全局 QWEN_STREAM_IDLE_TIMEOUT_MS=100
  2. 将其指向一个 SSE 测试端点:先发送一个 role chunk,静默 350 ms,再发送内容和 [DONE]
  3. 确认带 provider 配置的请求可以完成;删除该 provider 字段后,确认同一请求在约 100 ms 后中止。
  4. packages/core 运行 npx vitest run src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/content-generator-config.test.ts(153 项通过),在 packages/core 运行 npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream inactivity timeout"(34 项通过),并在 packages/cli 运行 npx vitest run src/config/settingsSchema.test.ts(43 项通过)。
  5. 使用仓库指定的 Node 22 工具链运行 npm run preflight。清理/安装、格式化、lint、构建和类型检查均通过。并行全量测试阶段命中一条现有的 AuthDialog MiniMax 键盘导航失败;该失败可在基准提交 431a0bd 的干净 detached worktree 中完全一致地复现。原始 server 失败在三文件复跑中通过,该次复跑新增的两条瞬时 server 失败在定向运行时 2/2 通过,workspace-agents.test.ts 也以 49/49 通过。

前后证据

N/A — 没有可视化 UI 变化。在本地 mock SSE 探针中,对照配置以退出码 1 结束,并输出 No stream activity for 100ms after 1 chunks;加入 provider 覆盖后,同一请求以退出码 0 结束并输出 provider timeout works

测试平台

操作系统 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

macOS arm64、Node.js v22.22.2、本地源码构建,以及 mock OpenAI-compatible SSE 端点。

风险与范围

  • 主要风险或权衡:显式模型配置现在会覆盖该模型的全局空闲超时环境变量;schema 将其限制为从 0 到平台定时器上限的整数。
  • 未验证 / 范围之外:Anthropic 和 Gemini generator 保持不变;将原始 SSE comment 视为活动、让总流生命周期按 provider 配置不在本 PR 范围内。
  • 破坏性变更 / 迁移说明:无。未配置新字段时,现有配置继续使用环境变量值或 240 秒默认值;0 只禁用空闲守卫,不会禁用独立的总生命周期守卫。

关联 Issue

处理 #5975 中按 provider 配置的后续需求。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 145fced and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 145fced 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed bug, not theoretical. #5975 reports No stream activity for 120000ms after 19 chunks hitting 100% of requests on an OpenAI-compatible provider (labeled welcome-pr, still open). This PR is the per-provider follow-up that issue asks for: a local queued server legitimately produces minutes of SSE silence, and the global QWEN_STREAM_IDLE_TIMEOUT_MS can't be relaxed for one provider without relaxing it for all.

Direction: aligned — per-provider timeout granularity is a natural fit for configs that mix fast cloud routes with a slow local server, and the PR explicitly scopes itself as the follow-up to #5975. CHANGELOG: no direct reference, but the area is clearly relevant.

Size: core paths touched (packages/core/src/models/**, packages/cli/src/config/**) — 15 production lines vs 31 test lines vs 6 generated/schema lines vs 11 docs lines. Well below every threshold; no maintainer-awareness escalation needed.

Approach: minimal and idiomatic. The production change is one field added to the existing MODEL_GENERATION_CONFIG_FIELDS propagation list, one type member, one settings-schema entry, and the regenerated editor schema — the resolver, registry, provider-switch clearing, and agent-config paths all iterate that list generically, so no logic had to be duplicated. Docs and tests track the precedence behavior. Nothing here looks cuttable, and no drive-by changes.

Risk: no elevated risk signals — none of the changed files match the high-risk (revert-correlated) path list.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 是已观测到的 bug,不是理论性问题。#5975 报告在 OpenAI-compatible provider 上 100% 出现 No stream activity for 120000ms after 19 chunks(带 welcome-pr 标签,仍然 open)。此 PR 正是该 issue 所期望的按 provider 配置的后续方案:本地排队服务可能合理地产生数分钟 SSE 静默,而全局 QWEN_STREAM_IDLE_TIMEOUT_MS 无法只放宽某一个 provider。

方向: 对齐——对混合了快速云端路由和慢速本地服务的配置来说,按 provider 的超时粒度是自然的方案,且 PR 明确把自己定位为 #5975 的后续。CHANGELOG:无直接引用,但该领域明显相关。

规模: 触及核心路径(packages/core/src/models/**packages/cli/src/config/**)——15 行生产代码、31 行测试、6 行生成/schema、11 行文档。远低于所有阈值,无需升级维护者关注。

方案: 最小且符合惯例。生产改动仅是向现有的 MODEL_GENERATION_CONFIG_FIELDS 传播列表加一个字段、加一个类型成员、加一条 settings schema 条目,并重新生成编辑器 schema——resolver、registry、provider 切换清理和 agent 配置链路都是通用地遍历该列表,因此没有重复任何逻辑。文档和测试覆盖了优先级行为。没有可砍的部分,也没有夹带改动。

风险: 无升级风险信号——改动文件均未命中高风险(与 revert 相关的)路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 145fced1d1e34ee069efbffb30ae0949bf683662 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Code review

Approach matches what I'd have proposed: don't write new plumbing, just teach the existing generic machinery about the field. I traced every consumer of MODEL_GENERATION_CONFIG_FIELDS to confirm:

  • resolveGenerationConfig (modelConfigResolver) — modelProviders beats model.generationConfig, using field in checks, so an explicit 0 wins instead of falling through a truthiness gap. New tests cover both sources.
  • buildAgentContentGeneratorConfig / applyResolvedModelConfig — the registry value is applied with !== undefined, and the same field list drives the cross-provider clear, so the value can't leak when switching providers (covered by the extended content-generator-config tests).
  • mergeSettingsGenerationConfig and the provider-apply loop in modelsConfig iterate the same list; the CLI's ignored-top-level-field warning picks the field up automatically.
  • Read site (per the AGENTS.md grep-the-read-sites rule): resolveStreamIdleTimeoutMs in the openai pipeline already implements exactly the documented semantics — explicit config wins, <= 0 disables the idle watchdog only, values above the JS timer ceiling fall back to env/default with a warning, and the lifetime guard resolves independently. The schema bounds (0..2_147_483_647) match MAX_STREAM_GUARD_TIMEOUT_MS exactly.
  • Type flow: the new Pick member feeds ModelGenerationConfig, which backs modelProviders[].generationConfig, resolved model configs, and registry entries — no shape conversion anywhere, matching the type's stated invariant.

No correctness, security, or convention findings. The change is the minimal wiring: 15 production lines, with the behavior itself already exercised by existing pipeline tests (disabled at <= 0, custom value honored, config precedence over env, out-of-range fallback).

Test evidence — the PR's own CI

⚠️ CI has not run on this commit yet. Both PR CI workflows report action_required — fork workflow runs from a first-time contributor need maintainer approval before they execute. The only completed check-runs on 145fced are bot orchestration jobs (precheck passed). There is no unit/lint/build signal from GitHub Actions yet, so nothing below the line is CI-verified.

Final CI results for 145fced (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

The PR body reports modelConfigResolver.test.ts / modelRegistry.test.ts / content-generator-config.test.ts (153 passed), the pipeline stream-inactivity subset (34 passed), settingsSchema.test.ts (43 passed), and a npm run preflight with one pre-existing AuthDialog failure reproduced on base 431a0bd — that is the author's claim, not independently verified evidence; it becomes checkable once the workflows above are approved and run.

Sandboxed verification would settle the behavioural claim: @qwen-code /verify — as a sponsored run (the author lacks write access, so /tmux is unavailable): it would A/B against the base build and prove a modelProviders[].generationConfig.streamIdleTimeoutMs value actually overrides QWEN_STREAM_IDLE_TIMEOUT_MS end-to-end in the live watchdog, which no single test in this PR pins (each link of the chain is unit-tested, the full settings-to-watchdog path is not). A maintainer's comment approves the head it's written against; the run carries a pre-execution risk screen and workspace wipe — still, read its report with the same skepticism as the fork's own CI logs, since the code under verification is adversarial input.

中文说明

代码审查

方案与我独立想到的做法一致:不写新的管道代码,只让现有的通用机制认识这个字段。我追踪了 MODEL_GENERATION_CONFIG_FIELDS 的所有使用者:

  • resolveGenerationConfig(modelConfigResolver)——modelProviders 优先于 model.generationConfig,使用 field in 判断,因此显式的 0 会生效而不会被真值判断漏掉。新测试覆盖了两种来源。
  • buildAgentContentGeneratorConfig / applyResolvedModelConfig——registry 值以 !== undefined 应用,跨 provider 切换时的清理也遍历同一字段列表,值不会泄漏(扩展后的 content-generator-config 测试已覆盖)。
  • modelsConfig 里的 mergeSettingsGenerationConfig 与 provider 应用循环遍历同一列表;CLI 的"顶层字段被忽略"警告也会自动包含该字段。
  • 读取点(按 AGENTS.md 的"grep 读取位置"规则):openai pipeline 中的 resolveStreamIdleTimeoutMs 已经实现了与文档完全一致的语义——显式配置优先、<= 0 只禁用空闲守卫、超过 JS 定时器上限的值带警告回退到环境变量/默认值,生命周期守卫独立解析。schema 边界(0..2_147_483_647)与 MAX_STREAM_GUARD_TIMEOUT_MS 完全一致。
  • 类型链路:新增的 Pick 成员进入 ModelGenerationConfig,它支撑 modelProviders[].generationConfig、解析后的模型配置和 registry 条目——全程无形状转换,符合该类型的既定约定。

没有正确性、安全性或规范问题。改动是最小接线:15 行生产代码,行为本身已由现有 pipeline 测试覆盖(<= 0 禁用、自定义值生效、配置优先于环境变量、越界回退)。

测试证据 —— PR 自己的 CI

⚠️ 该提交的 CI 尚未运行。 两个 PR CI 工作流均为 action_required——首次贡献者的 fork 工作流需要维护者批准后才会执行。145fced 上已完成的 check-run 只有机器人编排任务(precheck 通过)。GitHub Actions 尚无单元/lint/构建信号,因此目前没有任何 CI 层面的验证。

PR 正文报告了三文件测试(153 通过)、pipeline 流空闲子集(34 通过)、settingsSchema.test.ts(43 通过)以及 npm run preflight(仅一条可在基准提交 431a0bd 复现的既有 AuthDialog 失败)——这是作者的自述,不是独立验证的证据;待上述工作流被批准运行后才可核实。

沙盒验证可以敲定行为性结论:@qwen-code /verify——以赞助运行方式(作者无写权限,/tmux 不可用):与基准构建 A/B 对比,证明 modelProviders[].generationConfig.streamIdleTimeoutMs 确实能在真实看门狗中端到端覆盖 QWEN_STREAM_IDLE_TIMEOUT_MS——本 PR 没有任何单个测试钉住这一点(链路每一环都有单元测试,但 settings 到看门狗的完整路径没有)。维护者回复触发即批准其书写时所对的提交;该运行带执行前风险筛查与工作区清空——但仍请以对 fork CI 日志同样的怀疑态度阅读其报告,因为被验证的代码本身是不可信输入。

Qwen Code · qwen3.8-max

Reviewed at 145fced1d1e34ee069efbffb30ae0949bf683662 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal wiring of a clearly-needed setting with every propagation path traced; the only thing missing is CI signal, not code confidence.

Stepping back: this is the kind of PR that's easy to vouch for. The problem is a real, 100%-reproducible blocker from #5975 — users on queued local OpenAI-compatible servers can't relax the idle timeout without relaxing it for every provider — and the PR does exactly the follow-up that issue asks for, nothing more. Fifteen production lines that reuse the existing generic field machinery instead of adding parallel plumbing; the runtime semantics (0 disables the idle guard only, out-of-range falls back, lifetime guard untouched) already existed and are exactly what the docs now promise. My independent read of how to build this matches the diff; I found no simpler path and no hangers-on in the change. If I'm maintaining this in six months, there's nothing to curse — it's one more entry in a list the code already iterates.

The reservation, plainly stated: this commit has zero CI evidence — the fork's workflow runs are waiting on maintainer approval, and the test numbers in the PR body are the author's word. The code review stands on its own, but approval should wait for the suite.

Approval deferred until CI lands green on 145fced1d1e34ee069efbffb30ae0949bf683662 — a maintainer needs to approve the pending workflow runs first; once they complete green, the finalize job posts the commit-pinned approval.

中文说明

置信度:4/5 —— 干净、最小的接线,设置项明显有必要,每条传播路径都已追踪;唯一缺的是 CI 信号,而不是对代码的信心。

退一步看:这是一个容易背书的 PR。问题是 #5975 中真实的、100% 可复现的阻塞——在排队的本地 OpenAI-compatible 服务上的用户无法只放宽某一个 provider 的空闲超时——而此 PR 做的正是该 issue 所期望的后续方案,不多不少。15 行生产代码复用了现有的通用字段机制,而不是另起炉灶;运行时语义(0 只禁用空闲守卫、越界回退、生命周期守卫不受影响)早已存在,与文档现在的承诺完全一致。我对如何实现的独立判断与 diff 一致;没找到更简的路径,改动里也没有夹带。六个月后维护这段代码不会让人抱怨——它只是代码本就遍历的列表里多了一个条目。

明确说出保留意见:该提交没有任何 CI 证据——fork 的工作流运行正等待维护者批准,PR 正文中的测试数字是作者的自述。代码审查本身站得住,但批准应等测试套件跑完。

批准已推迟,等待 145fced1d1e34ee069efbffb30ae0949bf683662 上的 CI 变绿——维护者需要先批准待运行的工作流;其全绿完成后,finalize 任务会提交钉住该提交的批准。

Qwen Code · qwen3.8-max

Reviewed at 145fced1d1e34ee069efbffb30ae0949bf683662 · 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.

Reviewed — no blockers. Suggestions are inline.

Test Plan (not a blocker): src/models/modelConfigResolver.test.tsno such file or directory; src/models/modelRegistry.test.tsno such file or directory; src/models/content-generator-config.test.tsno such file or directory; src/core/openaiContentGenerator/pipeline.test.tsno such file or directory; src/config/settingsSchema.test.tsno such file or directory; and 3 more.

中文说明

已审查——无阻断问题。 建议见行内评论。

Test Plan(非阻断):src/models/modelConfigResolver.test.tsno such file or directory; src/models/modelRegistry.test.tsno such file or directory; src/models/content-generator-config.test.tsno such file or directory; src/core/openaiContentGenerator/pipeline.test.tsno such file or directory; src/config/settingsSchema.test.tsno such file or directory; and 3 more。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment on lines +1710 to +1712
streamIdleTimeoutMs: {
type: 'integer',
label: 'Stream Idle Timeout',

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.

[Suggestion] R1-1: The settings knob this PR adds takes precedence over QWEN_STREAM_IDLE_TIMEOUT_MS (per resolveStreamGuardMs in packages/core/src/core/openaiContentGenerator/pipeline.ts and the docs added here), but the StreamInactivityTimeoutError message in that same file (lines 192-196) still tells users to set the env var. That advice is silently ineffective whenever the timeout came from settings/modelProviders: an operator who sets model.generationConfig.streamIdleTimeoutMs: 300000 (the exact configuration the new docs recommend) and then hits a long SSE silence gets "Set QWEN_STREAM_IDLE_TIMEOUT_MS to increase this window (or 0 to disable it)" on every retry attempt; exporting that env var (even to 0) changes nothing because the explicit config field wins, and nothing hints that a settings value is overriding it. In a daemon deployment this turns a one-step fix into an open-ended debugging session.

Evidence: at the reviewed commit, npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream is silent past the idle timeout|explicit streamIdleTimeoutMs config take precedence" in packages/coreTests 2 passed | 158 skipped (160) — one test asserts the env-only advice string fires from an explicit-config timeout, and the other proves config beats env (env stubbed 1000 vs config 5000: the guard does not trip at 1000 ms and trips at 5000 ms).

Fix location: packages/core/src/core/openaiContentGenerator/pipeline.ts (~line 192) — name the settings knobs in the message, or make it source-aware (the pipeline resolves config.streamIdleTimeoutMs in its constructor and knows whether it was explicit):

super(
  `No stream activity for ${idleMs}ms after ${chunksReceived} chunks ` +
    `(stream lifetime: ${streamLifetimeMs}ms). Set ` +
    `model.generationConfig.streamIdleTimeoutMs (or the modelProviders ` +
    `entry's generationConfig.streamIdleTimeoutMs) — or ` +
    `${QWEN_STREAM_IDLE_TIMEOUT_MS_ENV} when no settings value is set — ` +
    `to increase this window (or 0 to disable it).`,
);
中文说明

本 PR 新增的设置项优先级高于 QWEN_STREAM_IDLE_TIMEOUT_MS(见 packages/core/src/core/openaiContentGenerator/pipeline.ts 中的 resolveStreamGuardMs 以及本 PR 新增的文档),但同一文件中的 StreamInactivityTimeoutError 错误信息(第 192-196 行)仍然只提示用户设置环境变量。当超时值来自 settings/modelProviders 时,该提示会静默失效:运维人员按新文档推荐设置 model.generationConfig.streamIdleTimeoutMs: 300000 后,若遇到较长的 SSE 静默,每次重试都会看到 "Set QWEN_STREAM_IDLE_TIMEOUT_MS to increase this window (or 0 to disable it)";此时导出该环境变量(即使设为 0)也不会有任何效果,因为显式配置优先于环境变量,而错误信息中没有任何线索表明有设置值在覆盖它。在守护进程部署场景下,这会把一步就能解决的问题变成无期限的排查。

证据:在被审提交上运行 npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream is silent past the idle timeout|explicit streamIdleTimeoutMs config take precedence"(位于 packages/core)→ Tests 2 passed | 158 skipped (160) —— 其中一个测试断言仅提及环境变量的提示文案会在显式配置超时场景下出现,另一个测试证明配置优先于环境变量(env 设为 1000、配置为 5000:守卫不在 1000 ms 触发,而在 5000 ms 触发)。

修复位置:packages/core/src/core/openaiContentGenerator/pipeline.ts(约第 192 行)—— 在错误信息中同时提及设置项,或让提示感知来源(pipeline 在构造函数中解析 config.streamIdleTimeoutMs,知道该值是否为显式配置),参考上方代码块。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment on lines 157 to 159
samplingParams: { temperature: 0.5 },
streamIdleTimeoutMs: 600000,
contextWindowSize: 200000,

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.

[Suggestion] R1-2: The registry-overlay path in applyResolvedModelConfig (packages/core/src/models/content-generator-config.ts, guarded by registryValue !== undefined) never receives a falsy registry value in any test. The documented streamIdleTimeoutMs: 0 disable case is pinned only in modelConfigResolver.test.ts, which exercises a different function (resolveGenerationConfig, guarded by field in). The code is correct today, but the one-line mutation registryValue !== undefinedregistryValue survives the entire relevant test population green (measured: 153/153 pass under the mutation), which would silently drop a provider's 0 so agent content generators fall back to the env var / 240 s default and abort streams the operator explicitly told to leave unbounded.

Evidence (mutation probe in an isolated scratch tree): baseline 18/18; with the mutation, 153/153 passed across content-generator-config.test.ts + modelConfigResolver.test.ts + modelRegistry.test.ts; adding the streamIdleTimeoutMs: 0 case below fails with expected undefined to be +0 under the mutation (the probe flips) and passes against the real code.

Suggested fix — pin the falsy branch with a sibling case in the with registry-resolved model describe block:

it('should apply a falsy registry-resolved streamIdleTimeoutMs', () => {
  const config = createMockConfig(parentConfig, {
    ...resolvedModel,
    generationConfig: {
      ...resolvedModel.generationConfig,
      streamIdleTimeoutMs: 0,
    },
  });

  const result = buildAgentContentGeneratorConfig(
    config,
    'registry-model-id',
    { authType: 'anthropic' },
  );

  expect(result.streamIdleTimeoutMs).toBe(0);
});
中文说明

applyResolvedModelConfig 中的 registry 覆盖路径(packages/core/src/models/content-generator-config.ts,守卫条件为 registryValue !== undefined)没有任何测试覆盖 falsy 值。文档中记载的 streamIdleTimeoutMs: 0 禁用场景只在 modelConfigResolver.test.ts 中被固定,而该文件测试的是另一个函数(resolveGenerationConfig,守卫条件为 field in)。代码目前是正确,但单行变异 registryValue !== undefinedregistryValue 可以在全部相关测试保持绿色的情况下存活(实测:变异后 153/153 通过),这会静默丢弃 provider 配置的 0,导致 agent 内容生成器回退到环境变量 / 240 秒默认值,从而中止运维人员明确要求不设上限的流。

证据(在隔离 scratch tree 中进行的变异探针):基线 18/18;应用变异后,content-generator-config.test.ts + modelConfigResolver.test.ts + modelRegistry.test.ts 共 153/153 通过;加入下方的 streamIdleTimeoutMs: 0 用例后,在变异代码上以 expected undefined to be +0 失败(探针翻转),在真实代码上通过。

建议修复 —— 在 with registry-resolved model describe 块中新增一个同级用例来固定 falsy 分支(见上方代码块)。

— qwen3.8-max via Qwen Code /review (v0.22.0)

@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 — CI landed green after the review. ✅

@wenshao

wenshao commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

✅ PR 9795 Local Validation Report

I built a real local verification environment for this PR and ran both the targeted unit tests and an end-to-end SSE scenario against a local OpenAI-compatible server.

Environment

  • Repository: QwenLM/qwen-code
  • Branch tested: pr-9795-validation (fetched from refs/pull/9795/head)
  • Node.js: v22.22.2
  • Date: 2026-08-24
  • Test setup: Isolated QWEN_HOME=.qwen/pr-9795-validation, local SSE server on 127.0.0.1:19876

Unit tests (all passed)

cd packages/core && npx vitest run src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/content-generator-config.test.ts
# Test Files  3 passed (3)
# Tests       153 passed (153)

cd packages/core && npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream inactivity timeout"
# Test Files  1 passed (1)
# Tests       34 passed | 126 skipped (160)

cd packages/cli && npx vitest run src/config/settingsSchema.test.ts
# Test Files  1 passed (1)
# Tests       43 passed (43)

Real-world SSE verification

A local OpenAI-compatible SSE server was configured to send one role chunk, wait 350 ms, then send the content chunk and [DONE].

Case 1 — provider streamIdleTimeoutMs: 1000 overrides global QWEN_STREAM_IDLE_TIMEOUT_MS=100

QWEN_STREAM_IDLE_TIMEOUT_MS=100 \
QWEN_HOME=.qwen/pr-9795-validation \
OPENAI_API_KEY=dummy \
node dist/cli.js -p "hello" --model slow-local-model --auth-type openai -o json
  • is_error: false
  • duration_api_ms: 447
  • result: "Hello after 350ms idle"
  • Server received the expected requests.

Conclusion: the per-provider setting successfully extends the idle window beyond the stricter global default.

success

Case 2 — removing the provider field falls back to global 100 ms and aborts

# Same command, but settings.json has generationConfig: {}
  • result: [API Error: No stream activity for 100ms after 1 chunks (stream lifetime: 102.6ms). Set QWEN_STREAM_IDLE_TIMEOUT_MS to increase this window (or 0 to disable it).]
  • Server received repeated requests due to retries.

Conclusion: when the provider-level override is absent, the global environment variable behaves exactly as before.

failure

Overall result

  • All listed unit tests pass.
  • The per-provider streamIdleTimeoutMs setting correctly overrides the global QWEN_STREAM_IDLE_TIMEOUT_MS in a real CLI run.
  • Removing the provider setting reverts to the global behavior.

LGTM for merge from a functional-validation standpoint.


📝 点击查看中文版验证报告

✅ PR 9795 本地验证报告

我为此 PR 搭建了真实本地验证环境,并针对本地 OpenAI 兼容 SSE 服务端运行了单元测试与端到端场景验证。

验证环境

  • 仓库: QwenLM/qwen-code
  • 测试分支: pr-9795-validation(从 refs/pull/9795/head 检出)
  • Node.js: v22.22.2
  • 日期: 2026-08-24
  • 测试配置: 隔离的 QWEN_HOME=.qwen/pr-9795-validation,本地 SSE 服务器 127.0.0.1:19876

单元测试(全部通过)

cd packages/core && npx vitest run src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/content-generator-config.test.ts
# 测试文件 3 个通过
# 测试用例 153 个通过

cd packages/core && npx vitest run src/core/openaiContentGenerator/pipeline.test.ts -t "stream inactivity timeout"
# 测试文件 1 个通过
# 测试用例 34 个通过,126 个跳过

cd packages/cli && npx vitest run src/config/settingsSchema.test.ts
# 测试文件 1 个通过
# 测试用例 43 个通过

真实 SSE 端到端验证

本地 OpenAI 兼容 SSE 服务器配置为:发送一个 role chunk,等待 350 ms,再发送 content chunk 和 [DONE]

场景 1 — provider 配置 streamIdleTimeoutMs: 1000 覆盖全局 QWEN_STREAM_IDLE_TIMEOUT_MS=100

QWEN_STREAM_IDLE_TIMEOUT_MS=100 \
QWEN_HOME=.qwen/pr-9795-validation \
OPENAI_API_KEY=dummy \
node dist/cli.js -p "hello" --model slow-local-model --auth-type openai -o json
  • is_error: false
  • duration_api_ms: 447
  • result: "Hello after 350ms idle"
  • 服务端收到预期请求

结论: provider 级别的设置成功将空闲等待窗口扩展到比全局默认值更长。

success

场景 2 — 移除 provider 字段后回退到全局 100 ms 并触发中断

# 相同命令,但 settings.json 中 generationConfig: {}
  • result: [API Error: No stream activity for 100ms after 1 chunks (stream lifetime: 102.6ms). Set QWEN_STREAM_IDLE_TIMEOUT_MS to increase this window (or 0 to disable it).]
  • 服务端因重试收到多次请求

结论: 当 provider 级别覆盖不存在时,全局环境变量行为与之前一致。

failure

总体结果

  • 列出的单元测试全部通过。
  • 真实 CLI 运行中,provider 级别 streamIdleTimeoutMs 正确覆盖全局 QWEN_STREAM_IDLE_TIMEOUT_MS
  • 移除 provider 设置后回退到全局行为。

从功能验证角度,建议合并(LGTM)。

@wenshao
wenshao added this pull request to the merge queue Aug 24, 2026
Merged via the queue into QwenLM:main with commit f241c19 Aug 24, 2026
114 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.22.2.

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.

3 participants