fix(core): resolve Qwen 3.8 reasoning budget conflicts - #8525
Conversation
# Conflicts: # docs/users/configuration/model-providers.md # packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts # packages/core/src/core/openaiContentGenerator/provider/dashscope.ts
# Conflicts: # docs/users/configuration/model-providers.md # packages/core/src/core/openaiContentGenerator/provider/dashscope.ts
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
@DragonnZhang Thanks for the follow-up work here — but the problem this PR describes was already fixed on main before this review ran.
The stated problem no longer exists on the current base. The PR says "selecting an effort could turn a previously valid configuration into an invalid API request" and that before this fix "the conflicting configurations emitted both fields." That was true in the window between #8472 and #8488 — but #8488 (fix(core): harden Qwen 3.8 reasoning effort wire shape, merged 2026-08-04) already landed: dropConflictingThinkingKnobs guarantees a tiered Qwen 3.8 request never ships reasoning_effort + thinking_budget together (the tier ships alone; every dropped knob is warn-logged). Your diff sits on top of #8488 and rewrites its tests — yet the PR body still describes the pre-#8488 world and never mentions #8488.
What remains is a proposal to flip two deliberate design choices that just shipped:
- Cross-layer conflicts — #8488 resolves them tier-wins (the losing knob is dropped with a warn log). This PR resolves them by layer precedence instead, and both of its resolution paths delete the losing field before the warn logic runs — so conflicts #8488 logs become silent. In particular, a configured
extra_body.thinking_budgetwould make/effortselections quietly no-op. - Explicit same-layer pairs — #8488 resolves them into a valid request. This PR preserves the pair, so configurations that work today would start returning DashScope 400s on principle.
Both are defensible design opinions, but they contradict a decision that went through a full review cycle hours before this triage ran. Reversing a just-merged design needs to argue against that design explicitly — with a concrete configuration harmed by #8488's behavior — not against a conflict shape that no longer ships.
Could you restate the problem against current main? Concretely: a real configuration where #8488's tier-wins + warn behavior hurts a user, and why a silent /effort no-op and a deliberate 400 on same-layer pairs are better outcomes. If the core concern is precedence consistency, an issue discussing the resolution policy with the maintainer who shipped #8488 is the right venue before more code.
中文说明
@DragonnZhang 感谢跟进——但本 PR 描述的问题在本次审查运行前已被 main 修复。
所述问题在当前基线上已不存在。 PR 说"选择 effort 可能使之前有效的配置变成无效 API 请求"、"修复前冲突配置会同时发送两个字段"。这在 #8472 与 #8488 之间的窗口内属实——但 #8488(fix(core): harden Qwen 3.8 reasoning effort wire shape,2026-08-04 合并)已经落地:dropConflictingThinkingKnobs 已保证 Qwen 3.8 分档请求绝不会同时携带 reasoning_effort + thinking_budget(tier 单独发送,每个被丢弃的字段都记录 warn 日志)。你的 diff 正是基于 #8488 之上并重写了它的测试——但 PR 正文仍在描述 #8488 之前的世界,且通篇未提及 #8488。
剩下的部分实际上是提议翻转两个刚刚发布的刻意设计选择:
- 跨层冲突——#8488 裁决 tier 优先(落选字段被丢弃并记录 warn)。本 PR 改为按配置层优先级裁决,且两条裁决路径都在 warn 逻辑运行之前删除落选字段——于是 #8488 会记录日志的冲突变成静默丢弃。特别地,配置了
extra_body.thinking_budget的用户使用/effort会悄悄失效。 - 同层显式成对配置——#8488 会将其解析为有效请求。本 PR 保留成对字段,使得今天能正常工作的配置开始收到 DashScope 400——出于原则。
两者都是可以成立的设计观点,但它们与数小时前刚经过完整审查周期并合并的决定相矛盾。翻转刚合并的设计,需要明确针对那个设计提出论证——给出一个被 #8488 行为实际损害的具体配置——而不是针对一个已不再被发送的冲突形态。
能否基于当前 main 重新陈述问题?具体说:一个 #8488 的 tier 优先 + warn 行为确实伤害用户的真实配置,以及为什么静默的 /effort 失效和同层成对配置刻意 400 是更好的结果。如果核心关切是优先级一致性,先开一个 issue 与发布 #8488 的 maintainer 讨论解析策略,再写更多代码。
— Qwen Code · qwen3.8-max
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
1 similar comment
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
Review —
|
|
Addressed the latest review in acfebe2:
I could not verify live qwen3.8-max acceptance of thinking_budget because this environment has no DashScope API credentials. That external API validation remains explicitly unverified rather than inferred from the local adapter tests. |
Review:
|
|
Addressed the deterministic review findings in
Validation passed: both focused Core suites (255 tests), Core TypeScript typecheck, targeted ESLint, Prettier, and the repository pre-commit hook. I intentionally did not guess on #1 because it changes product semantics. Which behavior should the interactive command have when static
Once that policy is confirmed I can implement the matching behavior. |
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not reviewed: reverse audit — stopped at the 5-round hard cap without two consecutive dry rounds; convergence not established (rounds 2-5 each reported findings — all verified, or excluded as re-reports of threads already on the PR).
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI; the touched integration file (cli/acp-integration.test.ts, formatting-only change) ran locally green (11/11), but the broader integration suite did not run locally.
Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — finished within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks completed within budget..
中文说明
已审查。 建议见行内评论。
未审查:reverse audit — stopped at the 5-round hard cap without two consecutive dry rounds; convergence not established (rounds 2-5 each reported findings — all verified, or excluded as re-reports of threads already on the PR)。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI; the touched integration file (cli/acp-integration.test.ts, formatting-only change) ran locally green (11/11), but the broader integration suite did not run locally。
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — finished within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| // qwen3.8 rejects reasoning_effort with thinking_budget. Resolve the | ||
| // highest-priority layer once; when both fields are explicit in that | ||
| // layer, reasoning_effort keeps the pre-existing provider behavior. | ||
| const selectedThinkingKnob = isTieredQwenModel |
There was a problem hiding this comment.
[Suggestion] R9-1: The ACP surface (session/set_config_option → reasoning_effort, packages/cli/src/acp-integration/acpAgent.ts:5451) was not updated for this PR's wire-precedence flip: it reports unconditional success as soon as the config accepts the tier, even when a higher-priority thinking_budget now shadows it on the wire — the exact misreport this PR fixed in the /effort command, the picker hook, and both SDKs via getReasoningEffortOverride. — Failure scenario: config with model: qwen3.8-max + samplingParams.thinking_budget: 2048 (or extra_body.thinking_budget); the user sets effort to xhigh from an ACP client → the handler returns success with currentValue === 'xhigh', but after this flip selectDashScopeThinkingKnob picks the budget and deletes reasoning_effort, so every request ships thinking_budget: 2048 and the tier never reaches the wire, while the client keeps showing xhigh as the active setting. Probe-verified at this head: success returned, getReasoningEffortOverride called 0 times.
Suggested fix (in acpAgent.ts, not anchored here):
// setConfigOption, reasoning_effort case, after applyReasoningEffort succeeds:
const override = session.getConfig().getReasoningEffortOverride?.();
// surface `override` in the response (warning note / config-option metadata)
// instead of silent success, mirroring formatEffortChangeMessage / handleSetEffort中文说明
R9-1: ACP 入口(session/set_config_option → reasoning_effort,packages/cli/src/acp-integration/acpAgent.ts:5451)没有随本 PR 的 wire 优先级翻转更新:配置接受档位即无条件返回成功,即使更高优先级的 thinking_budget 现在会在 wire 上遮蔽该档位——这正是本 PR 在 /effort 命令、选择器 hook 和两个 SDK 中通过 getReasoningEffortOverride 修复的同类误报。失败场景:配置 model: qwen3.8-max + samplingParams.thinking_budget: 2048(或 extra_body.thinking_budget);用户通过 ACP 客户端设置 effort 为 xhigh → handler 返回成功且 currentValue === 'xhigh',但翻转后 selectDashScopeThinkingKnob 选中 budget 并删除 reasoning_effort,每个请求都发送 thinking_budget: 2048,档位永远到不了 wire,而客户端仍显示 xhigh 为生效设置。已在当前 head 探针验证:返回成功且 getReasoningEffortOverride 被调用 0 次。建议修复:在 applyReasoningEffort 成功后调用 getReasoningEffortOverride() 并在响应中呈现,对齐 formatEffortChangeMessage / handleSetEffort。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| return t( | ||
| 'Reasoning effort set to {{tier}}, but thinking is currently disabled; after thinking is re-enabled, {{source}}.{{field}} will still have higher priority.', | ||
| { tier, source: override.source, field: override.field }, |
There was a problem hiding this comment.
[Suggestion] R9-2: The disabled-branch override message assumes the override is a value knob (effort/budget), but getReasoningEffortOverride() also returns field: 'enable_thinking' for an enable_thinking: false off-switch, producing self-contradictory guidance. — Failure scenario: tiered model + reasoning: false + extra_body: { enable_thinking: false } (thinking hard-disabled in two places), user runs /effort high → the message says "after thinking is re-enabled, extra_body.enable_thinking will still have higher priority" — but that off-switch is the documented escape hatch translated to reasoning_effort: 'none' on the wire, so re-enabling reasoning can never re-enable thinking while that exact knob is present: the message names as the future competitor the very knob that makes its precondition unreachable. Probe rendered the message verbatim at this head; with a value knob (thinking_budget) the same sentence is coherent.
| return t( | |
| 'Reasoning effort set to {{tier}}, but thinking is currently disabled; after thinking is re-enabled, {{source}}.{{field}} will still have higher priority.', | |
| { tier, source: override.source, field: override.field }, | |
| if (override.field === 'enable_thinking') { | |
| return t( | |
| 'Reasoning effort set to {{tier}}, but thinking is currently disabled; {{source}}.{{field}} keeps thinking off and takes precedence over the tier — remove it to let the tier take effect.', | |
| { tier, source: override.source, field: override.field }, | |
| ); | |
| } | |
| return t( | |
| 'Reasoning effort set to {{tier}}, but thinking is currently disabled; after thinking is re-enabled, {{source}}.{{field}} will still have higher priority.', | |
| { tier, source: override.source, field: override.field }, |
中文说明
R9-2: thinking 已禁用分支的覆盖提示假定覆盖项是值旋钮(effort/budget),但 getReasoningEffortOverride() 对 enable_thinking: false 关闭开关也会返回 field: 'enable_thinking',产生自相矛盾的指引。失败场景:分档模型 + reasoning: false + extra_body: { enable_thinking: false }(两处硬禁用思考),用户执行 /effort high → 消息说"after thinking is re-enabled, extra_body.enable_thinking will still have higher priority"——但该关闭开关正是文档化的逃生通道,wire 上被转换为 reasoning_effort: 'none',只要它存在,重新开启 reasoning 永远无法恢复思考:消息把令其前提不可达的旋钮说成未来的竞争者。已在本 head 探针逐字复现该消息;值旋钮(thinking_budget)场景下同一句子是成立的。修复:在该分支特判 override.field === 'enable_thinking',改为提示该旋钮保持思考关闭且优先于档位(删除它档位才能生效)。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| describe('reasoning effort override', () => { | ||
| it('reports a higher-priority DashScope knob that shadows reasoning effort', () => { |
There was a problem hiding this comment.
[Suggestion] R9-3: Config.getReasoningEffortOverride() is never tested with currentEffort === undefined (reasoning: false or absent) — every case in this new describe block sets a tier, yet the no-tier state is exactly what the new "thinking disabled + static override" UI path reads. — Failure scenario: with qwen3.8-max, reasoning: false, and extra_body: { thinking_budget: 4096 }, /effort high calls the real getter with currentEffort === undefined; a plausible future simplification ("no active tier → no override", an early return when getReasoningEffort() is undefined) leaves every test in this block green — all have a tier set — and /effort silently regresses to the plain "it will take effect when thinking is re-enabled" message, re-introducing the misleading confirmation this PR exists to remove. Probe-verified: the suggested cases pass on this head; applying the feared early return fails only the new cases while all 14 pre-existing cases stay green.
Suggested fix:
it('reports a static override when thinking is disabled (no active tier)', () => {
// config: reasoning: false, extra_body: { thinking_budget: 4096 }
// expect getReasoningEffortOverride() →
// { source: 'extra_body', field: 'thinking_budget' }
});
// plus a variant with `reasoning` omitted entirely中文说明
R9-3: Config.getReasoningEffortOverride() 从未在 currentEffort === undefined(reasoning: false 或缺省)下被测试——这个新 describe 块的所有用例都设置了档位,而无档位状态恰是新的"thinking 已禁用 + 静态覆盖"UI 路径所读取的。失败场景:qwen3.8-max + reasoning: false + extra_body: { thinking_budget: 4096 } 时,/effort high 会以 currentEffort === undefined 调用真实 getter;未来可能的简化("无激活档位 → 无覆盖",即 getReasoningEffort() 为 undefined 时提前返回)会让本块所有测试保持绿色——它们都设置了档位——/effort 将静默回退到普通的"it will take effect when thinking is re-enabled"消息,重新引入本 PR 要消除的误导性确认。已探针验证:建议的用例在当前 head 通过;施加担心的提前返回后仅新用例失败,14 个既有用例仍为绿。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| expect(result['reasoning_effort']).toBeUndefined(); | ||
| expect(result['enable_thinking']).toBeUndefined(); | ||
| expect(result['thinking_budget']).toBe(4096); |
There was a problem hiding this comment.
[Suggestion] R9-4: The new budget-selection drop branch in mergeExtraBodyAndResolveKnobs can drop two fields at once, but no test exercises both drops together — the two inner conditions are only ever tested in isolation. — Failure scenario: the branch is reachable with all three knobs present: config reasoning: { effort: 'high' } + extra_body: { thinking_budget: 4096 } (selection = budget) + request-level enable_thinking: false. Mutation-verified at this head: changing the two inner ifs to if/else if ships enable_thinking: false alongside the winning budget while the entire existing suite passes 132/132 — the suite provably cannot distinguish that regression from correct code.
Suggested fix:
it('drops both a configured tier and a request-level disable when a budget wins', () => {
// config: { reasoning: { effort: 'high' }, extra_body: { thinking_budget: 4096 } }
// request-level: enable_thinking: false
// expect reasoning_effort undefined, enable_thinking undefined,
// thinking_budget 4096, warn dropped: ['reasoning_effort', 'enable_thinking']
});中文说明
R9-4: mergeExtraBodyAndResolveKnobs 中新增的 budget 获胜删除分支可以一次删除两个字段,但没有任何测试同时触发两个删除——两个内部条件从未被一起测试。失败场景:三个旋钮同时存在时该分支可达:配置 reasoning: { effort: 'high' } + extra_body: { thinking_budget: 4096 }(选中 budget)+ 请求层 enable_thinking: false。已在当前 head 做变异验证:把两个 if 改成 if/else if 后,enable_thinking: false 会与获胜 budget 一起发送,而现有套件 132/132 全部通过——该套件从证明上无法区分这一回归与正确代码。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| it('keeps a legacy Qwen budget over an opaque none effort', () => { | ||
| const generator = new DashScopeOpenAICompatibleProvider( | ||
| { |
There was a problem hiding this comment.
[Suggestion] R9-5: The legacy branch of dropConflictingThinkingKnobs changed in this diff from a presence check ('thinking_budget' in merged — verified at merge base 7425e42fc7) to a value check (merged['thinking_budget'] === undefined), flipping behavior for undefined-valued budget keys on legacy models — but no test exercises a legacy model with an undefined-valued thinking_budget key (legacy models skip withoutNullishThinkingKnobs). — Failure scenario: qwen3.7-max + extra_body: { thinking_budget: undefined, reasoning_effort: 'max' } — the exact shape the tiered undefined-budget test proves occurs in practice. Mutation-verified: restoring the pre-PR presence check passes the entire suite (132/132) while silently dropping the user's reasoning_effort: 'max' for a placeholder key that serializes to nothing — the wire loses the override and gains nothing.
Suggested fix:
it('keeps a legacy reasoning_effort over an undefined-valued budget key', () => {
// model: 'qwen3.7-max'
// extra_body: { thinking_budget: undefined, reasoning_effort: 'max' }
// expect result['reasoning_effort'] === 'max', no warn fires
});中文说明
R9-5: dropConflictingThinkingKnobs 的 legacy 分支在本 diff 中从存在性检查('thinking_budget' in merged——已在 merge base 7425e42fc7 核实)改为值检查(merged['thinking_budget'] === undefined),翻转了 legacy 模型上 undefined 值 budget 键的行为——但没有测试覆盖 legacy 模型 + undefined 值 thinking_budget 键(legacy 模型跳过 withoutNullishThinkingKnobs 净化)。失败场景:qwen3.7-max + extra_body: { thinking_budget: undefined, reasoning_effort: 'max' }——正是分档 undefined-budget 测试证明实际会出现的形态。变异验证:恢复 PR 前的存在性检查后整套测试(132/132)依然通过,却会为一个序列化后不存在的占位键静默丢弃用户的 reasoning_effort: 'max'——wire 失去了覆盖值而一无所获。
— qwen3.8-max via Qwen Code /review (v0.21.9)
| { effort }, | ||
| ); | ||
| return Boolean((response as Record<string, unknown> | null)?.applied); | ||
| return parseEffortStatus(response) ?? { applied: false, override: null }; |
There was a problem hiding this comment.
[Suggestion] R9-6: The new setEffortStatus fallback for a set_effort response lacking a boolean applied has no covering test in either SDK (Python mirror: packages/sdk-python/src/qwen_code_sdk/query.py:515). — Failure scenario: paired with a CLI whose set_effort success response omits applied (a pre-PR CLI or future protocol drift), parseEffortStatus returns undefined and this ?? fallback is the only thing keeping setEffort from throwing a TypeError on .applied of undefined instead of returning the documented graceful false (the pre-diff code defended against exactly this with Boolean(response?.applied) / response.get("applied", False)). Probe-verified in both SDKs at this head: the new tests pass on PR code; dropping the fallback fails the probe in TS (expected undefined to deeply equal {applied: false, override: null}) and Python (assert None == {'applied': False, 'override': None}) while all existing effort tests stay green.
Suggested fix:
// Query.test.ts: answer the set_effort control request with {} (no `applied`);
// assert setEffortStatus resolves to { applied: false, override: null }.
// Parity test in test_query_core.py for set_effort_status.中文说明
R9-6: 新的 setEffortStatus 对缺少布尔 applied 的 set_effort 响应的兜底在两个 SDK 中都没有覆盖测试(Python 对应:packages/sdk-python/src/qwen_code_sdk/query.py:515)。失败场景:与 set_effort 成功响应缺少 applied 的 CLI(PR 前的 CLI 或未来协议漂移)配对时,parseEffortStatus 返回 undefined,这个 ?? 兜底是避免 setEffort 对 undefined 取 .applied 抛 TypeError(而非返回文档承诺的优雅 false)的唯一防线(diff 前代码正是用 Boolean(response?.applied) / response.get("applied", False) 防御这一形态)。已在当前 head 对两个 SDK 探针验证:新测试在 PR 代码上通过;删除兜底后 TS 探针失败(expected undefined to deeply equal {applied: false, override: null})、Python 探针失败(assert None == {'applied': False, 'override': None}),而现有 effort 测试全部保持绿色。
— qwen3.8-max via Qwen Code /review (v0.21.9)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action required (PR #8525)This round found nothing actionable:
The PR is in Critical-only mode after five change-producing rounds. The deferred non-Critical items listed by the workflow (one review plus six inline findings from the automated reviewer) were excluded from this round's actionable scope by the deterministic filter; per policy they remain open for human follow-up and were intentionally left untouched — no code changes, no thread resolutions, and no comment replies for them. No code changes were made and no commit was created this round. The branch head remains 中文说明Autofix 评审轮次:无需处理(PR #8525)本轮没有发现任何可处理的内容:
该 PR 在经历五个产生改动的轮次后已进入仅处理 Critical 的模式。工作流列出的被延后的非 Critical 条目(一条评审,以及来自自动评审者的六条行内发现)已被确定性过滤器排除在本轮可处理范围之外;按策略它们保持开放、留待人工跟进,本轮有意不做任何处理 —— 不修改代码、不解决任何讨论串、也不对这些条目撰写评论回复。 本轮没有修改任何代码,也没有创建任何提交。分支头部仍为 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (
中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Maintainer verification — real local stack, BEFORE/AFTER A/BI built both sides from source and exercised them against a real DashScope-compatible endpoint that enforces the documented rule this PR exists for ( Builds — 1. Wire bodies — 17 configurations, same settings, two buildsFour configurations shipped an illegal field pair before this PR and now do not (
These are exactly the holes in the old guard: it returned early on Precedence now follows the documented layer order — A1 ( thinkingMandatory — A11 ( No regression on the common paths — all five effort tiers with no conflicting knob (B1–B5) are byte-identical between builds, as are the legacy The once-per-generator warning fires correctly — one 2.
|
| Check | Result |
|---|---|
packages/core — dashscope + pipeline + config |
799 passed |
packages/cli — effort-command, use-effort-command, systemController |
37 passed, 2 failed |
packages/sdk-typescript — Query.test.ts |
66 passed |
packages/sdk-python — full unit suite (py3.12 container) |
148 passed |
npm run typecheck |
pass |
npm run lint |
pass |
prettier --check on changed files |
pass |
The 2 CLI failures are get_usage_info › returns dashboard … timeouts; they fail identically on the merge-base, so they are pre-existing and unrelated to this PR.
Notes for the merge decision
Nothing here blocks the merge. Three things worth a second look:
- Behaviour change beyond "avoid an invalid request". For a valid pre-existing config like
samplingParams.thinking_budget+/effort high,mainsent the tier; this PR sends the budget instead. That is the correct precedence, but it means/effortbecomes a no-op on the wire for those users. The new/effortmessage and SDK status are what make that discoverable — good — but it is a semantic change, not purely a rejection fix, and the PR description currently frames it only as the latter. - PR description does not cover the whole diff. The body describes only the DashScope request-construction change, while the diff also adds public SDK surface (
EffortStatus/EffortOverride/setEffortStatus/getInitialEffortStatusin TS,EffortStatus/set_effort_status/initial_effort_statusin Python), theeffort_statusfield on the control-protocolinitializeresponse, and the/effortmessage rework. New public API deserves to be in the description and release notes. - Minor SDK asymmetry. The CLI emits
reasononeffort_status, and the TSEffortStatuscarries it, but the PythonEffortStatusTypedDict and_parse_effort_statusdrop it — Python callers can only reconstruct a reason fromoverride. Alsointegration-tests/cli/acp-integration.test.tscontains a formatting-only reflow unrelated to this change.
Verdict: verified. The illegal field pair is eliminated in every configuration I could construct that produced it, precedence matches the documented order, all five effort tiers and the legacy Qwen paths are unchanged, and the reporting surfaces now tell the truth.
中文版本
维护者验证 —— 本地真实环境,BEFORE/AFTER 双臂对比
我从源码分别构建了两侧,并针对一个真实的 DashScope 兼容端点进行验证,该端点强制执行本 PR 所针对的既有规则(reasoning_effort 与 thinking_budget 同时出现 → HTTP 400 InvalidParameter)。下面每条结论都来自记录到的真实请求体或真实的 TUI / SDK 运行,而非单测断言。
构建:BEFORE = merge-base main @ 7425e42,AFTER = PR head ea91a35。两侧均执行 npm install && npm run build && npm run bundle,以 dist/cli.js 驱动。每个场景使用隔离的 QWEN_HOME;通过 DASHSCOPE_PROXY_BASE_URL 选中 provider,确保真正走 DashScope 代码路径。
1. 线上请求体 —— 17 组配置,相同设置,两个构建
修复前有四组配置会发出非法字段对,现在不会了(ex=1 表示 CLI 因端点返回 400 而非零退出):
| # | 配置 | BEFORE | AFTER |
|---|---|---|---|
| A7 | samplingParams: { reasoning_effort: 'none', thinking_budget: 2048 } |
effort="none" budget=2048 → 400 |
effort="none" → 200 |
| A8 | extra_body.reasoning_effort='none' + samplingParams.thinking_budget |
effort="none" budget=2048 → 400 |
effort="none" → 200 |
| A9 | samplingParams: { reasoning_effort: 4, thinking_budget: 2048 } |
effort=4 budget=2048 → 400 |
effort=4 → 200 |
| A12 | 旧模型 qwen3-max,reasoning_effort='none' + budget |
effort="none" budget=2048 → 400 |
budget=2048 → 200 |
这正是旧守卫的漏洞所在:它在 effort === 'none' 以及任何非字符串 effort 时提前返回,导致两个字段一起发出。这是真实存在的既有缺陷,已被修复。
优先级现在符合文档中的层级顺序:A1(/effort high + samplingParams.thinking_budget)与 A2(/effort high + extra_body.thinking_budget)此前发出 reasoning_effort 并静默丢弃用户显式配置的 budget,现在改为发出 budget。A3/A4(effort 胜出、同层显式配对)行为不变。
thinkingMandatory:A11(qwen3.8-max-preview 形态:thinkingMandatory: true + extra_body.enable_thinking: false)此前会发出该系列会拒绝的 reasoning_effort: 'none',现在完全不发送任何 thinking 字段。pipeline 中新增的剥离逻辑是必要的。
常见路径无回归:五个 effort 档位在无冲突字段时(B1–B5)两侧完全一致;旧版 qwen3-max 的 on-switch 映射(A6)以及 preset 注入 extra_body.enable_thinking: true + 档位的场景(单独验证)也都一致,两侧均只发 reasoning_effort。
一次性告警行为正确:两次请求的会话中只出现一条 [WARN] DashScope: dropped conflicting thinking knobs { model: 'qwen3.8-max', reasoningEffort: 'high', dropped: [ 'reasoning_effort' ] }。
2. 真实 TUI 中的 /effort
真实 tmux TUI,配置 extra_body.enable_thinking: false,相同按键。BEFORE 显示 Reasoning effort: high (requested; …),但实际线上发出的是 reasoning_effort: 'none' —— 这是既有的误报,现在会正确指出真正生效的字段。在没有冲突配置时,提示文案保持不变。
3. SDK effort 状态
TypeScript SDK 驱动真实 CLI,配置 samplingParams.thinking_budget=2048,调用 query({ effort: 'high' })。BEFORE:setEffort() 返回 true,但该档位从未到达线上,且没有任何状态查询接口。AFTER:getInitialEffortStatus() / setEffortStatus() 返回 applied: false,并带有 override: { source: 'samplingParams', field: 'thinking_budget' } 及可读的 reason。
4. 测试与静态检查(PR head)
| 检查项 | 结果 |
|---|---|
packages/core —— dashscope + pipeline + config |
799 通过 |
packages/cli —— effort-command、use-effort-command、systemController |
37 通过,2 失败 |
packages/sdk-typescript —— Query.test.ts |
66 通过 |
packages/sdk-python —— 完整单测(py3.12 容器) |
148 通过 |
npm run typecheck |
通过 |
npm run lint |
通过 |
改动文件 prettier --check |
通过 |
2 个 CLI 失败是 get_usage_info › returns dashboard … 超时,在 merge-base 上同样失败,属于既有问题,与本 PR 无关。
合并决策参考
没有阻塞项。三点值得再看一眼:
- 行为变更超出了「避免非法请求」的范围。 对于
samplingParams.thinking_budget+/effort high这类原本合法的配置,main发送的是档位,本 PR 改为发送 budget。这个优先级是对的,但意味着这些用户的/effort在线上变成了空操作。新的/effort提示和 SDK 状态让这一点可被发现(很好),但这是语义变更,而不仅仅是修复拒绝问题,而当前 PR 描述只按后者来陈述。 - PR 描述未覆盖完整改动。 正文只描述了 DashScope 请求构建的改动,而 diff 还新增了公开 SDK 接口(TS 侧
EffortStatus/EffortOverride/setEffortStatus/getInitialEffortStatus,Python 侧EffortStatus/set_effort_status/initial_effort_status)、控制协议initialize响应上的effort_status字段,以及/effort文案改造。新增公开 API 应写进描述和 release notes。 - SDK 存在小的不对称。 CLI 在
effort_status中输出reason,TS 的EffortStatus保留了该字段,但 Python 的EffortStatusTypedDict 与_parse_effort_status丢弃了它 —— Python 调用方只能从override反推原因。另外integration-tests/cli/acp-integration.test.ts中包含一处与本次改动无关的纯格式重排。
结论:验证通过。 我能构造出的所有会产生非法字段对的配置都已消除,优先级符合文档顺序,五个 effort 档位与旧版 Qwen 路径保持不变,提示与状态接口现在如实反映实际生效的配置。
The CLI emits a human-readable reason on effort_status and the TypeScript SDK surfaces it, but the Python EffortStatus TypedDict and _parse_effort_status dropped it, leaving Python callers to reconstruct the reason from override. Add reason as an optional field and pass it through, mirroring the TypeScript parser.
|
🤖 Addressed the latest review feedback (round 6/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 6/100 轮)。改动内容与我反驳保留之处如下: Address-review round summary — PR #8525Round input: one issue-level maintainer verification comment (@wenshao, verdict "verified — nothing blocks the merge") carrying three follow-up points. No inline comments, no reviews, no failed checks were actionable this round; Critical-only mode is active. Dispositions below. Feedback points and decisions1. Minor SDK asymmetry — Python drops the effort-status
|
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — I did not run the sdk-typescript vitest suite, but I filed no finding that depends on it; my conclusion rests on static tracing of the diff against its p…; PR #8525 resolves DashScope Qwen 3.8 conflicts between r...: none — all planned checks completed within budget.`.
中文说明
未发现问题。LGTM!✅
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — I did not run the sdk-typescript vitest suite, but I filed no finding that depends on it; my conclusion rests on static tracing of the diff against its p…;PR #8525 resolves DashScope Qwen 3.8 conflicts between r...:none — all planned checks completed within budget.`。
— qwen3.8-max via Qwen Code /review (v0.21.10)
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
中文说明
已审查——无阻断问题。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
| * same-layer precedence as the request builder. Keeping this decision shared | ||
| * lets UI reporters describe the value that will actually reach the wire. | ||
| */ | ||
| export function selectDashScopeThinkingKnob( |
There was a problem hiding this comment.
[Suggestion] Add direct unit tests for selectDashScopeThinkingKnob
selectDashScopeThinkingKnob is exported and contains complex multi-layer resolution logic (4 nested helpers, 3-layer precedence with on-switch special case) but has no direct unit test. It is exercised indirectly through buildRequest() tests and getReasoningEffortOverride() tests, which are comprehensive, but a direct test suite would be more robust against future refactors.
| export function selectDashScopeThinkingKnob( | |
| Add a `describe('selectDashScopeThinkingKnob', ...)` block with direct test cases for each major resolution path: extra_body disable, extra_body budget, extra_body on-switch, samplingParams disable, samplingParams budget, same-layer effort/budget conflict, and all three on-switch sub-paths. |
中文说明
建议: 为 selectDashScopeThinkingKnob 添加直接单元测试。
selectDashScopeThinkingKnob 是导出的函数,包含复杂多层解析逻辑(4 个嵌套辅助函数,3 层优先级含 on-switch 特例),但没有直接单元测试。它通过 buildRequest() 和 getReasoningEffortOverride() 的测试间接覆盖,覆盖全面,但直接测试套件在后续重构时会更健壮。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
…enLM#8525) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
🤖 Addressed the latest review feedback (round 7/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 7/100 轮)。改动内容与我反驳保留之处如下: Autofix review-response summary — PR #8525Feedback points[Suggestion] Add direct unit tests for The function is exported and has two independent consumers: the DashScope No other actionable feedback this round (no failed checks; the deferred ChangesOne commit, test-only — no production code touched:
Conflict notesNone ( VerificationCommands actually run, in order:
Integration tests were not run: the change adds unit tests only and exercises 中文说明Autofix 审查响应总结 — PR #8525反馈条目[建议] 为 该函数是导出的,且有两个独立的使用方:DashScope provider 的请求构建器和 本轮无其他可执行反馈(无失败检查;延后的非 Critical 区域未列出任何条目)。 改动单个提交,纯测试——未触碰任何生产代码:
冲突说明无( 验证实际执行的命令(按顺序):
未运行集成测试:本次改动仅新增单元测试,不涉及仅存在于打包后 CLI 中的行为。 Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 281 passed · 0 failed · 281 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:281 通过 · 0 失败 · 281 总计 Verification reportPR 8525 — fix(core): resolve Qwen 3.8 reasoning budget conflicts (round 5)Verdict: Follow-up round at a moved head AND moved base again. Round 4 verified head 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与新 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。
Previous-finding status (round 4 → round 5)
Central claim and A/B proofCentral claim: for tiered Harness
Result: 179/179 arm assertions. Head flips exactly the 14 cells where base ships a wrong shape and perturbs none of the 21 parity cells; 32 tiered no-pair invariants, 35 preserve_thinking markers, 2 P10 strip checks, and 4 closure controls all green. Closure controls: base dist lacks Wire oracle ( Full-pipeline CLI E2E ( Live premise ( Reviewer Test Plan walk-through
Delta verification (commits since round 4)
FindingsNo blocking findings. Observations (informational, not blocking)
Mutation matrix (vacuity of the PR's own tests)Witness:
Targeted gatesWitness:
Not covered
MethodologyEnvironment: CI merge-ref checkout ( Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally; the touched integration file (cli/acp-integration.test.ts, formatting-only change) was manually run green (11/11), but the broader integration suite went unexercised.
Not reviewed: reverse audit — stopped at the 5-round hard cap without two consecutive dry rounds; convergence not established (rounds 3 and 5 fully dry; round 4's two findings resolved to one probe-rejected and one low-confidence terminal-only).
Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget., and 2 more.
中文说明
已审查。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally; the touched integration file (cli/acp-integration.test.ts, formatting-only change) was manually run green (11/11), but the broader integration suite went unexercised。
未审查:reverse audit — stopped at the 5-round hard cap without two consecutive dry rounds; convergence not established (rounds 3 and 5 fully dry; round 4's two findings resolved to one probe-rejected and one low-confidence terminal-only)。
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.,另有 2 条。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| /** Return the server-reported status for the initial effort request. */ | ||
| getInitialEffortStatus(): EffortStatus | undefined { |
There was a problem hiding this comment.
[Suggestion] R11-1: getInitialEffortStatus() is a synchronous getter populated only when the async initialize() completes, but neither its JSDoc nor the package README states that callers must await query.initialized first — and undefined already means two other things ("no effort requested", "legacy CLI"), so the unready state is silently ambiguous. The Python twin initial_effort_status (packages/sdk-python/src/qwen_code_sdk/query.py:525-526) has the same undocumented precondition. — Failure scenario: a consumer following the README's construct-then-iterate pattern checks q.getInitialEffortStatus()?.applied === false right after construction to surface a fallback for a shadowed effort tier; initialize() yields at its first await before the control round-trip starts, so the getter returns undefined — indistinguishable from "no override" — and the shadowed-effort branch never fires. Probe-verified: undefined synchronously after construction and while initialize is in flight; populated only after await query.initialized.
| /** Return the server-reported status for the initial effort request. */ | |
| getInitialEffortStatus(): EffortStatus | undefined { | |
| /** Return the server-reported status for the initial effort request. | |
| * Only meaningful after `initialized` resolves; returns `undefined` until | |
| * then, and also when no effort was requested or the CLI predates the field. */ | |
| getInitialEffortStatus(): EffortStatus | undefined { |
中文说明
R11-1: getInitialEffortStatus() 是一个同步 getter,其值只有在异步 initialize() 完成后才会填充,但它的 JSDoc 和包 README 都没有说明调用方必须先 await query.initialized——而 undefined 已经表示另外两种含义("未请求 effort"、"旧版 CLI"),因此"尚未就绪"状态被静默地混入其中。Python 对应物 initial_effort_status(packages/sdk-python/src/qwen_code_sdk/query.py:525-526)存在同样的未文档化前置条件。失败场景:使用者按 README 的"构造后直接迭代"模式,在构造后立即检查 q.getInitialEffortStatus()?.applied === false,以决定是否提示被遮蔽的 effort 档位;initialize() 在控制协议往返开始前的第一个 await 处就让出,getter 此时返回 undefined——与"无覆盖"无法区分——遮蔽提示分支永远不会触发。已用探针验证:构造后同步及 initialize 进行中均为 undefined,仅在 await query.initialized 后才有值。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| const { | ||
| sendRequest, | ||
| cleanup, | ||
| stderr, | ||
| sessionUpdates, | ||
| permissionRequests, | ||
| agent, | ||
| } = setupAcpTest(rig, { |
There was a problem hiding this comment.
[Suggestion] R11-2: This PR edits an integration test that no PR-gating CI job ever collects — the required PR gate test:integration:no-ak:sandbox:none runs a fixed 12-file list that excludes this file; ci.yml's integration_cli job is merge_group-only (it never triggers on pull_request while the merge queue is disabled); e2e.yml collects it but runs only post-merge / nightly / on workflow_dispatch. — Failure scenario: a regression in ACP set_config_option reasoning-effort handling — the surface this PR's behavior change rides on — or a broken edit to this test merges with zero PR-time signal and surfaces only at the post-merge e2e push run or the nightly regression. Nothing is currently broken: this file ran green in this review (11/11, bundled CLI + fake OpenAI server) — this is a gate-coverage gap, not a defect.
Suggested fix: add ./cli/acp-integration.test.ts to the test:integration:no-ak:sandbox:none file list (it uses only the fake OpenAI server, so it fits the no-AK gate), or explicitly accept post-merge-only coverage for it.
中文说明
R11-2: 本 PR 修改了一个不被任何 PR 门禁 CI 任务收集的集成测试——必需的 PR 门禁 test:integration:no-ak:sandbox:none 运行一个固定的 12 文件列表,其中不包含本文件;ci.yml 的 integration_cli 任务仅属于 merge_group(在 merge queue 未启用时不会在 pull_request 上触发);e2e.yml 会收集它,但只在合并后 push / 每日定时 / workflow_dispatch 时运行。失败场景:ACP set_config_option reasoning-effort 处理(本 PR 行为变更所依赖的表面)出现回归,或对本测试的破坏性修改,将在 PR 阶段毫无信号地合并,直到合并后的 e2e push 运行或每日回归才暴露。当前没有破坏:本审查中该文件运行全绿(11/11,打包 CLI + fake OpenAI server)——这是门禁覆盖缺口,不是缺陷。
建议修复:将 ./cli/acp-integration.test.ts 加入 test:integration:no-ak:sandbox:none 的文件列表(它只使用 fake OpenAI server,符合 no-AK 门禁),或明确接受对它仅做合并后覆盖。
— qwen3.8-max via Qwen Code /review (v0.21.10)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.21.11. |










What this PR does
This change prevents DashScope Qwen 3.8 requests from carrying both
reasoning_effortandthinking_budgetwhen those settings come from different configuration layers. It applies the existingextra_body> request sampling parameters >reasoningprecedence, keeps unrelated thinking settings intact, and documents how conflicts and explicit same-layer pairs are handled.Why it's needed
Qwen 3.8 effort support introduced a flat
reasoning_effortfield, but existing configurations may already setthinking_budget. DashScope rejects requests that contain both fields, so selecting an effort could turn a previously valid configuration into an invalid API request. This follow-up to #8472 preserves the higher-priority user setting instead of emitting the conflicting pair.Reviewer Test Plan
How to verify
Configure a DashScope Qwen 3.8 model with a nested reasoning effort and a higher-priority
thinking_budgetin eithersamplingParamsorextra_body. Confirm that the resulting request retains the budget, omits both flat and nested effort fields, and preservesenable_thinkingwhen configured. Then placereasoning_effortinextra_bodywith a lower-priority request-level budget and confirm that the request retains only the effort. Existing Qwen 3.8 effort-only requests should continue to pass all five effort tiers through unchanged.Evidence (Before & After)
N/A — request-construction behavior with no UI change. Before this fix the conflicting configurations emitted both fields; after this fix the higher-priority field is the only one emitted.
Tested on
Environment (optional)
Local Node.js workspace with the focused DashScope provider and OpenAI pipeline Vitest suites, package build, typecheck, ESLint, Prettier, and diff checks.
Risk & Scope
Linked Issues
Follow-up to #8472.
中文说明
这个 PR 做了什么
当 DashScope Qwen 3.8 的
reasoning_effort和thinking_budget来自不同配置层时,这个改动会防止请求同时携带两个字段。它按现有的extra_body> 请求采样参数 >reasoning优先级处理冲突,保留无关的思考设置,并记录冲突和同层显式配置的处理方式。为什么需要这个改动
Qwen 3.8 effort 支持引入了顶层
reasoning_effort字段,但现有配置可能已经设置thinking_budget。DashScope 会拒绝同时包含两个字段的请求,因此选择 effort 可能使之前有效的配置变成无效 API 请求。作为 #8472 的后续修复,这个改动会保留优先级更高的用户设置,而不是发送冲突字段对。Reviewer 测试计划
如何验证
为 DashScope Qwen 3.8 模型配置嵌套 reasoning effort,并在
samplingParams或extra_body中设置优先级更高的thinking_budget。确认最终请求保留 budget,删除顶层和嵌套 effort 字段,并在已配置时保留enable_thinking。然后在extra_body中设置reasoning_effort,同时在优先级更低的请求层设置 budget,确认请求只保留 effort。现有只配置 effort 的 Qwen 3.8 请求应继续原样传递全部五个 effort 档位。证据(修复前与修复后)
不适用——这是没有 UI 变化的请求构建行为。修复前,冲突配置会同时发送两个字段;修复后,只会发送优先级更高的字段。
测试平台
环境(可选)
本地 Node.js 工作区;已运行 DashScope provider 和 OpenAI pipeline 的定向 Vitest 测试、package build、typecheck、ESLint、Prettier 和 diff 检查。
风险与范围
关联 Issue
#8472 的后续修复。