Skip to content

fix(core): drop top-level oneOf from send_message tool schema - #7989

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
netbrah:fix/send-message-oneof-anthropic-schema
Jul 29, 2026
Merged

fix(core): drop top-level oneOf from send_message tool schema#7989
wenshao merged 1 commit into
QwenLM:mainfrom
netbrah:fix/send-message-oneof-anthropic-schema

Conversation

@netbrah

@netbrah netbrah commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Removes the top-level oneOf combinator from send_message's tool
input_schema. Anthropic's Messages API rejects any tool schema
carrying a top-level oneOf/allOf/anyOf with a hard 400, so this
made send_message completely unusable on every Anthropic-backed
model (native API, Vertex, or an Anthropic-compatible proxy).

Why it is needed

Fixes #7984.

send_message's schema used oneOf: [{ required: ['to'] }, { required: ['task_id'] }] to express "either a teammate recipient or a
background-task id is required." This schema is forwarded verbatim as
tools[].input_schema to Anthropic in
packages/core/src/core/anthropicContentGenerator/converter.ts
(convertSchemainput_schema), and Anthropic's tool-schema
validator does not accept oneOf at the schema root — every call
400s before the model even sees the tool.

The oneOf constraint was redundant, not the sole enforcement:
send_message's execute() already returns a clear runtime error
("No active team and no task_id provided...") when neither to nor
task_id is supplied. Removing it from the JSON Schema leaves that
runtime check intact and unchanged for OpenAI/Gemini-backed models,
while making the tool actually callable on Anthropic-backed models.

Verification

Reproduced against the live Anthropic Messages API with
claude-sonnet-5, isolating the exact schema shape:

Request with the oneOf constraint (current main):

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "tools.0.custom.input_schema: input_schema does not support oneOf, allOf, or anyOf at the top level"
  }
}

Identical request with oneOf removed (this PR): 200, tool_use
returned successfully.

packages/core/src/tools/send-message.test.ts (20 tests) passes
unchanged — no test asserted on the oneOf field, and the tool's
runtime "missing recipient" behavior is untouched.

Also ran, clean:

  • npx tsc --noEmit -p packages/core/tsconfig.json (0 errors)
  • npx eslint packages/core/src/tools/send-message.ts (0 warnings)
  • npx prettier --check packages/core/src/tools/send-message.ts
  • repo's own pre-commit lint-staged hook (prettier + eslint --fix
    --max-warnings 0) on the commit

Demo

N/A — internal tool schema fix, no user-facing UI. Behavioral
before/after is the curl reproduction above (400 → 200) rather than a
screenshot, since this is a wire-level API compatibility fix.

Anthropic's Messages API rejects any tool `input_schema` carrying a
top-level `oneOf`/`allOf`/`anyOf` combinator with a hard 400:

  input_schema does not support oneOf, allOf, or anyOf at the top level

send_message's schema used `oneOf: [{ required: ['to'] }, { required:
['task_id'] }]` to express "either a teammate recipient or a
background-task id is required." Since this schema is forwarded
verbatim as `tools[].input_schema` to Anthropic
(anthropicContentGenerator/converter.ts -> convertSchema), every call
to send_message on an Anthropic-backed model (native API, Vertex, or
an Anthropic-compatible proxy) failed outright -- not degraded, fully
unusable.

Verified live against the Anthropic Messages API (claude-sonnet-5):
the schema with `oneOf` 400s with the exact message above; the
identical request with `oneOf` removed succeeds (200, tool_use
returned).

The `oneOf` constraint was redundant defense-in-depth, not the only
enforcement: send_message's execute() already returns a clear runtime
error ("No active team and no task_id provided...") when neither `to`
nor `task_id` is supplied. Dropping the schema-level constraint leaves
that runtime validation intact and unchanged for OpenAI/Gemini-backed
models, while making the tool actually callable on Anthropic-backed
models.

Fixes QwenLM#7984
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Jul 29, 2026
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

@yiliang114

Copy link
Copy Markdown
Collaborator

⚠️ Failed to process this request. Please re-mention the bot to retry.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — and for the clean reproduction in #7984.

Template: the body uses its own headings ("Verification", "Demo") rather than the template's "Reviewer Test Plan / How to verify / Evidence / Tested on / Risk & Scope" structure, and there's no Chinese <details> translation. The substance the template exists to guarantee is all here though (clear what/why plus a live before/after), so this isn't blocking — just worth matching next time.

Problem: real and observed, not theoretical. #7984 includes a live curl against the Anthropic Messages API showing the hard 400 — input_schema does not support oneOf, allOf, or anyOf at the top level — so send_message is completely unusable on every Anthropic-backed model. I confirmed the root cause in the code: in auto compliance mode convertSchema returns the schema unchanged, so the top-level oneOf is forwarded verbatim into tools[].input_schema.

Direction: aligned. A tool that 400s before the model ever sees it is a clear bug, and Anthropic's rejection of top-level combinators is a well-known, documented constraint.

Size: touches a core path (packages/core/src/tools/send-message.ts) but it's 6 production lines, all deletions — well under any threshold. No maintainer escalation needed on size.

Approach: this is the minimal fix. The oneOf was redundant — execute() already returns clear runtime errors when neither to nor task_id is supplied ("No active team and no task_id provided…" / "Recipient "to" is required."), so removing the schema constraint loses no correctness, only a model hint that was actively backfiring on Anthropic. The alternative — stripping top-level combinators in the Anthropic converter — would be speculative hardening for a constraint no tool actually needs, and would touch shared infra. Scoping the fix to the source schema is the right call. Note the change is correctly targeted: the oneOf on agent.ts's fork_turns is property-level, which Anthropic accepts, so it's rightly left alone.

Risk: no elevated risk signals — send-message.ts doesn't match any of the high-revert paths.

Moving on to code review. 🔍

中文说明

感谢贡献,也感谢 #7984 里清晰的复现。

模板: PR 正文用了自己的小标题("Verification"、"Demo"),而非模板的 "Reviewer Test Plan / How to verify / Evidence / Tested on / Risk & Scope" 结构,也没有中文 <details> 翻译。不过模板想保证的实质内容都在(清晰的 what/why 加上 live before/after),所以不阻塞——下次对齐模板即可。

问题: 真实且已观测,不是理论问题。#7984 包含对 Anthropic Messages API 的 live curl,返回硬 400——input_schema does not support oneOf, allOf, or anyOf at the top level——因此 send_message 在所有 Anthropic 后端模型上完全不可用。我在代码里确认了根因:auto 合规模式下 convertSchema 原样返回 schema,所以顶层 oneOf 被原封不动地传入 tools[].input_schema

方向: 对齐。一个在模型看到之前就 400 的工具是明确的 bug,Anthropic 拒绝顶层组合关键字是已知的、有文档的约束。

规模: 触及核心路径(packages/core/src/tools/send-message.ts),但只有 6 行生产代码,且全是删除——远低于任何阈值。无需因规模升级维护者关注。

方案: 这是最小修复。该 oneOf 是冗余的——当 totask_id 都未提供时,execute() 已经返回清晰的运行时错误("No active team and no task_id provided…" / "Recipient "to" is required."),所以移除 schema 约束不会损失任何正确性,只去掉了一个在 Anthropic 上反而帮倒忙的模型提示。另一种方案——在 Anthropic converter 里剥离顶层组合关键字——属于为没有工具真正需要的约束做投机性加固,而且会动到共享基础设施。把修复限定在源 schema 是正确选择。注意改动定位精准:agent.tsfork_turns 上的 oneOf 是属性级的,Anthropic 接受,所以正确地未改动。

风险: 无升级风险信号——send-message.ts 不匹配任何高回滚路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

Reading just the title and motivation, my own proposal for "top-level oneOf makes Anthropic 400" would be: drop the redundant constraint at the source, since execute() already enforces recipient presence at runtime. That's exactly what this PR does, so there's no simpler path it missed.

The diff is the whole change — six deleted lines (the constraint plus its explanatory comment), nothing else touched. No correctness, security, or regression concerns:

  • Runtime enforcement is intact. Route 2 still returns "No active team and no task_id provided…" and "Recipient "to" is required." when neither field is supplied, so OpenAI/Gemini-backed models keep the same behavior; the only thing removed is a schema hint Anthropic can't parse.
  • The worst case the old comment worried about — the model sending {message} alone — now costs one recoverable error round-trip instead of a guaranteed 400 on Anthropic. That's the right tradeoff.
  • No test asserted on the oneOf field (confirmed by grep), and the runtime "missing recipient" tests are unaffected.
  • Correctly scoped: the property-level oneOf on agent.ts's fork_turns is left alone, since Anthropic only rejects top-level combinators.

Nothing to flag.

Test evidence (PR's own CI)

CI on the reviewed commit is still settling — the Linux unit suite is mid-run, so I'm reporting what's there now rather than polling. The finalize job will refresh the table below once CI completes.

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

Check Conclusion
Classify PR ✅ 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 unit suite passing wouldn't by itself prove the fix — no test asserts on the wire schema, so the suite is green with or without the oneOf. The load-bearing evidence is the author's live curl reproduction in #7984 (400 with the constraint, 200 without). That's the author's claim, not independently re-run here.

The sandboxed verification lanes (@qwen-code /verify, @qwen-code /tmux) aren't available for this PR — they execute the author's code, which requires write access that this fork contributor doesn't have. To independently settle the wire-format claim, a maintainer can reproduce it by hand in a disposable container: POST the tool schema with and without the top-level oneOf to the Anthropic Messages API and confirm 400 → 200.

中文说明

代码审查

只看标题和动机,我对"顶层 oneOf 导致 Anthropic 400"的方案就是:在源头删掉这个冗余约束,因为 execute() 已经在运行时强制要求收件人。这正是本 PR 所做的,所以没有更简单的路径被遗漏。

diff 就是全部改动——删除六行(约束及其解释注释),没有动其他地方。无正确性、安全或回归隐患:

  • 运行时强制保持不变。当两个字段都未提供时,Route 2 仍返回 "No active team and no task_id provided…" 和 "Recipient "to" is required.",所以 OpenAI/Gemini 后端模型行为不变;唯一移除的是 Anthropic 无法解析的 schema 提示。
  • 旧注释担心的最坏情况——模型只发 {message}——现在代价是一次可恢复的错误往返,而不是 Anthropic 上必然的 400。这是正确的取舍。
  • 没有测试断言 oneOf 字段(grep 已确认),运行时"缺少收件人"的测试不受影响。
  • 定位精准:agent.tsfork_turns 的属性级 oneOf 未改动,因为 Anthropic 只拒绝顶层组合关键字。

无可指摘。

测试证据(PR 自身 CI)

被审 commit 上的 CI 仍在进行——Linux 单元测试套件正在运行,所以我报告当前状态而非轮询。CI 完成后 finalize 任务会刷新下表。

单元测试通过本身并不能证明修复——没有测试断言 wire schema,所以无论有没有 oneOf 套件都是绿的。承载性证据是作者在 #7984 里的 live curl 复现(带约束 400,去掉后 200)。这是作者的声明,此处未独立重跑。

沙盒验证通道(@qwen-code /verify@qwen-code /tmux)对本 PR 不可用——它们会执行作者代码,需要写权限,而该 fork 贡献者没有。要独立验证 wire-format 声明,维护者可在一次性容器里手动复现:把带/不带顶层 oneOf 的工具 schema POST 到 Anthropic Messages API,确认 400 → 200。

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — a textbook minimal fix: real, reproduced bug; six deleted lines; runtime enforcement untouched; nothing else in the diff.

Stepping back, this is exactly the kind of PR you want to see. The problem is unambiguous — send_message 400s on every Anthropic-backed model before the model even sees it, with a live curl reproduction to prove it. The fix removes a constraint that was redundant (runtime already enforces recipient presence) and actively harmful on one provider, and it resists the temptation to over-engineer: no converter-level stripping, no new abstraction, no drive-by edits. The scoping is precise — it touches only the top-level oneOf that Anthropic rejects, leaving the property-level one on agent.ts alone. In six months I'd thank the author, not curse them.

The only thing I can't attest to yet is CI — the Linux unit suite is still running on the reviewed commit. The verdict is approve, but I'm not posting the approval while CI is in flight; that would attest to a result that doesn't exist yet. Approval is deferred until CI lands green on b014df42aaaef214ca997495df27a46c5fa5acf5; the finalize job will post it (commit-pinned) once every check completes, and withhold it if anything lands red or the head moves.

中文说明

置信度:5/5 —— 教科书式的最小修复:真实且有复现的 bug;删除六行;运行时强制未动;diff 里没有别的东西。

退一步看,这正是你想看到的 PR。问题明确——send_message 在每个 Anthropic 后端模型上、模型看到之前就 400,并有 live curl 复现为证。修复移除了一个既冗余(运行时已强制要求收件人)又在某个 provider 上有害的约束,并且克制住了过度设计的冲动:没有 converter 层剥离,没有新抽象,没有顺手改动。定位精准——只动 Anthropic 拒绝的顶层 oneOfagent.ts 上的属性级 oneOf 原样保留。六个月后我会感谢作者,而不是骂他。

我唯一还不能背书的是 CI——被审 commit 上的 Linux 单元测试套件仍在运行。结论是 approve,但我不会在 CI 进行时提交批准;那等于为一个尚不存在的结果背书。批准推迟到 CI 在 b014df42aaaef214ca997495df27a46c5fa5acf5 上全绿之后;finalize 任务会在每个 check 完成后(按 commit 绑定)提交批准,若有任何 check 变红或 head 移动则保留不批。

Qwen Code · qwen3.8-max-preview

Reviewed at b014df42aaaef214ca997495df27a46c5fa5acf5 · 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 — CI landed green after the review. ✅

@wenshao

wenshao commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — built and tested locally at b014df4

The triage bot correctly flagged that the load-bearing evidence here (the wire-format 400) was the author's claim, not independently re-run. I reproduced the whole chain locally instead. Verdict: merge-ready. The fix is correct, the redundancy argument holds, and the bug is actually worse than the PR describes.

Setup — isolated worktree at the PR head, real Config + real tool registry + real AnthropicContentGenerator (real Anthropic SDK client, real converter) pointed at a local endpoint that captures the exact JSON body and applies Anthropic's documented tool-schema rule. Single variable between arms: the six removed lines, applied by overlaying origin/main's send-message.ts into the same tree.

What is measured vs. cited: everything below is measured locally except Anthropic's server-side rejection itself — I have no Anthropic credentials in this environment, so my endpoint replays the documented error from #7984's live curl rather than re-proving it. What I did measure is the part that was never checked: that the raw oneOf reaches tools[].input_schema verbatim, and what that costs.


1. Premise confirmed at the wire — and the blast radius is bigger than stated

wire A/B

The captured request body on the main arm carries oneOf at the root of send_message.input_schema, untouched — confirmed independent of provider config: convertSchema preserves a top-level oneOf in both auto and openapi_30 compliance modes, so every Anthropic-backed configuration is affected.

The PR says "send_message is unusable." The lifecycle table says something stronger:

  • send_message is shouldDefer: true, so a fresh session is fine — it isn't in tools[] (13 tools).
  • The moment ToolSearch reveals it (or visibleTools lists it), it enters tools[] (14 tools) and the request 400s.
  • The reveal is sticky for the session, so turn 3 — an ordinary message that has nothing to do with send_message — also 400s. A single bad tool schema fails the whole request, taking the other 13 tools down with it.
  • Recovery is /clear (which calls clearRevealedDeferredTools()), not "avoid that tool."

So on Anthropic-backed models this is a session-bricking bug, not just an unusable tool. That raises the merge priority; it doesn't change the fix.

2. The "redundant constraint" claim holds — with one semantic delta worth knowing

behaviour matrix

Real build() → ajv → real execute(), every routing-field combination × team present/absent:

  • {message} alone is still rejected after the change — No active team and no task_id provided… with no team, Recipient "to" is required. with a team. Runtime enforcement is intact in both states, which is exactly the PR's claim.
  • Cost is unchanged, not increased: an ajv rejection already surfaced to the model as an error tool-result (createErrorResponse(..., INVALID_TOOL_PARAMS) in coreToolScheduler.ts). Before and after, a recipient-less call costs the model exactly one error round-trip — only the message text differs. The old comment's "wastes a round-trip" rationale did not hold even before this PR.
  • One real behavioural delta: {message, to, task_id} (both routing fields set) was rejected by oneOf ("must match exactly one schema") and is now accepted, routing to task_id and silently ignoring to. Benign — it matches execute()'s existing route order — but it is undocumented. Optional one-liner for the tool description: "If both are given, task_id wins." That also restores the model hint the oneOf used to carry, in a form Anthropic accepts.

3. Regression clean; the one gap is a missing guard

regression + guard

  • 3048 tests / 91 files pass (src/tools, src/core/anthropicContentGenerator, schema utils) on the PR head. tsc --noEmit on packages/core: 0 errors. eslint + prettier on the changed file: clean.
  • I swept all 62 registered tools (including deferred) through the real converter: on the PR head, zero top-level combinators; on main, exactly one — send_message. The fix is complete for the built-in surface, and the bot is right that agent.ts's fork_turns oneOf is property-level and correctly untouched.
  • Coverage gap: the existing 20 send-message.test.ts tests pass identically against both arms, so nothing pins this. The oneOf shipped with the Agent Team feature in feat: add Agent Team experimental feature for parallel sub-agent coordination #4844 (11 Jun) and has been unguarded since. A guard that fails on main and passes here (proven in the screenshot) is 12 lines:
Proposed regression guard (verified discriminating)
const ROOT_COMBINATORS = ['oneOf', 'allOf', 'anyOf'] as const;

// Anthropic's Messages API rejects a tool whose `input_schema` carries a
// top-level combinator with a 400 that fails the WHOLE request, so one
// offending tool takes every other tool down with it.
it('declares no top-level oneOf/allOf/anyOf', () => {
  const tool = new SendMessageTool({} as unknown as Config);
  const schema = tool.schema.parametersJsonSchema as Record<string, unknown>;
  for (const keyword of ROOT_COMBINATORS) {
    expect(schema[keyword]).toBeUndefined();
  }
});

A registry-wide version (which catches the next tool to do this, not just this one) is what I actually ran:

const registry = await config.createToolRegistry(undefined, { skipDiscovery: true });
await registry.warmAll();
const offenders = registry
  .getFunctionDeclarations({ includeDeferred: true })
  .filter((decl) => {
    const schema = decl.parametersJsonSchema as Record<string, unknown> | undefined;
    return !!schema && ROOT_COMBINATORS.some((k) => schema[k] !== undefined);
  })
  .map((decl) => decl.name);
expect(offenders).toEqual([]);   // ['send_message'] on main, [] here

4. Follow-up (not a blocker for this PR)

The bot called converter-level hardening "speculative hardening for a constraint no tool actually needs." It isn't speculative for third-party tools: I registered a real DiscoveredMCPTool whose server-advertised schema uses a root-level oneOf (valid JSON Schema — "either query or id") and the same session-bricking 400 comes back on the PR head. convertSchema(..., 'auto') is a no-op, so nothing between a tool declaration and Anthropic's validator normalizes root combinators, and an MCP server author has no way to know they must avoid them.

Fixing that belongs in the Anthropic converter (lift a root oneOf/anyOf into a validation-only concern, or drop it with a debug warning), not in this PR. Worth a separate issue.

Verdict

Merge as-is. Correct fix, minimal diff, no regressions, runtime enforcement genuinely intact, and the only behavioural delta is a benign precedence case. The two suggestions above — the guard test and the task_id-wins note in the description — are worth a follow-up commit but should not hold this up, given the bug bricks whole sessions on Anthropic-backed models.

中文说明

维护者验证 —— 在本地基于 b014df4 真实构建并测试

triage 机器人正确地指出:这里最关键的证据(wire 层的 400)只是作者的声明,未被独立复跑。我在本地把整条链路重新跑了一遍。结论:可以合并。 修复正确,"冗余约束"的论证成立,而且这个 bug 比 PR 描述的还要严重。

环境 —— 在 PR head 上开独立 worktree,使用真实 Config + 真实工具注册表 + 真实 AnthropicContentGenerator(真实 Anthropic SDK client、真实 converter),指向一个本地端点:它捕获真实的 JSON 请求体,并施加 Anthropic 有文档记载的 tool-schema 规则。两个 arm 之间的唯一变量就是被删掉的这 6 行——通过把 origin/mainsend-message.ts 覆盖进同一棵树来切换。

哪些是实测、哪些是引用: 下面所有结论都是本地实测,除了 Anthropic 服务端的拒绝本身——我这个环境没有 Anthropic 凭据,所以我的端点是"回放" #7984 里 live curl 得到的文档化错误,而不是重新证明它。我真正实测的是此前从未被验证的那部分:原始 oneOf 确实原封不动地进入了 tools[].input_schema,以及它的代价。

1. 前提在 wire 层得到确认——而且影响面比描述的更大

main arm 捕获到的请求体中,send_message.input_schema 根部确实带着 oneOf,未被改写。并且与 provider 配置无关:convertSchemaautoopenapi_30 两种模式下都保留顶层 oneOf,因此所有 Anthropic 后端配置都受影响。

PR 说"send_message 不可用",而生命周期表说明的问题更严重:

  • send_messageshouldDefer: true,所以新会话是好的——它不在 tools[] 里(13 个工具)。
  • 一旦 ToolSearch 揭示它(或 visibleTools 列出它),它进入 tools[](14 个工具),请求即 400。
  • 该揭示在整个会话内是粘性的,所以第 3 轮——一条与 send_message 毫无关系的普通消息——同样 400。一个坏的工具 schema 会让整个请求失败,把另外 13 个工具一起拖下水。
  • 恢复方式是 /clear(会调用 clearRevealedDeferredTools()),而不是"别用那个工具"。

所以在 Anthropic 后端模型上,这是会话级瘫痪的 bug,而不只是某个工具不可用。这提高了合并优先级,但不改变修复方案。

2. "冗余约束"的说法成立——但有一个值得知道的语义差异

真实 build() → ajv → 真实 execute(),覆盖两个路由字段的所有组合 × 有/无 team:

  • 改动后 {message} 仍然被拒绝——无 team 时是 No active team and no task_id provided…,有 team 时是 Recipient "to" is required.两种状态下运行时校验都完好,这正是 PR 的核心主张。
  • 代价没有增加:ajv 的拒绝本来就是以错误型 tool-result 返回给模型的(coreToolScheduler.ts 中的 createErrorResponse(..., INVALID_TOOL_PARAMS))。改动前后,缺收件人的调用都只花费一次错误往返,只是文案不同。旧注释里"浪费一次往返"的理由在改动前就不成立。
  • 唯一真实的行为差异: {message, to, task_id}(两个路由字段都给)以前会被 oneOf 拒绝("必须恰好匹配一个 schema"),现在被接受,路由到 task_id 并静默忽略 to。这是良性的——与 execute() 既有的路由顺序一致——但没有文档。建议在工具描述里加一句:"若同时提供,以 task_id 为准。" 这同时也用 Anthropic 能接受的形式,把 oneOf 原本承载的模型提示补了回来。

3. 回归干净;唯一的缺口是缺少守卫测试

  • PR head 上 3048 个测试 / 91 个文件全部通过src/toolssrc/core/anthropicContentGenerator、schema 工具)。packages/coretsc --noEmit:0 错误。改动文件的 eslint + prettier:干净。
  • 我把全部 62 个已注册工具(含 deferred)过了一遍真实 converter:PR head 上零个顶层组合关键字;main 上恰好一个——send_message。对内置工具面而言修复是完整的;机器人说 agent.tsfork_turns oneOf 是属性级、正确地未改动,这一点也对。
  • 覆盖缺口: 现有的 20 个 send-message.test.ts 测试在两个 arm 上表现完全一致,也就是没有任何测试钉住这个改动。该 oneOf 随 Agent Team 功能在 feat: add Agent Team experimental feature for parallel sub-agent coordination #4844(6 月 11 日)引入,此后一直没有守卫。一个在 main 上失败、在此 PR 上通过的守卫(截图中已验证)只需 12 行——见上方英文折叠块中的代码。

4. 后续(不阻塞本 PR)

机器人把 converter 层加固称为"为没有工具真正需要的约束做投机性加固"。对第三方工具而言这并不投机:我注册了一个真实的 DiscoveredMCPTool,其服务端声明的 schema 使用根级 oneOf(合法 JSON Schema——"query 或 id 二选一"),在 PR head 上同样的会话级 400 又回来了。convertSchema(..., 'auto') 是空操作,所以从工具声明到 Anthropic 校验器之间没有任何环节会规范化根级组合关键字,而 MCP server 作者无从得知必须避开它们。

这个修复应该落在 Anthropic converter 里(把根级 oneOf/anyOf 降级为仅本地校验,或带 debug 警告丢弃),而不是本 PR。值得单开一个 issue。

结论

按原样合并。 修复正确、diff 最小、无回归、运行时校验确实完好,唯一的行为差异是良性的优先级问题。上面两条建议——守卫测试与描述里的 "task_id 优先" 说明——值得后续补一个 commit,但不应阻塞本 PR:这个 bug 会让 Anthropic 后端模型的整个会话瘫痪。

@wenshao
wenshao added this pull request to the merge queue Jul 29, 2026
Merged via the queue into QwenLM:main with commit eb5798b Jul 29, 2026
53 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(core): send_message tool schema's top-level oneOf breaks it entirely on Anthropic-backed models

4 participants