Skip to content

feat: 客户端发来的协议头没有放行的路(anthropic-version、beta 之类) #23

Closed

Description

@tokenmp

现象

内核构造上游请求时完全不碰客户端发来的请求头:server/forward.go 只取宿主用
WithUpstreamHeader 设的那一份(headers := s.upstreamHeaders(ctx)),塞进
channel.Request.Header,此外没有任何地方读过进来的请求头。

这一点与方言是否相同无关:同方言透传只是 body 逐字节透传,头一样不带。
进来的 query 串也不带(channel/client.go 先 parsed.RawQuery = "",
之后只为 auth.style: query 拼鉴权参数),所以 Claude Code 的
POST /v1/messages?beta=true 里那个 ?beta=true 也丢了。

设计意图是不让客户端的 Authorization 漏给上游(issue #17 的验收条件,有测试钉住),
这个出发点没问题。代价分三档:

一、无所谓的。 x-stainless-*、accept-encoding、user-agent 这类观测性的头。
Go 自己会补后两个。

二、静态能补,但现在是手工活:anthropic-version。
官方 versioning 页原话:

When making API requests, you must send an anthropic-version request header. For
example, anthropic-version: 2023-06-01. If you are using the client SDKs, this is
handled for you automatically.

关键在后半句:内核自己就是那个「客户端」,SDK 帮不了它,得它自己发。
而仓库里搜不到任何地方设置它,RelayKit 里也没有,所以接真实 Anthropic 上游只能在
渠道的 headers: 里写死,每个渠道写一遍。

口径冲突要说清:Messages API 参考页把同一个东西标成
"anthropic-version": optional string,与 versioning 页的 must send 不一致。
两处口径都列在这里,不当下结论。

三、按请求变化的,现在补不了:anthropic-beta。
官方参考里多处写「Requires the user-profiles beta header」、
「based on beta headers」—— 功能开关就是靠这个头。Claude Code 每请求可能开不同的
beta,静态配置表达不了。同类还有 Codex 的 Session-Id、Originator、X-Codex-*,
以及 OpenAI 的 OpenAI-Beta。

期望

  • 按方言声明一份放行白名单:名单里的头按请求跟到上游。
  • 名单外的照旧不转发,尤其 authorization、x-api-key、cookie。
  • 落到哪一个上游 workspace 属于计费归属,anthropic-workspace-id 这类
    「选上游归属」的头也不从客户端放行,由中继或宿主决定。
  • 名单为空时行为与 v0.5.0 逐条相同。

可选形状

  1. 渠道配置里加放行名单(像上游 new-api 那样,按客户端类型配)。
  2. 名单写在方言里(代码固定,方言决定协议需要哪些头),配置只用来开关或追加。
  3. 不做,把「要哪些头就在渠道 headers: 里写死」写进 docs/config.md。

验收条件

  • 名单里的头按请求到达上游;名单外的不到,authorization、x-api-key、cookie、
    anthropic-workspace-id 有不带去的用例。
  • 名单为空时行为与 v0.5.0 相同(有测试对照)。
  • 与宿主用 WithUpstreamHeader 设的头同名时的优先级写清并有测试。
  • make check 全绿。

要定的口径

  • 名单放哪一层:渠道配置,还是方言(谁维护、谁负责跟进上游新增的协议头)。
  • 只能是白名单,不能是黑名单 —— 这条没有余地,写下来是防止后来人图省事反过来。
  • 要不要给方言一份内置默认名单(比如 Claude 渠道默认放 anthropic-version 与
    anthropic-beta)。
  • 与 WithUpstreamHeader(issue feat: 宿主按请求给上游加头没有路可走 #17,已随 v0.5.0 发布)的关系:前者是宿主主动加,
    这里是客户端本来就有、协议要求带过去。同名时谁赢要定,并写进文档。
  • anthropic-version 到底必需不必需(两处官方口径冲突,要实测)。

上游 new-api 的情况(查证过)

它有两份按客户端挑的透传名单:

claudeCliPassThroughHeaders: X-Stainless-Arch / Lang / Os / Package-Version /
                             Retry-Count / Runtime / Runtime-Version / Timeout,
                             User-Agent, X-App, Anthropic-Beta, Anthropic-Version,
                             Anthropic-Dangerous-Direct-Browser-Access
codexCliPassThroughHeaders:  Originator, Session_id, Thread_id, Session-Id, Thread-Id,
                             X-Client-Request-Id, User-Agent, X-Codex-Beta-Features,
                             X-Codex-Turn-State, X-Codex-Turn-Metadata, X-Codex-Window-Id,
                             X-Codex-Parent-Thread-Id, X-OpenAI-Subagent,
                             X-OpenAI-Memgen-Request, X-ResponsesAPI-Include-Timing-Metrics,
                             X-OpenAI-Internal-Codex-Responses-Lite

它用 param_override_template 的 pass_headers 加 keep_origin 按请求放行。
说明「一个头都不抄」太绝对了:真实部署需要按请求放行一部分。

没查的

  • 缺 anthropic-version 时真实上游的确切反应(两处文档口径冲突,没有实测)。
  • Claude Code 与 Codex 实际发出的完整头清单(没抓包;上面那份名单来自 new-api)。
  • 中转层(LiteLLM、new-api 之类)是否通常替上游补 anthropic-version。

相关

环境

core v0.5.0(dd1c7ab)。函数名与行号取自这一版。

Activity

  1. tokenmp commented on Oct 11, 2026

    @tokenmp
    OwnerAuthor

    落地的形状与这条 issue 的「期望」有一处偏离,记在这里。

    白名单放在渠道级(channels[].pass_headers),不放在方言级。 issue 写的是「按方言声明一份放行白名单」。

    理由是放行的信任边界:要回答的问题是「这条上游渠道可以收到哪些客户端头」,不是「客户端是什么方言」。按客户端方言放行会把 Claude 客户端的特性开关(anthropic-beta)带给一个它没打算对话的 OpenAI 上游,而那正是这条功能要防的事。渠道级由部署者按上游逐条声明。

    对应 PR #36(已随 v0.7.0 之后的下一个 tag)。文档里给了两份可直接复制的推荐名单(Claude Code 与 Codex),数据取自这条 issue 里那份 new-api 的透传清单。

    两处明确留在范围外:客户端请求的 query 串(?beta=true 仍然丢),以及内核自己补 anthropic-version 这类「协议必需的头」(仍写在渠道 headers 里,因为「是否必需」两处官方文档口径冲突、没有实测)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions