mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(gateway): filter anthropic-beta on the Vertex Anthropic path (#3358)
Vertex AI's Anthropic endpoint rejects unknown anthropic-beta tokens with HTTP 400. buildUpstreamRequestAnthropicVertex forwarded the client header verbatim via the allowedHeaders whitelist, so recent Claude Code CLIs that send advisor-tool-2026-03-01, prompt-caching-scope-2026-01-05, redact-thinking-2026-02-12 and thinking-token-count-2026-05-13 broke every Vertex service_account request, even though plain account-test requests passed. This is the only upstream builder that bypassed beta filtering: the OAuth/API-key path uses computeFinalAnthropicBeta and the Bedrock path uses filterBedrockBetaTokens. Close the gap with a Vertex-specific whitelist (vertexSupportedBetaTokens) mirroring bedrockSupportedBetaTokens, plus the existing BetaPolicy block check: - evaluateBetaPolicy block check (symmetric to resolveBedrockBetaTokensForRequest) - filterVertexBetaTokens strips policy-filtered + non-whitelisted tokens - body context_management sanitize now keys on the final beta, not the raw client value - overwrite the anthropic-beta header after the whitelist copy loop with the final value Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4a5665da5b
commit
40e1cc14b3
@@ -6830,6 +6830,48 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
|
||||
return req, body, nil
|
||||
}
|
||||
|
||||
// vertexSupportedBetaTokens 是 Vertex AI 的 Anthropic 端点接受的 anthropic-beta
|
||||
// 白名单。Vertex 对任何未知 token 直接 HTTP 400,故采用白名单(与 Bedrock 的
|
||||
// bedrockSupportedBetaTokens 同思路)而非黑名单:未来 Claude Code 新增的、Vertex 尚未
|
||||
// 支持的 token 天然被剥离。当 Vertex 新增支持某 beta 时在此补充。
|
||||
//
|
||||
// 明确排除(issue #3358 中 Vertex 报 400 的 token):advisor-tool-2026-03-01、
|
||||
// prompt-caching-scope-2026-01-05、redact-thinking-2026-02-12、
|
||||
// thinking-token-count-2026-05-13;以及 claude-code-20250219 / oauth-2025-04-20 等
|
||||
// 客户端身份 beta——Vertex service_account 走 Bearer 鉴权,不需要它们。
|
||||
var vertexSupportedBetaTokens = map[string]bool{
|
||||
"context-1m-2025-08-07": true,
|
||||
"context-management-2025-06-27": true,
|
||||
"fine-grained-tool-streaming-2025-05-14": true,
|
||||
"interleaved-thinking-2025-05-14": true,
|
||||
}
|
||||
|
||||
// filterVertexBetaTokens 解析 client 的 anthropic-beta header,先剔除 drop 集合中的
|
||||
// token(BetaPolicy filter + 默认 drop),再只保留 Vertex 支持的 token,去重后逗号拼接。
|
||||
// 返回最终 header(可能为空字符串)。
|
||||
func filterVertexBetaTokens(header string, drop map[string]struct{}) string {
|
||||
tokens := parseAnthropicBetaHeader(header)
|
||||
if len(tokens) == 0 {
|
||||
return ""
|
||||
}
|
||||
out := make([]string, 0, len(tokens))
|
||||
seen := make(map[string]bool, len(tokens))
|
||||
for _, t := range tokens {
|
||||
if _, dropped := drop[t]; dropped {
|
||||
continue
|
||||
}
|
||||
if !vertexSupportedBetaTokens[t] {
|
||||
continue
|
||||
}
|
||||
if seen[t] {
|
||||
continue
|
||||
}
|
||||
seen[t] = true
|
||||
out = append(out, t)
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func (s *GatewayService) buildUpstreamRequestAnthropicVertex(
|
||||
ctx context.Context,
|
||||
c *gin.Context,
|
||||
@@ -6844,14 +6886,27 @@ func (s *GatewayService) buildUpstreamRequestAnthropicVertex(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 能力维度 sanitize:Vertex 路径上 anthropic-beta header 原样透传客户端值
|
||||
// (下面白名单跳过 anthropic-version 但保留 anthropic-beta),依此决定是否
|
||||
// 保留 body 中的 context_management,与 Anthropic 直连 / Bedrock 路径对称。
|
||||
// 计算最终 outgoing anthropic-beta。Vertex AI 的 Anthropic 端点只接受一小撮
|
||||
// beta token,未知 token 会直接 HTTP 400——近期 Claude Code CLI 透传的
|
||||
// advisor-tool-2026-03-01 / prompt-caching-scope-2026-01-05 /
|
||||
// redact-thinking-2026-02-12 / thinking-token-count-2026-05-13 都不被 Vertex 接受
|
||||
// (issue #3358)。这里复用 BetaPolicy 的 block 检查(与 Bedrock 的
|
||||
// resolveBedrockBetaTokensForRequest 对称),再按 vertexSupportedBetaTokens 白名单
|
||||
// 剥离其余 token,使该路径与 Anthropic 直连 / Bedrock 路径行为一致。
|
||||
clientBeta := ""
|
||||
if c != nil && c.Request != nil {
|
||||
clientBeta := getHeaderRaw(c.Request.Header, "anthropic-beta")
|
||||
if sanitized, changed := sanitizeAnthropicBodyForBetaTokens(vertexBody, clientBeta); changed {
|
||||
vertexBody = sanitized
|
||||
}
|
||||
clientBeta = getHeaderRaw(c.Request.Header, "anthropic-beta")
|
||||
}
|
||||
policy := s.evaluateBetaPolicy(ctx, clientBeta, account, modelID)
|
||||
if policy.blockErr != nil {
|
||||
return nil, policy.blockErr
|
||||
}
|
||||
finalBeta := filterVertexBetaTokens(clientBeta, mergeDropSets(policy.filterSet))
|
||||
|
||||
// 能力维度 sanitize:基于最终 beta(而非原始 client 值)决定是否保留 body 中的
|
||||
// context_management,与 Anthropic 直连 / Bedrock 路径对称。
|
||||
if sanitized, changed := sanitizeAnthropicBodyForBetaTokens(vertexBody, finalBeta); changed {
|
||||
vertexBody = sanitized
|
||||
}
|
||||
fullURL, err := buildVertexAnthropicURL(account.VertexProjectID(), account.VertexLocation(modelID), modelID, reqStream)
|
||||
if err != nil {
|
||||
@@ -6883,6 +6938,13 @@ func (s *GatewayService) buildUpstreamRequestAnthropicVertex(
|
||||
setHeaderRaw(req.Header, "authorization", "Bearer "+token)
|
||||
setHeaderRaw(req.Header, "content-type", "application/json")
|
||||
|
||||
// 覆盖上面白名单 loop 写入的原始 client anthropic-beta,使用过滤后的最终值。
|
||||
// finalBeta 为空(全部被剥离)时不下发该 header,与 Vertex 无 beta 请求一致。
|
||||
deleteHeaderAllForms(req.Header, "anthropic-beta")
|
||||
if finalBeta != "" {
|
||||
setHeaderRaw(req.Header, "anthropic-beta", finalBeta)
|
||||
}
|
||||
|
||||
s.debugLogGatewaySnapshot("UPSTREAM_FORWARD_VERTEX_ANTHROPIC", req.Header, vertexBody, map[string]string{
|
||||
"url": req.URL.String(),
|
||||
"token_type": "service_account",
|
||||
|
||||
Reference in New Issue
Block a user