shunt 可以在内置的 anthropic 提供方背后把多个 Claude 订阅 OAuth 凭据组成池。当 Claude Code 提供 x-claude-code-session-id 时请求具有会话粘性;不带该头部的请求使用按提供方的轮询。shunt 跟踪每个账户的上游配额头部,当粘性账户接近与模型相关的配额时主动轮换;而配额拒绝、认证失败和上游故障仍由被动故障转移兜底。
配置账户池
设置 auth = "claude_oauth" 并添加显式的账户条目:
[providers.anthropic]
kind = "anthropic"
base_url = "https://api.anthropic.com"
auth = "claude_oauth"
# 现有的 Claude Code 凭据文件。shunt 会刷新并写回。
[[providers.anthropic.accounts]]
name = "primary"
credentials = "~/.claude/.credentials.json"
uuid = "00000000-0000-0000-0000-000000000000" # 可选
# 长期有效的 `claude setup-token` 值。按原样使用;不刷新。
[[providers.anthropic.accounts]]
name = "backup"
token_env = "CLAUDE_BACKUP_OAUTH_TOKEN"
uuid = "11111111-1111-1111-1111-111111111111" # 可选export CLAUDE_BACKUP_OAUTH_TOKEN='<value from claude setup-token>'
shunt check
shunt run可用三种 Claude 登录模式中的任意一种存储账户:
# 创建一个新的可刷新登录(默认使用自动 localhost callback)。
shunt login claude --name primary --mode oauth
# 导入当前可刷新的 Claude Code 登录。
shunt login claude --name imported --mode import
# 生成并存储一个一年期、仅推理的 setup token。
shunt login claude --name backup --mode setup-token在 TTY 中省略 --mode 时,会打开默认选中 OAuth 的三选一提示。非交互输入继续沿用原有的 import 默认值。--long-lived 是 --mode setup-token 的 deprecated alias。Full OAuth 通常通过一次性的 127.0.0.1 callback 完成。要粘贴 <code>#<state>,请使用 --manual。如果浏览器启动、callback bind 或 5 分钟等待失败,shunt 也会回退到手动粘贴。
然后使用只带名字的条目:
[[providers.anthropic.accounts]]
name = "primary"
[[providers.anthropic.accounts]]
name = "backup"存储文件位于 ~/.shunt/accounts/claude/<name>.json;设置 SHUNT_CLAUDE_ACCOUNTS_DIR 可覆盖该目录。如果配置的 accounts 列表为空,shunt 会扫描存储目录,按文件名顺序使用所有有效的 JSON 账户文件。存储文件是私有的(Unix 上为 0600,目录为 0700)。
远程运营者可以通过可选启用的管理 Web 界面,在浏览器中预配可刷新的 Full OAuth 账户或一年期 setup token 账户,并查看池的当前健康状况。导入已有 credential 文件仍然仅限 CLI。
Full OAuth 创建一个新的可刷新 credential;import 把当前的 ~/.claude/.credentials.json credential 复制进 shunt 的存储。两者都保留刷新能力,import 还会记录当前账户 UUID。setup-token 模式运行与 claude setup-token 相同的一年期、仅推理 PKCE 流程。批准后,shunt 交换显示的授权码,把 token 与签发账户 UUID 一起存储,并且不打印 token。这使得当池选中另一个账户时,metadata.user_id.account_uuid 保持一致。复用同一个名称会替换该账户的存储文件。已有的外部 setup token 仍需要 token_env 加显式 uuid。
账户字段
| 字段 | 必填 | 含义 |
|---|---|---|
name |
是 | 只含小写字母、数字和连字符的唯一标签。若没有其他来源字段,则解析同名的 shunt 存储文件。 |
credentials |
可用来源之一 | Claude Code .credentials.json 形态的文件。~/ 会被展开。shunt 在临近过期时刷新,并将刷新后的 token 原子性地写回。 |
token_env |
可用来源之一 | 包含 setup token 的环境变量。其值按原样使用,401 之后无法刷新。 |
uuid |
否 | 所选账户的 Anthropic UUID,用于改写已存在的 metadata.user_id.account_uuid,同时也是池中用于合并别名的稳定身份。仅有名称的条目(通过存储扫描解析)会在选择发生前自动从存储的 shuntAccountUuid 填充。通过 credentials 或 token_env 配置的条目,其身份在设置了 uuid 时为该值,否则为 name;只要该身份与另一别名显式的 uuid 或名称回退身份相等,就会与之合并——为清晰、有意的合并,请在两个条目上设置匹配且非空的 uuid(当某个显式 uuid 意外匹配另一账户的名称回退身份时,shunt 也会发出警告)。 |
threshold |
否 | [0.0, 1.0] 范围内的按账户软配额阈值,适用于所有没有按窗口取值的窗口。较低的取值把该账户标记为提前轮换出去的后备账户。 |
threshold_5h / threshold_7d / threshold_fable |
否 | 按窗口的软阈值;各自在其窗口上优先于 threshold。 |
max_utilization |
否 | [0.0, 1.0] 范围内的按账户硬上限,适用于所有没有按窗口取值的窗口。在某个起作用的窗口里使用率达到或超过它的账户会被移出选择(见硬上限)。没有 [server.pool] 时同样生效。 |
max_utilization_5h / max_utilization_7d / max_utilization_fable |
否 | 按窗口的硬上限;各自在其窗口上优先于 max_utilization。Fable 上限只作用于 Fable 模型请求。 |
priority |
否 | 粘性账户不健康时的选择优先级;数值越低越优先,默认 100。 |
disabled |
否 | true 把该账户完全移出选择,同时保留在配置和管理仪表板上。 |
不要在同一个账户上同时设置 credentials 和 token_env。
选择与主动轮换
- 带
x-claude-code-session-id时:一个稳定的哈希选出粘性账户。如果该账户可用且低于切换阈值,shunt 会让它保持在首位。 - 不带该头部、或该头部为空值时:每个提供方有自己的轮询计数器。空值不是一次对话,因此它共享这个轮询,而不会被哈希到某个固定账户上。
- 在
claude_oauth账户池处理的每个上游响应上,shunt 会记录以下头部(如存在):anthropic-ratelimit-unified-5h-utilization、anthropic-ratelimit-unified-7d-utilization和anthropic-ratelimit-unified-7d_oi-utilization;anthropic-ratelimit-unified-5h-reset、anthropic-ratelimit-unified-7d-reset和anthropic-ratelimit-unified-7d_oi-reset(Unix 秒);以及anthropic-ratelimit-unified-status、anthropic-ratelimit-unified-5h-status、anthropic-ratelimit-unified-7d-status和anthropic-ratelimit-unified-7d_oi-status。
- 默认切换阈值是
0.98。当 5 小时或共享7dstatus 为rejected时,该账户对所有模型都被视为接近配额。当7d_oistatus 为rejected时,该账户对 Fable 请求被视为接近配额。当共享 5 小时使用率达到阈值,或适用的每周使用率达到阈值时,同样视为接近配额。status 的拒绝判定不取决于对应使用率头部是否存在。计算使用率阈值和余量时,Fable 模型在7d_oi使用率存在时使用它,否则使用共享7d。聚合 status 仅在没有记录任何按窗口 status 时作为兼容回退使用。阈值可以按账户(上文的threshold*字段)或池级(见调优选择)调低。 - 5 小时桶适用于所有模型。Fable 模型 id 在
7d_oi周桶使用率存在时使用它,否则回退到共享7d。其他所有模型家族使用共享7d;由于目前没有 Sonnet 专属头部,Sonnet 也使用7d。 - 接近配额、处于冷却中或被禁用的粘性账户会被主动轮换掉。shunt 优先选择低于阈值的可用账户,先按
priority(数值低者优先)排序,再按起决定作用的周桶最早重置的顺序排列,先花掉“不用即失”的配额。周重置未知的账户排在最前。随后是可用但接近配额的账户,再后是按最快恢复排序的冷却中账户。配置了[server.pool]时,燃烧率余量会取代周重置这一次级排序依据(见下文)。 - 本地配额和冷却状态不会让尝试顺序变空:即使所有账户都接近配额或在冷却中,每个非
disabled且未暂停的账户仍留在尝试顺序里。唯一的例外是硬上限(max_utilization*,见硬上限),它会在使用率达到或超过上限期间剔除账户。 - 使用率在自身观测时间上限和该窗口的重置之间较早者到达时过期。只有观测时间上限已过而该窗口的重置仍在未来时,shunt 只清除该窗口的使用率及其时间戳,并保留该未来重置作为独立元数据。按窗口 status 在自身观测时间上限和观测该 status 时捕获的重置边界之间较早者到达时过期;status 清除时也会清除其捕获边界。后续使用量更新和 reset-only 更新不会移动已捕获的 status 边界。该窗口的重置经过后会清除该窗口的使用率和重置;捕获的 status 边界也经过后才清除 status。带时间戳的聚合 status 有自己的寿命,不会因无关窗口过期而清除;只有 legacy unstamped aggregate 遵循窗口过期。非 Fable 的成功响应只清除账户级冷却,保留 Fable 专属冷却;Fable 的成功响应会同时清除两种冷却。
池的选择、冷却和配额状态在进程存活期间跨配置热重载保留。如果主动轮换无法避开上游限制,被动故障转移仍然生效。
调优选择([server.pool])
可选的 [server.pool] 表(issue #135)在上述行为之上增加按窗口的软阈值与感知燃烧率(burn-rate)的排序。没有该表时,选择逻辑使用单一的内置 0.98 阈值,与之前完全一致。
[server.pool]
# hard_threshold = 0.98 # (默认)兜底;达到或超过时始终排在最后
default_threshold = 0.9 # 所有窗口的软默认值
default_threshold_5h = 0.95 # 按窗口覆盖
default_threshold_fable = 0.85
burn_rate_avoidance = true # 避开按预测会在重置前触及阈值的账户
usage_refresh_seconds = 300 # 为可刷新账户校正带外用量
state_path = "shunt-state.json" # 跨重启保留配额(热启动)
ramp_initial_concurrency = 2 # 风暴控制:对刚切换过来的账户逐步放行(slow-start)
[[providers.anthropic.accounts]]
name = "primary"
priority = 1 # 粘性账户不健康时优先选择
[[providers.anthropic.accounts]]
name = "backup"
threshold = 0.5 # 后备:配额用掉一半即轮换出去
[[providers.anthropic.accounts]]
name = "spare"
disabled = true # 保留配置,但永不被选中- 阈值解析。对每个窗口
X(5h、7d、fable),生效的软阈值为:账户threshold_X→ 账户threshold→default_threshold_X→default_threshold→hard_threshold,并以hard_threshold为上限。所有取值都是[0.0, 1.0]范围内的使用率分数;超出范围会使shunt check失败。 - 燃烧率余量。根据每个窗口的使用率与重置时刻(窗口长度固定为 5 小时和 7 天),shunt 按观测到的平均速度预测触及软阈值所需的时间,再减去窗口重置所需的时间。余量为正意味着按当前速度该账户能撑到重置。
priority相同的可用账户按余量最大者优先排序;未观测到的窗口按无限余量计。 - 预测性规避。设置
burn_rate_avoidance = true时,预测余量为负的账户被视为接近配额,在真正触及阈值之前就被轮换掉。默认关闭 —— 而按余量排序始终生效。 - 全员接近配额的兜底。当每个账户都超过了软阈值(或被预测将耗尽)时,池不会变空:接近配额的账户按最佳余量的顺序继续服务,而达到或超过
hard_threshold的账户仍排在最后,其后才是冷却中的账户。 - 适用范围。这些配额旋钮作用于两个池家族: 本池依据
anthropic-ratelimit-unified-*头部,Codex 池依据上报的x-codex-*5h/7d 窗口(issue #195)。Codex 没有 Fable 专属的7d_oi窗口,因此default_threshold_fable在那里不起作用;priority和disabled在所有池中都适用。 - 管理池端点(
GET /admin/api/pool)会报告每个账户的priority、disabled标志,以及在配置了[server.pool]时该账户当前以秒为单位的余量预测;仪表板的状态列会标记被禁用的账户。
硬上限(max_utilization)
软阈值只对账户重新排序。硬上限则在使用率达到或超过上限期间,把账户移出选择。
[server.pool]
default_max_utilization = 0.95 # 所有窗口,除非被覆盖
default_max_utilization_5h = 0.9 # 按窗口的默认值
[[providers.anthropic.accounts]]
name = "primary"
max_utilization_fable = 0.5 # Fable 用到该账户 Fable 周桶约 50% 即停止,Opus 继续使用- 键。按账户:
max_utilization、max_utilization_5h、max_utilization_7d、max_utilization_fable。在[server.pool]中:default_max_utilization、default_max_utilization_5h、default_max_utilization_7d、default_max_utilization_fable。取值必须在[0.0, 1.0]内,其他取值会使启动和shunt check失败。 - 解析顺序。对每个窗口:账户
max_utilization_X→ 账户max_utilization→default_max_utilization_X→default_max_utilization→ 无上限。上限不受hard_threshold钳制。账户上限在没有[server.pool]时同样生效;通过存储扫描得到的账户(没有[[providers.anthropic.accounts]]条目)只获得池默认值。 - 剔除,而非重新排序。在某个起作用的窗口里已观测到的使用率达到或超过其上限的账户会被移出选择。5h 与 7d 上限作用于每个请求;Fable 上限只作用于 Fable 模型请求,且只有在该账户的
7d_oi使用率已被观测到之后才起作用。没有观测到使用率的窗口从不剔除账户。 - 恢复。使用率降到上限以下后,账户会自动重新可选:途径是窗口重置或过期、更低的观测值,或配置重载时调高上限。上限只阻止新的路由,并不是精确的预算,也不会为已分派的请求预留用量,因此进行中或并发的请求可能超出上限,超出量取决于它们实际消耗的量。
- 耗尽。当上限使池中没有可选账户时,shunt 返回网关自身生成的 HTTP
429rate_limit_error(“all selectable accounts for provider ‘X’ are at their max_utilization cap for this request”),在已知最早可用时间时附带retry-after。与上游429一样,它会推进[[upstreams]]故障转移链,因此后面的上游(可能是按量付费)会处理该请求;只有都无法处理时客户端才会看到这个429。但对于已提交到 Codex/ChatGPT 池(已发送 HTTP200)的流式请求,若后面的上游都无法处理,客户端收到的是流内的终止 SSEerror事件,而不是 HTTP429,也没有retry-after头。在入站 Codex 端点上,同一错误以 OpenAI 错误形态返回。 - 提供商。对 Claude OAuth 池(5h、7d、
7d_oi)和 Codex/ChatGPT 池(5 小时与周窗口;Fable 的键在那里不起作用)有效。Kimi OAuth 池在 Kimi 上游报告使用率头时同样适用上限;目前尚未观测到其报告,因此实际上保持不起作用,未观测到的窗口从不剔除账户。Antigravity 的配额不计入上限。 - 状态显示。管理仪表板把达到上限的账户显示为 “Capped”,仅 Fable 上限则显示 “Capped (Fable)”,并把已配置的各上限标注在账户的使用量条和池表格中。
GET /admin/api/pool按账户报告解析后的max_utilization_5h、max_utilization_7d、max_utilization_fable(该窗口没有生效上限时为null)。当没有可用账户且至少有一个已启用(非disabled)且未暂停的账户达到上限时,GET /usage报告池状态capped。
Usage-API 对账
配额头部只反映流经 shunt 的流量。usage_refresh_seconds 通过轮询 GET /api/oauth/usage,把权威的使用率、每个报告窗口自身的重置时刻和使用率观测时间应用到同样的 5 小时、共享周(7d)和 Fable 专用周(7d_oi)窗口,从而弥补这一差距。
字段未设置或为 0 时轮询关闭;低于 60 的正值会被取整到 60 秒。只有 imported 的可刷新账户符合条件,长期 claude setup-token 与 token_env 账户会被跳过,因为其令牌无法调用该端点。间隔在启动时固定,因此配置重载不会启动、停止或重新调整轮询器。这一周期性校正会更新每个窗口的用量、重置时刻和用量观测时间;只有按窗口及聚合 status 的新鲜度,以及观测 status 时捕获的重置边界仍由头部驱动。
配额状态持久化
迁移时,没有 observed_at_status 的聚合 status 会捕获已保存的 reset_5h、reset_7d、reset_7d_oi 中最早的重置作为不可变期限。若该重置已经过去,则在同一次 import 中同时删除已过期的重置、无时间戳的聚合 status 及其合成时间戳。超过合理七天范围的未来重置会保守地限制在启动时间加七天;没有重置时则从启动时间开始七天上限。已有 v2 时间戳不会根据重置重新解释,但正常 import 仍会规范化孤立元数据、使已过去的信号失效、将未来时间钳制到启动时间,并在必要时为仍存活且无时间戳的聚合补上启动时间。后续 reset-only 或 usage 更新不会延长期限,重写为 v3 并第二次恢复后结果仍保持等价。
池的配额存在内存中,因此重启会从冷状态开始:每个账户在重启后首个响应之前都显示为未观测,这会禁用 burn-rate 规避,并使 GET /usage 在流量重新填充池之前返回空值。设置 state_path 会把每个账户的按窗口使用率与重置保存到该文件,使池从最后观测到的状态热启动。
该文件是尽力而为的缓存,而非权威来源 —— 配额无论如何都会从上游响应重新导出,因此文件缺失、陈旧或损坏只会导致冷启动,绝不会导致启动失败。写入使用私有 temp 文件(Unix 上为 0600)并将其原子重命名覆盖目标,且仅在配额变化时按 15 秒后台定时器进行。写入失败时会保持 dirty 状态,并在下一个 tick 重试。冷却不会被持久化(重启即失效),恢复的窗口中重置已过期的会在恢复时的 import 阶段、首次选择或 snapshot 之前丢弃。使用率和按窗口 status 具有独立的无重置寿命;使用率过期后该窗口的未来重置仍可保留,而捕获的 status 重置会随 status 一起清除。版本2文件通过迁移路径重写为版本3;版本3的无重置 status 在仅重置更新后仍保持无重置。路径在启动时固定;字段未设置时持久化关闭。
故障转移规则
| 响应 | 行为 |
|---|---|
| 2xx | 中继并标记为健康。 |
Fable 请求上仅 anthropic-ratelimit-unified-7d_oi-status: rejected 的 429 |
Fable 配额耗尽:按数值 retry-after 施加仅对 Fable 请求生效的冷却(默认 60 秒,钳制到 1–3600 秒),然后轮换。非 Fable 请求仍可使用该账户。对于非 Fable 请求,同样仅拒绝 7d_oi 的响应会触发账户级冷却。 |
429 且 anthropic-ratelimit-unified-5h-status: rejected 或 -7d-status: rejected |
共享配额耗尽:按数值 retry-after 施加账户级冷却(默认 60 秒,钳制到 1–3600 秒),然后对所有模型轮换。即使 7d_oi 同时被 rejected,仍是账户级冷却。 |
| 普通 429 | 瞬时限流:按数值 retry-after 等待(默认 1 秒,上限 300 秒),对同一账户重试一次,然后中继该重试的响应。 |
使用 credentials 时的 401 |
强制刷新,对同一账户重试一次;若仍是 401,冷却 5 分钟并轮换。 |
使用 token_env 或存储管理的 setup token 时的 401 |
无法刷新:冷却 5 分钟并轮换。 |
| 5xx | 冷却 30 秒并轮换。 |
| 传输失败(没有响应,例如连接断开或 TLS 错误) | 向同一账户重新发送一次,在限流重试或刷新重试中也是如此;只有重发再次出现传输失败时,才冷却 30 秒并轮换。超时不会重发。重发会完整地重新 POST,因此在少数情况下同一轮会执行两次。 |
| 其他状态 | 直接中继,不做故障转移。 |
分类发生在响应体开始流式传输之前,因此流中途的失败绝不会被重放。如果池在收到响应后耗尽了尝试,客户端会得到最后一个真实的上游状态和响应体。如果所有账户在收到任何上游响应之前都失败了,shunt 返回一个网关自身的错误。
路由到 Anthropic 的 POST /v1/messages/count_tokens 请求使用同一个池。
让 Claude Sonnet 5.5 对话保持在同一个账户上
Anthropic 会把 Claude Sonnet 5.5 生成的 thinking 块绑定到生成它们的账户(Preserved thinking)。当另一个账户把这样的块发回时,API 会在模型看到之前丢弃它。请求仍然成功,但模型在没有那段先前推理的情况下作答。更早模型的 thinking 块不受影响。
池具有会话粘性,因此一次对话通常停留在一个账户上。在以下情况下它会转到另一个账户:
- 粘性账户接近配额、处于冷却中或被禁用,于是主动轮换把会话从该账户移走。
- 某个响应按照故障转移规则触发轮换:配额 429、刷新无法解决的 401(setup token 无法刷新,因此其上的任何 401 都会轮换),5xx,或者在立即重发后仍然出现的传输失败。
- 池中不同账户的数量发生变化,例如用
shunt login claude新增的存储条目会在不重启的情况下进入池。粘性账户是按池中不同账户的数量对会话 ID 取哈希选出的,因此大多数现有会话会被分配到另一个账户。 - 请求不带
x-claude-code-session-id,因此使用轮询。
转移之后,后续的 Sonnet 5.5 轮次会在没有前一个账户上产生的推理的情况下运行。不会有任何失败。shunt 不会添加 thinking-binding-controls-2026-08-01 beta 头部,因此除非客户端自己发送该头部,响应不会报告这次丢弃(shunt 会保留客户端的 anthropic-beta 值,仅在缺少 oauth-2025-04-20 时追加它)。shunt 也不会记录这次丢弃。
如果你希望每个 Sonnet 5.5 请求都落在同一个账户上,请为该模型配置一个限定到该账户的专用上游。账户限定使用有序的 [[upstreams]] 形式(见配置中的 [[upstreams]] 一节),它不能与 [providers.*] 在同一个配置文件中混用。用 shunt login claude 存储账户,并按名称引用它们:
[server]
default_provider = "anthropic"
# 最先声明,因此对映射到它的每个模型都最先尝试。
[[upstreams]]
name = "anthropic-sonnet55"
provider = "anthropic"
auth = { mode = "claude_oauth", account = "primary" }
# 其他所有模型:整个 Claude 存储,按通常方式轮换。
[[upstreams]]
name = "anthropic"
provider = "anthropic"
auth = "claude_oauth"
[[models]]
id = "claude-sonnet-5-5"
[models.upstream_model]
anthropic-sonnet55 = "claude-sonnet-5-5"
anthropic = "claude-sonnet-5-5" # 如需严格固定,删除此行Sonnet 5.5 请求首先发往 primary。当模型映射中有 anthropic 这一行时,primary 上推进链路的失败(例如配额 429 或 5xx)会落到整个池。每一轮只能使用处理该轮的账户所产生的 Sonnet 5.5 推理,因此由另一个账户处理的轮次会在没有 primary 上产生的推理的情况下运行。由于使用整个存储的池也包含 primary,它也可能再次尝试 primary,此时 primary 上产生的推理会被保留。只有当池尝试的每个账户都失败时,请求才会失败。没有这一行时,固定是严格的:客户端会收到 primary 的错误,而不会有任何东西转移该对话。
请记住以下权衡:
- 每个 Sonnet 5.5 会话都消耗同一个账户的 5 小时配额和每周配额。
primary也仍留在扫描整个存储的anthropic池中。若要把它保留给 Sonnet 5.5,请改为限定该池,例如auth = { mode = "claude_oauth", accounts = ["backup", "team-2"] }。- 配额、冷却和刷新状态属于账户,而不属于上游。在两个上游中都列出的存储账户共享同一份状态和同一把刷新锁。当 shunt 无法识别内联账户表时,即它没有
uuid、其凭据中也没有shuntAccountUuid时,它按上游分别建键。在两个上游中内联重复这样的凭据会拆分其状态和刷新锁,因此请改为按名称引用存储账户。 shunt check不会验证限定的名称是否存在于存储中。缺失的名称会在请求解析该池时失败。
请求与响应的改动
对所选账户,shunt 将客户端认证替换为:
Authorization: Bearer <selected OAuth token>
anthropic-beta: ...,oauth-2025-04-20它会移除传入的 authorization 和 x-api-key,仅在缺失时追加 oauth-2025-04-20,并保留其他端到端头部。
经过池的响应会标识账户:
x-shunt-account: backup在共享网关上请使用中性的账户名。该头部会把配置的标签暴露给收到响应的每个已授权客户端。池耗尽后对最后一个上游响应的中继会省略 x-shunt-account。
account_uuid
Claude Code 可能把账户元数据以 JSON 形式编码在字符串值的 metadata.user_id 里。如果所选账户有 uuid,shunt 会用该值替换已存在的内部 account_uuid。若元数据缺失、格式错误、缺少 account_uuid,或所选账户没有 UUID,则请求体保持原样。它不会注入缺失的元数据。
安全约束
claude_oauth 仅在以下条件下被接受:
- 提供方的
kind = "anthropic"; base_url使用 HTTPS;且- 其主机是
anthropic.com或诸如api.anthropic.com的子域。
这些启动检查防止 OAuth bearer 被发送到源之外或以明文传输。HTTPS 与主机检查在回环主机上放宽(localhost、127.0.0.1、[::1] 等):回环的 base_url 可以使用纯 HTTP 和任意主机,这样本地调试代理或 mock 能接收流量 —— bearer 无法离开运营者的机器。非回环主机始终要求 HTTPS + anthropic.com。在共享部署上,还应配置 [server.auth],因为 claude_oauth 花费的是网关自有的凭据。客户端随后可以用它已经在发送的 ANTHROPIC_AUTH_TOKEN 完成认证(客户端 token 除 x-shunt-token、x-api-key 外,也接受 Authorization: Bearer)—— 在仅池化的网关上不需要 ANTHROPIC_CUSTOM_HEADERS 行。
风暴控制(storm control)
设置 [server.pool] ramp_initial_concurrency(默认关闭)会按账户身份以 slow-start 逐步放行(ramp)并发准入,这样一次故障转移切换就不会用所有在途请求同时冲垮刚选中的账户。刚开始承接流量的身份最多准入所配置数量的并发请求;每次成功响应把额度翻倍,一次故障转移会重启该 ramp,被拒绝的请求则顺延到选择顺序中的下一个账户(最后一个候选始终会被尝试)。参见 [server.pool]。
实现行为参考了 KarpelesLab/teamclaude 与随产品发布的 Claude Code 二进制。shunt 对 teamclaude 没有运行时依赖。