From c1c14c9267afa2627da99719798074db75823d84 Mon Sep 17 00:00:00 2001 From: "Ruihao (Ray) Zhang" Date: Wed, 21 Oct 2026 11:58:28 +0000 Subject: [PATCH 1/3] test(model-gateway): keep the Agent SDK beta combination covered Replacing the SDK capability test with the per-CLI table dropped the only direct check that structured-outputs-2025-12-15 and thinking-display-updates-2026-08-18, which the Agent SDK adds on top of the pinned CLI, are forwarded over ?beta=true with bearer auth. Restore it. --- internal/modelgateway/anthropic_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/internal/modelgateway/anthropic_test.go b/internal/modelgateway/anthropic_test.go index 9e2b883de..e4b4ee2b1 100644 --- a/internal/modelgateway/anthropic_test.go +++ b/internal/modelgateway/anthropic_test.go @@ -48,6 +48,30 @@ func TestPinnedCLIClaudeModelBetas(t *testing.T) { } } +// Agent SDK query creation enables these additional capabilities on the same pinned native CLI. +func TestPinnedClaudeAgentSDKCapabilities(t *testing.T) { + betas := pinnedClaudeBetas + ",structured-outputs-2025-12-15,thinking-display-updates-2026-08-18" + var calls atomic.Int64 + service, _ := anthropicFixture(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls.Add(1) + if r.Header.Get("Anthropic-Beta") != betas || r.URL.RawQuery != "beta=true" { + t.Error("SDK capabilities changed while forwarding") + } + w.Header().Set("Content-Type", "application/json") + _, _ = io.WriteString(w, "{\"id\":\"m\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"fixture-model\",\"content\":[{\"type\":\"text\",\"text\":\"{}\"}],\"stop_reason\":\"end_turn\",\"usage\":{\"input_tokens\":3,\"output_tokens\":1}}") + })) + request := httptest.NewRequest(http.MethodPost, "/runtime/anthropic/v1/messages?beta=true", strings.NewReader("{\"model\":\"fixture-model\",\"messages\":[],\"max_tokens\":16}")) + request.Header.Set("Authorization", "Bearer "+strings.Repeat("r", 40)) + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Anthropic-Version", anthropicVersion) + request.Header.Set("Anthropic-Beta", betas) + response := httptest.NewRecorder() + service.ModelHandler(response, request) + if response.Code != 200 || calls.Load() != 1 { + t.Fatalf("pinned SDK request rejected: status=%d calls=%d", response.Code, calls.Load()) + } +} + // anthropicFixture keeps the upstream key separate from the workload's short-lived token. func anthropicFixture(t *testing.T, handler http.Handler) (*Service, *fakePolicy) { t.Helper() From e4dcc492e94e80f336cb230677d8d73383e010c4 Mon Sep 17 00:00:00 2001 From: "Ruihao (Ray) Zhang" Date: Wed, 21 Oct 2026 11:58:28 +0000 Subject: [PATCH 2/3] test(model-gateway): describe the captured Claude header as a direct CLI request The header in the table was recorded from the bundled CLI run with the adapter's alias and model environment, not through the ACP adapter, so it does not include the Agent SDK's additions. --- internal/modelgateway/anthropic_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/modelgateway/anthropic_test.go b/internal/modelgateway/anthropic_test.go index e4b4ee2b1..a0367fd5e 100644 --- a/internal/modelgateway/anthropic_test.go +++ b/internal/modelgateway/anthropic_test.go @@ -19,7 +19,8 @@ const pinnedClaudeBetas = "claude-code-20250219,interleaved-thinking-2025-05-14, // Each pinned CLI's exact first-request header for a Claude model is forwarded unchanged. func TestPinnedCLIClaudeModelBetas(t *testing.T) { for name, betas := range map[string]string{ - // Claude Code 2.1.303 through the pinned ACP adapter, with claude-haiku-5-5. + // Claude Code 2.1.303 run directly with the adapter's alias and model environment, with + // claude-haiku-5-5; the Agent SDK's own additions are covered separately below. "claude": "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", // OpenCode 1.19.3 through @ai-sdk/anthropic, with claude-haiku-5-5. "opencode": "thinking-binding-controls-2026-08-01,structured-outputs-2025-11-13", From a6227991d22bf8aba3020d439d275ed2dca605bc Mon Sep 17 00:00:00 2001 From: "Ruihao (Ray) Zhang" Date: Wed, 21 Oct 2026 11:58:28 +0000 Subject: [PATCH 3/3] docs(model-gateway): point the beta allowlist at its table instead of a count The READMEs said the allowlist had nine tokens; it now has twelve. Name the claudeBetaHeaders table so the docs do not drift again when it changes. --- internal/modelgateway/README.en.md | 4 ++-- internal/modelgateway/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/modelgateway/README.en.md b/internal/modelgateway/README.en.md index 5e7b49b3b..f83380f5f 100644 --- a/internal/modelgateway/README.en.md +++ b/internal/modelgateway/README.en.md @@ -25,8 +25,8 @@ SSE events flush continuously; error events inside HTTP200 are sanitized too. Re the upstream and a blocked downstream write. Each watcher has one request owner, cancellation and a join. The Anthropic surface targets Claude Code 2.1.303. POST messages accepts no query or exactly -beta=true; the version is fixed to 2023-06-01 and beta tokens are limited to the nine in -anthropic.go. Duplicate or unknown tokens fail. Other client headers are dropped; the CLI's +beta=true; the version is fixed to 2023-06-01 and beta tokens are limited to the claudeBetaHeaders +set in anthropic.go. Duplicate or unknown tokens fail. Other client headers are dropped; the CLI's browser-access marker is validated and discarded. GET models (no query or limit=1000) rechecks current authorization and returns only the frozen model without an upstream call, key decryption or inference-attempt record. Optional endpoints such as count_tokens remain explicitly unsupported. diff --git a/internal/modelgateway/README.md b/internal/modelgateway/README.md index f9bdd13ef..1e0394ce2 100644 --- a/internal/modelgateway/README.md +++ b/internal/modelgateway/README.md @@ -23,7 +23,7 @@ 每个授权观察任务归属于一个请求,取消后必须等待它退出。 Anthropic 兼容面固定到 Claude Code 2.1.303:推理只接受 POST messages 的空 query 或 -精确 beta=true;协议版本固定 2023-06-01,beta token 仅允许 anthropic.go 列出的九项, +精确 beta=true;协议版本固定 2023-06-01,beta token 仅允许 anthropic.go 中 claudeBetaHeaders 列出的值, 重复或未知值拒绝。其他客户端头不转发;CLI 的 browser-access 标记只验证并丢弃。 GET models(空 query 或 limit=1000)在重新核验当前授权后返回绑定的唯一模型,不请求上游、 不解密 Key、不登记推理 attempt。count_tokens 等可选接口保持未实现并明确拒绝。