From ab3e44e4bd23265fdaaf74121db3e5f4df4458a8 Mon Sep 17 00:00:00 2001 From: shaw Date: Mon, 30 Mar 2026 11:28:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=80=82=E9=85=8DX-Claude-Code-Session-?= =?UTF-8?q?Id=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/service/gateway_service.go | 20 ++++++++++++++++++++ backend/internal/service/header_util.go | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 44214b65..b54f463b 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -369,6 +369,8 @@ var allowedHeaders = map[string]bool{ "user-agent": true, "content-type": true, "accept-encoding": true, + "x-claude-code-session-id": true, + "x-client-request-id": true, } // GatewayCache 定义网关服务的缓存操作接口。 @@ -5756,6 +5758,15 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex } } + // 同步 X-Claude-Code-Session-Id 头:取 body 中已处理的 metadata.user_id 的 session_id 覆盖 + if sessionHeader := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id"); sessionHeader != "" { + if uid := gjson.GetBytes(body, "metadata.user_id").String(); uid != "" { + if parsed := ParseMetadataUserID(uid); parsed != nil { + setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", parsed.SessionID) + } + } + } + // === DEBUG: 打印上游转发请求(headers + body 摘要),与 CLIENT_ORIGINAL 对比 === s.debugLogGatewaySnapshot("UPSTREAM_FORWARD", req.Header, body, map[string]string{ "url": req.URL.String(), @@ -8475,6 +8486,15 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con } } + // 同步 X-Claude-Code-Session-Id 头:取 body 中已处理的 metadata.user_id 的 session_id 覆盖 + if sessionHeader := getHeaderRaw(req.Header, "X-Claude-Code-Session-Id"); sessionHeader != "" { + if uid := gjson.GetBytes(body, "metadata.user_id").String(); uid != "" { + if parsed := ParseMetadataUserID(uid); parsed != nil { + setHeaderRaw(req.Header, "X-Claude-Code-Session-Id", parsed.SessionID) + } + } + } + if c != nil && tokenType == "oauth" { c.Set(claudeMimicDebugInfoKey, buildClaudeMimicDebugLine(req, body, account, tokenType, mimicClaudeCode)) } diff --git a/backend/internal/service/header_util.go b/backend/internal/service/header_util.go index 6acfee5a..1091070d 100644 --- a/backend/internal/service/header_util.go +++ b/backend/internal/service/header_util.go @@ -36,6 +36,11 @@ var headerWireCasing = map[string]string{ "sec-fetch-mode": "sec-fetch-mode", "accept-encoding": "accept-encoding", "authorization": "authorization", + + // Claude Code 2.1.87+ 新增 header + "x-claude-code-session-id": "X-Claude-Code-Session-Id", + "x-client-request-id": "x-client-request-id", + "content-length": "content-length", } // headerWireOrder 定义真实 Claude CLI 发送 header 的顺序(基于抓包)。 @@ -55,11 +60,14 @@ var headerWireOrder = []string{ "authorization", "x-app", "User-Agent", + "X-Claude-Code-Session-Id", "content-type", "anthropic-beta", + "x-client-request-id", "accept-language", "sec-fetch-mode", "accept-encoding", + "content-length", "x-stainless-helper-method", }