Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/modelgateway/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion internal/modelgateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 等可选接口保持未实现并明确拒绝。
Expand Down
27 changes: 26 additions & 1 deletion internal/modelgateway/anthropic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -48,6 +49,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()
Expand Down
Loading