feat(core): add opt-in built-in web_search backed by the DashScope Responses API - #7215
Conversation
E2E test reportRun against live DashScope with a standard Bailian key on macOS (bundled build,
|
|
Thanks for the PR! Template looks good ✓ Problem: This is a feature addition, not a bug fix — but the motivation is grounded in observed evidence, not theory. The PR documents a concrete quality failure with the currently recommended Bailian WebSearch MCP (0/5 relevant results on a ground-truthed query across replays), and the DashScope Responses API path answered the same query correctly in both test runs. The revival requests (#3841, #4801) were closed with "use MCP" guidance that testing showed was inadequate. This is a real, demonstrated gap. Direction: Web search is a must-have for a coding agent — Claude Code ships it as a built-in (CHANGELOG confirms: session-wide search limits, sub-agent search fixes, Vertex web search support, date-aware queries). Bundling a high-quality search path with the Qwen model stack instead of pointing users at an underperforming external MCP is well-aligned with the product's direction. The author is a maintainer and explicitly frames this as a maintainer decision superseding the earlier MCP-only guidance. The opt-in design (two explicit settings, off by default, bills the user's own key) addresses the original removal concerns from #3502. Size: Core paths are heavily touched. Production logic: ~1199 lines (additions + deletions, excluding 976 test lines, 20 schema lines, 91 docs lines). The bulk is the new Approach: The scope feels right for what it delivers. Each piece serves the stated goal: the gate/registration logic (opt-in with diagnostic notices), the tool itself (streaming, retry, truncation, safety framing), permission integration, env-only configuration for locked-down environments, and the docs update. The Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 这是一个功能新增,而非 bug 修复——但动机基于实际观测证据,而非理论推测。PR 记录了当前推荐的百炼 WebSearch MCP 的具体质量问题(在一个有标准答案的查询上,多次重放均返回 0/5 相关结果),而 DashScope Responses API 路径在两次测试中都正确回答了同一查询。恢复请求(#3841、#4801)以"请使用 MCP"口径关闭,但测试表明该口径不够充分。这是一个真实的、已验证的差距。 方向: 联网搜索对编码代理来说是必备能力——Claude Code 已将其作为内置功能(CHANGELOG 确认:会话级搜索限制、子代理搜索修复、Vertex 搜索支持、日期感知查询)。将高质量搜索路径与 Qwen 模型体系捆绑提供,而非引导用户使用表现不佳的外部 MCP,与产品方向高度一致。作者是维护者,并明确将此定位为取代此前"仅 MCP"口径的维护者决定。显式开启设计(两项显式设置、默认关闭、使用用户自己的 key 计费)回应了 #3502 中原始移除的顾虑。 规模: 核心路径被大量触及。生产逻辑约 1199 行(增删合计,不含 976 行测试、20 行 schema、91 行文档)。主体是新的 方案: 范围与交付内容匹配。每个部分都服务于既定目标:门控/注册逻辑(显式开启 + 诊断提示)、工具本体(流式、重试、截断、安全框架)、权限集成、面向受限环境的纯环境变量配置,以及文档更新。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewIndependent proposal vs. actual approach: I'd have done this the same way — a new Findings — no critical blockers. The implementation is well-structured and follows project conventions:
One minor observation (non-blocking): the Real-Scenario TestingNo DashScope API key available in this environment, so live search could not be tested. The enablement matrix was tested end-to-end via tmux with the bundled build ( Test 1: Default config (no web search settings)✅ No Test 2: Enabled but no model✅ Correct diagnostic notice, tool stays off. Test 3: Enabled + model + base URL but no API key✅ Correct notice naming the exact failed condition. Test 4: Valid env-only config (dummy key)✅ Tool registers with valid config. (Search would fail at invocation with 401, which the error handling covers per unit tests.) Test 5: Non-DashScope endpoint✅ Non-DashScope endpoint correctly rejected with a clear notice. Unit tests( 中文说明代码审查独立方案 vs. 实际方案: 我会用同样的方式实现——在 发现——无关键阻断项。 实现结构良好,遵循项目规范:
一个次要观察(非阻断): 真实场景测试本环境无 DashScope API key,无法测试实际搜索。启用矩阵通过 tmux 使用打包构建进行了端到端测试:
— Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 4/5 — solid, well-tested feature with a clean enablement matrix; only non-blocking nits (minor unrelated churn in This is a well-executed feature addition. The motivation is grounded in observed evidence — the recommended MCP returned 0/5 relevant results on a ground-truthed query, while the DashScope Responses API path answered it correctly. The opt-in design (two explicit settings, off by default, bills the user's own key) directly addresses the concerns that led to the original removal in #3502. The implementation follows project conventions cleanly: it reuses existing utilities ( The enablement matrix works exactly as promised in testing: default → silent, misconfigured → diagnostic notice naming the exact failed condition, valid config → tool registers. The "no unaudited narration" invariant (partial results require at least one executed search) is a thoughtful safety property. If I had to maintain this in six months, I'd thank the author — the comments explain the why (retry rationale, truncation strategy, security invariants), the tests are comprehensive, and the code doesn't over-abstract. 中文说明置信度:4/5 — 扎实、测试充分的功能,启用矩阵干净;仅有非阻断的小问题( 这是一个执行良好的功能新增。动机基于实际观测证据——推荐的 MCP 在一个有标准答案的查询上返回 0/5 相关结果,而 DashScope Responses API 路径正确回答了该查询。显式开启设计(两项显式设置、默认关闭、使用用户自己的 key 计费)直接回应了导致 #3502 原始移除的顾虑。 实现干净地遵循项目规范:复用现有工具函数、匹配 启用矩阵在测试中完全按预期工作:默认 → 静默,配置错误 → 指明具体失败条件的诊断提示,有效配置 → 工具注册。"无未审计叙述"不变量(部分结果需要至少一次已执行的搜索)是一个深思熟虑的安全属性。 — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Resolves the ACP bootstrap conflict (keep #7145 profiling wrapper + warning emission), drops the session.ts warnings block superseded by #7174, and guards the ACP getWarnings call against stubbed configs. Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb
…nresolved agent allow-lists Review follow-ups on #7215: the endpoint gate now rejects plaintext endpoints (the side request carries a bearer key), and an agent allow-list whose names resolve to no registered tool keeps its dead entries instead of widening to the inherited toolset — an agent restricted to the unavailable WebSearch now runs tool-less rather than gaining shell/write. Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb
…dirs The CLI unit-test suites write debug logs relative to the package dir (custom/, first/, from-env/, workspace/); a merge-commit git add swept them in. Remove them and ignore the directories until the tests are pointed at temp dirs. Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Qwen Code review did not complete successfully. Qwen review aborted with an API error before posting comments. See workflow logs. |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: This PR adds an opt-in built-in web_search tool backed by... — pointed at diff lines it never opened: it made tool calls, but none of them read the diff.
— qwen3.8-max-preview via Qwen Code /review
|
@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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
@qwen-code /takeover stop |
|
👋 Takeover released: the autofix loop will no longer engage this PR (an in-flight round, if any, completes its bounded work). Re-apply 中文说明👋 已释放:autofix 循环不再介入此 PR(在飞的一轮如有,将完成其有界工作)。重新打上 |
…-error partials - Override maxOutputChars (result limit + envelope headroom) so the scheduler's global 25k threshold no longer slices results before the tool's section-aware truncation can protect URL evidence sections. - Route in-stream backend errors through the shared terminal-failure tail so results streamed (and billed) before the error surface as a partial result, matching the transport-error path. - Strengthen gate tests: assert gate.ok before webExtractor, exercise the https-only endpoint guard, and make the config mock disambiguate same-id entries by baseUrl like the real Config.
Apply the function's set-but-empty-is-unset rule to the API key env var like every sibling env read, and add loadCliConfig coverage for the web search settings resolution (env precedence, empty-env fallthrough, base-URL key selection).
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. 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.
— qwen3.8-max-preview via Qwen Code /review
|
@qwen-code /review |
1 similar comment
|
@qwen-code /review |
ReviewOverviewReintroduces a built-in Overall this is well-engineered and unusually well-tested. The failure-mode handling (salvaging billed partial results only when a search actually executed, the probe-verified typeless HTTP-200 error event, surrogate-pair-safe truncation) shows real care. I verified the integration points against main — Strengths
Findings (all minor)
Risk notes (acknowledged in the PR, agreeing with the assessment)
None of the findings block merging. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: chunk 1, chunk 3, chunk 6, chunk 5, chunk 2, chunk 8, chunk 7, chunk 9, chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: Agent 0: Issue fidelity & root-cause ownership, Test coverage matrix (whole-diff), Agent 1b: Removed-behavior audit, Agent 1c: Cross-file tracer, Agent 7: Build & test verification — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — no auditor was launched with a prompt this skill builds — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries. Not reviewed: verification — a verifier ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and the posted findings cannot be counted as verified against it. [Critical] packages/core/src/tools/web-search.ts:727-732 — response.cancelled missing from terminal-event switch (existing blocker still stands; the terminal-event switch handles completed, failed, and incomplete but omits cancelled, making the status === 'cancelled' handler at line 818 dead code)
— qwen3.7-max via Qwen Code /review
| override get schema(): FunctionDeclaration { | ||
| return { | ||
| name: this.name, | ||
| description: getWebSearchToolDescription(), | ||
| parametersJsonSchema: this.parameterSchema, | ||
| }; | ||
| } |
There was a problem hiding this comment.
[Suggestion] The schema getter override is the only mechanism that keeps the tool description's embedded month/year current across a month boundary in a long-running process; no test accesses .schema or verifies its description content. — Failure scenario: a long-running qwen serve or ACP bridge process started in January still reports "The current month is January 2026" in July. The model uses the wrong year in search queries, returning outdated results. The regression is silent because nothing asserts on the schema's description after construction.
Consider adding a test that constructs a WebSearchTool, accesses tool.schema, and asserts schema.description contains the current month/year (using vi.setSystemTime to pin the date).
— qwen3.7-max via Qwen Code /review
|
@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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
… switch and trim gate env keys - Add response.cancelled to the terminal-event switch so the status === 'cancelled' handler is reachable instead of dead code - Trim API key env vars in the gate (all three check sites), matching the CLI-side whitespace rule from 302cf3b - Add tests: cancelled with/without prior search, whitespace-only env key rejection, schema getter month/year embedding
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: Review feedback addressedImplemented
Declined (with reasons)
Conflict notesNo conflicts ( Verification
中文说明已处理的评审反馈已实施
已拒绝(附理由)
冲突说明无冲突( 验证结果
Base-conflict check: no conflict with main. Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. 🧠 Handled by Qwen Code · model/模型 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: No action taken — there is no review feedback to address. The feedback file contains no reviews, no inline comments, and no issue-level The PR branch ( 中文说明未采取任何操作——没有需要处理的审查反馈。 反馈文件中没有审查意见、没有行内评论、也没有 Issue 级别的评论。所有列出的 CI 检查( PR 分支( Base-conflict check: no conflict with main. 🧠 Handled by Qwen Code · model/模型 |
Code Review —
|
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: Review feedback addressedCommit: Feedback points and decisions
Changes made
Verification
ConflictNo conflict ( 中文说明已处理的评审反馈提交: 反馈要点及决定
所做变更
验证结果
冲突无冲突( Base-conflict check: no conflict with main. Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. 🧠 Handled by Qwen Code · model/模型 |
Code review — opt-in built-in
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: Review feedback addressedInline comments
Issue-level observations (@wenshao)
Verification
中文说明已处理的评审反馈行内评论
Issue 级别观察(@wenshao)
验证
Base-conflict check: no conflict with main. Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. 🧠 Handled by Qwen Code · model/模型 |
|
Qwen Code review timed out. Qwen review timed out after 300 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
|
Released in v0.20.1. |
|
|
What this PR does
web_searchtool, backed by DashScope's server-side search via the Responses API. It works with the standard Bailian API key users already configure for their model — no MCP server hunting, no extra provider, no extra key.tools.webSearch.enabled: trueand a search model (tools.webSearch.model, resolved againstmodelProvidersthe same wayfastModelis —"modelId"or"authType:modelId"). The main model can be any provider; only the search side request needs a DashScope-compatible entry. It never activates implicitly.ENABLE_WEB_SEARCH,WEB_SEARCH_MODEL,WEB_SEARCH_BASE_URL, andWEB_SEARCH_API_KEY(falling back toDASHSCOPE_API_KEY).tools.webSearch.webExtractor, billed by DashScope). Results distinguish pages actually read from unopened candidates, and the model is required to end its answer with a Sources section of markdown links.Why it's needed
Reviewer Test Plan
How to verify
settings.json(any existing DashScope entry works;envKeynames the variable holding your Bailian key):{ "modelProviders": { "openai": [ { "id": "qwen3.6-plus", "envKey": "DASHSCOPE_API_KEY", "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1" } ] }, "tools": { "webSearch": { "enabled": true, "model": "qwen3.6-plus" } } }What did CMS change about FQHC payment rates this year? Search the web.— expect a confirmation dialog showing the query with the standard options (allow once, always allow, no), then an answer ending in aSources:section with markdown links. Progress showsSearching: …→Did N searches in Xs.qwen -p "..." --approval-mode yolo— expect the same cited answer;web_searchappears in the init tools list.model(or unset the key variable) and start — expect a startup notice naming the failed condition, and noweb_searchin the tool list. With everything unset (the default), expect no notice and no tool: nothing changes for existing users.export ENABLE_WEB_SEARCH=true WEB_SEARCH_MODEL=qwen3.6-plus WEB_SEARCH_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1withDASHSCOPE_API_KEYset — expect the tool to register and search.Evidence (Before & After)
Before: no built-in web search — the docs pointed to the Bailian WebSearch MCP, which returned irrelevant results on hard queries. After: E2E sweep summary (full sweeps 2026-07-18 plus a targeted 17/17 re-verification on 2026-07-19 against the final build, covering the enablement matrix, a new stream-json notice check, errors, permissions, and core search; macOS, real DashScope searches; full report posted as a PR comment):
WEB_SEARCH_BASE_URLthere is no silent default endpointTested on
Environment (optional)
Bundled build (
npm run build && npm run bundle,node dist/cli.js) against live DashScope with a standard Bailian key; interactive groups via tmux.Risk & Scope
dashscope-us) is now recognized as DashScope-compatible by main provider detection as well.Linked Issues
References #3502 (original removal), #3841 and #4801 (revival requests closed with MCP guidance — this PR supersedes that guidance per maintainer decision; see the policy comment on those threads).
中文说明
本 PR 的内容
web_search工具,基于 DashScope Responses API 的服务端搜索。使用用户已配置的标准百炼 API key 即可 — 无需寻找 MCP 服务、无需额外供应商或额外 key。tools.webSearch.enabled: true和搜索模型(tools.webSearch.model,与fastModel相同的方式在modelProviders中解析 —"modelId"或"authType:modelId")。主模型可以是任意供应商;只有搜索侧请求需要一个 DashScope 兼容的条目。绝不会隐式激活。ENABLE_WEB_SEARCH、WEB_SEARCH_MODEL、WEB_SEARCH_BASE_URL、WEB_SEARCH_API_KEY(回退到DASHSCOPE_API_KEY)。tools.webSearch.webExtractor,由 DashScope 计费)。结果区分实际读取过的页面与未打开的候选链接,并要求模型在回答末尾附上 Sources 引用小节。为什么需要
审阅者测试方案
如何验证
settings.json中添加上文所示配置(任何现有 DashScope 条目均可;envKey指定存放百炼 key 的环境变量名)。What did CMS change about FQHC payment rates this year? Search the web.— 应出现显示查询内容的确认对话框(标准选项:允许一次、始终允许、拒绝),随后回答以Sources:小节结尾。进度显示Searching: …→Did N searches in Xs。qwen -p "..." --approval-mode yolo同样得到带引用的回答;init 工具列表中包含web_search。model(或不设 key 变量)后启动 — 应出现说明具体失败条件的启动提示,且工具列表中无web_search。完全不配置(默认状态)— 无提示、无工具:现有用户无任何变化。export ENABLE_WEB_SEARCH=true WEB_SEARCH_MODEL=qwen3.6-plus WEB_SEARCH_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1,并已设置DASHSCOPE_API_KEY— 工具应注册并可搜索。证据(前后对比)
之前:无内置搜索 — 文档指向百炼 WebSearch MCP,其在困难查询上返回无关结果。之后:E2E 测试摘要(全量测试 2026-07-18,另于 2026-07-19 针对最终构建做了 17/17 的定向复验;macOS,真实 DashScope 搜索;完整报告将作为 PR 评论发布),详见上方英文表格 — 6 组共 20 项测试全部通过。
测试平台
macOS ✅;Windows / Linux 依赖 CI⚠️ 。
环境
打包构建(
npm run build && npm run bundle,node dist/cli.js),使用标准百炼 key 访问线上 DashScope;交互式分组通过 tmux 执行。风险与范围
dashscope-us)现在也会被主供应商检测识别为 DashScope 兼容端点。关联 Issue
参考 #3502(原始移除)、#3841 与 #4801(以 MCP 口径关闭的恢复请求 — 本 PR 依维护者决定取代该口径;见相关线程中的政策评论)。