Skip to content

fix(model-gateway): allow the betas the pinned Claude and OpenCode CLIs send for Claude models - #115

Merged
EricWvi merged 2 commits into
ora-space:mainfrom
ObsisMc:fix/370-claude-per-turn-control-beta
Oct 21, 2026
Merged

EricWvi merged 2 commits into
ora-space:mainfrom
ObsisMc:fix/370-claude-per-turn-control-beta

Conversation

@ObsisMc

@ObsisMc ObsisMc commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Claude Code 2.1.303, driven through the pinned claude-agent-acp 0.94.0 with claude-haiku-5-5, sends per-turn-control-2026-07-01 in anthropic-beta on its first Messages request. The gateway's bounded allowlist did not have it, so the request was refused with 400 model_protocol_header_forbidden before any upstream attempt, and the run ended agent_turn_failed.

The same rehearsal then showed the pinned OpenCode 1.19.3, requesting claude-haiku-5-5 through @ai-sdk/anthropic, sending thinking-binding-controls-2026-08-01 and structured-outputs-2025-11-13; both were refused the same way. The allowlist was only ever drawn from the Claude CLI, so this affects OpenCode with any Claude model regardless of route.

This adds those three tokens, a test that replays each pinned CLI's exact first-request header, and the matching sentences in both model-gateway docs.

Evidence

  • Before: a pre-merge rehearsal (main + feat(agent-runtime): dispatch frozen configuration and verify Node adapters #106 + feat(web): add personal Agent configuration and model entry #103, desktop#708, a declared Claude package) failed twice on the first turn. The Thread recorded API Error: 400 {"code":"model_protocol_header_forbidden",...} and there were no model_request_attempts rows.
  • The header was captured by pointing the bundled aarch64 CLI at a local recorder, with the same alias and model environment the adapter uses: interleaved-thinking-2025-05-14,thinking-token-count-2026-05-13,context-management-2025-06-27,prompt-caching-scope-2026-01-05,claude-code-20250219,per-turn-control-2026-07-01,mid-conversation-tool-changes-2026-07-01,effort-2025-11-24.
  • After: with this entry, the same rehearsal ran two real turns against Anthropic with repository tool calls, ended completed with a changed Revision, and released the run Workspace.
  • OpenCode's header was captured the same way, with the plugin-generated provider configuration pointed at a local recorder: thinking-binding-controls-2026-08-01,structured-outputs-2025-11-13.
  • TestPinnedCLIClaudeModelBetas (Claude and OpenCode cases) fails with status=400 calls=0 without the entries and passes with them; go test -count=1 ./internal/modelgateway/ passes.

Not related to any ADR status change: the policy is the existing bounded allowlist.

Refs ora-space/circus#370

Merge Danger

Door: two-way

Blast Radius: model-gateway

Adds three accepted beta values on the Anthropic Messages route; unknown and duplicate values are still refused.

… CLI sends

Claude Code 2.1.303, driven through the pinned ACP adapter with
claude-haiku-5-5, adds per-turn-control-2026-07-01 to its first Messages
request. The gateway refused it as an unknown Anthropic extension, so
every such run ended with agent_turn_failed before any upstream call.
The pinned OpenCode 1.19.3 declares thinking-binding-controls-2026-08-01
and structured-outputs-2025-11-13 when it requests a claude-* model over
Anthropic Messages through @ai-sdk/anthropic. Both were refused as unknown
extensions, failing the turn before any upstream attempt. The test now
replays each pinned CLI's exact first-request header.
@ObsisMc ObsisMc changed the title fix(model-gateway): allow the per-turn-control beta the pinned Claude CLI sends fix(model-gateway): allow the betas the pinned Claude and OpenCode CLIs send for Claude models Oct 20, 2026

@HappyAuratus HappyAuratus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

已审查 head b4c5e23097f4215490ee6427ffeb28f0c247825a(base ff72afa0d8dbebe77ec10231936221e17945c048),无阻塞发现,Approve。

生产改动仅向既有 Anthropic beta 白名单添加三个固定 token。已沿 runtimeModelRoute → ModelHandler → anthropicProtocolHeaders → 上游请求构造核对:未知/重复值拒绝、固定协议版本、冻结模型、认证替换、限定目标及转发生命周期均未被放宽。四个改动文件均为交付物,中英文模型网关文档与新增值一致。

本地验证:新增 Claude/OpenCode 首请求 beta 组合、既有 query/header/tool SSE、未知协议面拒绝与冻结模型目录测试均通过;go run ./cmd/checkformat 和 git diff --check 通过。全包 go test -count=1 ./internal/modelgateway/ 在本 Windows 环境有三个既有 egress 测试因 staticAddresses 地址策略失败;已在 base 对相同三个测试复现。PR 的 Backend CI 为 SUCCESS,包含 format、lint、race、PostgreSQL/S3 测试及 build。未重跑真实 CLI/供应商、完整 PostgreSQL 验收,本结论不替代 #370 的正式验收。

非阻塞建议:

  • internal/modelgateway/anthropic_test.go:19:新增两个 CLI 用例时替换掉了原 TestPinnedClaudeAgentSDKCapabilities,使原先 structured-outputs-2025-12-15 与 thinking-display-updates-2026-08-18 的 SDK 组合不再有直接转发回归覆盖。建议在表里保留第三个 SDK 用例。
  • internal/modelgateway/README.md:26 与 README.en.md:28 仍说白名单只有九项;当前已有十二项。建议同步中英文说明,直接引用策略表而不固定数量可避免再次漂移。

@wanglongan587 wanglongan587 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.

审查版本:b4c5e23097f4215490ee6427ffeb28f0c247825a(base ff72afa0d8dbebe77ec10231936221e17945c048,对照 upstream/main 66533b4f592aa24ef654b18fdd2ff68907504302)。结论:Approve,无阻塞发现。与 @HappyAuratus 在同一 head 上的 Approve 一致,本次是在同一 head 上的独立复核。

已核对

  • 生产改动只有 internal/modelgateway/anthropic.go 的三个新 token。anthropicProtocolHeaders 仍是逐 token 的受限白名单:未知值、重复值、空值、超过 1024 字节都拒绝;runtimeModelRoute 的路径与 query 约束、冻结模型目录、认证替换、出网检查和转发生命周期都没有放宽。
  • 与当前 upstream/main(66533b4)对比:internal/modelgateway/ 和 docs/models/ 自 base 起没有其它提交;本地 git merge-tree 模拟与 main 合并无冲突(MERGEABLE/CLEAN)。
  • 文档:中英文 model-gateway 文档的改动与代码一致,四个改动文件都是交付物。
  • ADR:PR 描述写明不涉及 ADR 状态变化。我在 specs upstream/main(0a100a6)上检索 anthropic-beta、beta token、受限白名单,没有任何 ADR 列举这份白名单,因此本 PR 不需要规格落地 PR,描述的说法成立。
  • CI:check 通过(run 39443434741)。
  • 本地验证(worktree 检出 b4c5e230):gofmt -l internal/modelgateway 无输出;go build ./internal/modelgateway/ 通过;TestPinnedCLIClaudeModelBetas、TestClaudeProtocolRejectsUnknownSurface、TestClaudeMessagesQueryHeadersAndToolStream 通过。
  • 反向核对:临时删掉这三个 token 后,TestPinnedCLIClaudeModelBetas 的 claude 与 opencode 两个子用例都以 status=400 calls=0 失败;恢复后通过。这条测试确实保护新增值,与 PR 描述一致。
  • 全包 go test -count=1 ./internal/modelgateway/ 在本 Windows 环境有三个 egress_test.go 用例因 staticAddresses 地址策略失败;在 base ff72afa0 上用相同命令复现了同样三个失败,与本次改动无关。

非阻塞(建议在 #370 的下一个 PR 一并处理)

  1. internal/modelgateway/README.md:26、README.en.md:28 仍写 beta token“九项”(英文 “the nine in anthropic.go”),本 PR 之后是十二项。建议改成直接引用 anthropic.go 的策略表、不再固定数量,避免下次再漂移。
  2. internal/modelgateway/anthropic_test.go:19-25 的表格替换掉 TestPinnedClaudeAgentSDKCapabilities 后,structured-outputs-2025-12-15 与 thinking-display-updates-2026-08-18 的 SDK 组合不再有直接转发回归覆盖(两个值仍在白名单里,但没有任何测试固定它们)。建议保留第三个 SDK 用例。
  3. anthropic_test.go:22 的注释写 “through the pinned ACP adapter”,但按 PR 描述,这份头是“pointing the bundled aarch64 CLI at a local recorder”直连录的,不含 SDK 追加的两项。建议把注释改成 CLI 直连头(或保留 SDK 用例后分别标注),免得后来的人把它当成 adapter 的完整生产头。

以上都不是本次改动的功能问题。本结论只针对 b4c5e230,不替代 #370 第 3 项的真实供应商验收;第 2–4 项仍在等前置与 owner 的开关决定。

@wanglongan587

Copy link
Copy Markdown
Collaborator

补充一条同源的非阻塞项(不改变 Approve 结论):上面第 1 条的模块 README 里,除了“九项”之外,同一段还写 “Anthropic 兼容面固定到 Claude Code 2.1.303”(internal/modelgateway/README.md:25,英文 README.en.md:27 “The Anthropic surface targets Claude Code 2.1.303”)。本 PR 之后 OpenCode 1.19.3 也通过同一白名单贡献 beta,这句限定已经不完整;建议连同数量一起改成“由 anthropic.go 的受限集合定义,升级任一固定 CLI 前先核对”的措辞。

另外核对过但不需要改动的:替换后的表测试仍覆盖 beta=true query 与 Bearer 认证路径(分别在 anthropic_test.go:80/97 与 :100/:155/:203),这两项没有丢;丢的只是那两个 SDK beta 的直接回归覆盖(见上)。

@EricWvi
EricWvi merged commit 3366278 into ora-space:main Oct 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants