feat(gateway): harden provider request execution

Preserve exact request payloads and model client surface and API operation explicitly.

Add Anthropic compatibility profiles, bounded stream commitment, and scoped OAuth retry behavior across provider transports.
This commit is contained in:
elky
2026-07-27 09:36:31 +08:00
parent 79b70f7b5c
commit 531cf11025
152 changed files with 13984 additions and 2075 deletions
@@ -2,149 +2,19 @@ use aether_contracts::{
TRANSPORT_BACKEND_REQWEST_RUSTLS, TRANSPORT_HTTP_MODE_AUTO, TRANSPORT_POOL_SCOPE_KEY,
};
use serde_json::{Map, Value};
use sha2::{Digest, Sha256};
use uuid::Uuid;
// Chrome impersonate profiles
const CHROME_IMPERSONATE_PROFILES: &[&str] = &[
"chrome110",
"chrome116",
"chrome119",
"chrome120",
"chrome123",
"chrome124",
"chrome131",
"chrome133",
];
use super::profile::current_claude_code_transport_identity_profile;
const CHROME_VERSIONS: &[(&str, &str)] = &[
("chrome110", "110.0.5481.177"),
("chrome116", "116.0.5845.188"),
("chrome119", "119.0.6045.214"),
("chrome120", "120.0.6099.216"),
("chrome123", "123.0.6312.122"),
("chrome124", "124.0.6367.243"),
("chrome131", "131.0.6778.265"),
("chrome133", "133.0.6943.142"),
];
// (os, arch, platform_token, platform_info)
const PLATFORM_VARIANTS: &[(&str, &str, &str, &str)] = &[
("Linux", "x64", "X11; Linux x86_64", "Linux x86_64"),
("Linux", "arm64", "X11; Linux arm64", "Linux arm64"),
(
"Windows",
"x64",
"Windows NT 10.0; Win64; x64",
"Windows x64",
),
(
"MacOS",
"x64",
"Macintosh; Intel Mac OS X 10_15_7",
"Darwin x64",
),
(
"MacOS",
"arm64",
"Macintosh; ARM Mac OS X 14_0_0",
"Darwin arm64",
),
];
const STAINLESS_PACKAGE_VERSIONS: &[&str] = &["0.68.0", "0.69.0", "0.70.0", "0.71.0"];
const NODE_VERSIONS: &[&str] = &["v20.18.1", "v22.12.0", "v22.14.0", "v24.13.0"];
const ELECTRON_VERSIONS: &[&str] = &["35.5.1", "36.7.1", "37.3.0", "38.7.0", "39.2.3"];
const STAINLESS_TIMEOUTS: &[&str] = &["600", "900"];
const CLAUDE_CODE_TRANSPORT_PROFILE_ID: &str = "claude_code_nodejs";
/// Deterministic hash-based index picker, compatible with Python implementation.
/// Each `slot` produces a different selection from the same seed.
struct SeededPicker {
seed_bytes: [u8; 32],
}
impl SeededPicker {
fn new(seed: &str) -> Self {
let mut hasher = Sha256::new();
hasher.update(seed.as_bytes());
Self {
seed_bytes: hasher.finalize().into(),
}
}
/// Pick an index from `[0, len)` using a specific slot.
/// Different slots produce independent-looking selections from the same seed.
fn pick(&self, slot: u8, len: usize) -> usize {
if len == 0 {
return 0;
}
// Hash seed_bytes + slot to get a new digest, take first 8 bytes as u64
let mut hasher = Sha256::new();
hasher.update(self.seed_bytes);
hasher.update([slot]);
let hash = hasher.finalize();
let value = u64::from_be_bytes(hash[..8].try_into().unwrap());
(value % len as u64) as usize
}
}
fn chrome_version_for_profile(profile: &str) -> &'static str {
for (p, v) in CHROME_VERSIONS {
if p.eq_ignore_ascii_case(profile) {
return v;
}
}
"120.0.6099.216"
}
fn build_user_agent(platform_token: &str, chrome_version: &str, electron_version: &str) -> String {
format!(
"Mozilla/5.0 ({platform_token}) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/{chrome_version} Electron/{electron_version} Safari/537.36"
)
}
fn resolve_platform_token(os: &str, arch: &str) -> &'static str {
let os_lower = os.to_ascii_lowercase();
let arch_lower = arch.to_ascii_lowercase();
if os_lower.starts_with("win") {
return "Windows NT 10.0; Win64; x64";
}
if matches!(os_lower.as_str(), "darwin" | "mac" | "macos") {
return if matches!(arch_lower.as_str(), "arm64" | "aarch64") {
"Macintosh; ARM Mac OS X 14_0_0"
} else {
"Macintosh; Intel Mac OS X 10_15_7"
};
}
if matches!(arch_lower.as_str(), "arm64" | "aarch64") {
"X11; Linux arm64"
} else {
"X11; Linux x86_64"
}
}
/// Generate a complete Claude Code transport fingerprint from a seed.
/// Generate the transport-profile metadata and per-key pool partition from a
/// stable key seed. HTTP identity headers are owned by the versioned profile
/// and are not overridden from this stored value.
pub fn generate_fingerprint(seed: &str) -> Value {
wrap_header_fingerprint(generate_header_fingerprint(seed))
}
fn generate_header_fingerprint(seed: &str) -> Value {
let picker = SeededPicker::new(seed);
let impersonate =
CHROME_IMPERSONATE_PROFILES[picker.pick(0, CHROME_IMPERSONATE_PROFILES.len())];
let chrome_version = chrome_version_for_profile(impersonate);
let node_version = NODE_VERSIONS[picker.pick(1, NODE_VERSIONS.len())];
let electron_version = ELECTRON_VERSIONS[picker.pick(2, ELECTRON_VERSIONS.len())];
let platform = PLATFORM_VARIANTS[picker.pick(3, PLATFORM_VARIANTS.len())];
let (stainless_os, stainless_arch, platform_token, platform_info) = platform;
let stainless_package_version =
STAINLESS_PACKAGE_VERSIONS[picker.pick(4, STAINLESS_PACKAGE_VERSIONS.len())];
let stainless_timeout = STAINLESS_TIMEOUTS[picker.pick(5, STAINLESS_TIMEOUTS.len())];
let profile = *current_claude_code_transport_identity_profile();
let vscode_session_id = Uuid::new_v5(
&Uuid::NAMESPACE_URL,
format!("aether:fingerprint:{seed}").as_bytes(),
@@ -152,32 +22,36 @@ fn generate_header_fingerprint(seed: &str) -> Value {
.simple()
.to_string();
let user_agent = build_user_agent(platform_token, chrome_version, electron_version);
serde_json::json!({
"impersonate": impersonate,
"stainless_package_version": stainless_package_version,
"stainless_os": stainless_os,
"stainless_arch": stainless_arch,
"stainless_runtime_version": node_version,
"stainless_timeout": stainless_timeout,
"node_version": node_version,
"chrome_version": chrome_version,
"electron_version": electron_version,
"identity_profile_version": profile.version().as_str(),
"cli_version": profile.cli_version(),
"billing_cli_version": profile.billing_cli_version(),
"stainless_lang": profile.stainless_lang(),
"stainless_package_version": profile.stainless_package_version(),
"stainless_os": profile.stainless_os(),
"stainless_arch": profile.stainless_arch(),
"stainless_runtime": profile.stainless_runtime(),
"stainless_runtime_version": profile.stainless_runtime_version(),
"stainless_retry_count": profile.stainless_retry_count(),
"stainless_timeout": profile.stainless_timeout(),
"vscode_session_id": vscode_session_id,
"platform_info": platform_info,
"user_agent": user_agent,
"user_agent": profile.user_agent(),
})
}
fn wrap_header_fingerprint(header_fingerprint: Value) -> Value {
let profile = *current_claude_code_transport_identity_profile();
serde_json::json!({
"transport_profile": {
"profile_id": CLAUDE_CODE_TRANSPORT_PROFILE_ID,
"profile_id": profile.transport_profile_id(),
"backend": TRANSPORT_BACKEND_REQWEST_RUSTLS,
"http_mode": TRANSPORT_HTTP_MODE_AUTO,
"pool_scope": TRANSPORT_POOL_SCOPE_KEY,
"header_fingerprint": header_fingerprint,
"extra": {
"claude_code_identity_profile_version": profile.version().as_str(),
"claude_code_cli_version": profile.cli_version(),
}
}
})
}
@@ -192,82 +66,31 @@ pub fn header_fingerprint_from_fingerprint(fingerprint: &Value) -> Option<&Map<S
/// Generate a random (non-deterministic) fingerprint.
pub fn generate_random_fingerprint() -> Value {
let random_seed = Uuid::new_v4().to_string();
generate_fingerprint(&random_seed)
generate_fingerprint(&Uuid::new_v4().to_string())
}
/// Sanitize an existing fingerprint JSON, filling missing fields with
/// deterministic fallbacks derived from `key_id`.
/// Upgrade stored transport metadata to the current typed identity profile.
/// Only the per-key pool partition (kept under its legacy session-id field) is
/// retained; fixed CLI and Stainless values remain one coherent version set.
pub fn sanitize_fingerprint(raw: &Value, key_id: &str) -> Value {
let generated = generate_header_fingerprint(key_id);
let gen_map = generated.as_object().unwrap();
let raw_map = header_fingerprint_from_fingerprint(raw);
let mut generated = generate_header_fingerprint(key_id);
let Some(generated) = generated.as_object_mut() else {
return generate_fingerprint(key_id);
};
let mut out = Map::new();
// Start with generated values, then overlay non-empty raw values
for (key, gen_value) in gen_map {
let value = raw_map
.and_then(|raw_map| raw_map.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|v| !v.is_empty())
.map(|v| Value::String(v.to_string()))
.unwrap_or_else(|| gen_value.clone());
out.insert(key.clone(), value);
}
// Normalize impersonate to known profile
let impersonate = out
.get("impersonate")
.and_then(Value::as_str)
.unwrap_or_default()
.to_ascii_lowercase();
let is_known = CHROME_IMPERSONATE_PROFILES
.iter()
.any(|p| p.eq_ignore_ascii_case(&impersonate));
if !is_known {
out.insert("impersonate".to_string(), gen_map["impersonate"].clone());
}
// Ensure chrome_version matches impersonate profile
let profile = out
.get("impersonate")
.and_then(Value::as_str)
.unwrap_or_default();
let chrome_version = chrome_version_for_profile(profile);
out.insert(
"chrome_version".to_string(),
Value::String(chrome_version.to_string()),
);
// Rebuild user_agent if missing
let has_ua = out
.get("user_agent")
if let Some(session_id) = header_fingerprint_from_fingerprint(raw)
.and_then(|raw| raw.get("vscode_session_id"))
.and_then(Value::as_str)
.map(str::trim)
.is_some_and(|v| !v.is_empty());
if !has_ua {
let os = out
.get("stainless_os")
.and_then(Value::as_str)
.unwrap_or("Linux");
let arch = out
.get("stainless_arch")
.and_then(Value::as_str)
.unwrap_or("x64");
let electron = out
.get("electron_version")
.and_then(Value::as_str)
.unwrap_or("38.7.0");
let platform_token = resolve_platform_token(os, arch);
out.insert(
"user_agent".to_string(),
Value::String(build_user_agent(platform_token, chrome_version, electron)),
.filter(|value| !value.is_empty())
{
generated.insert(
"vscode_session_id".to_string(),
Value::String(session_id.to_string()),
);
}
wrap_header_fingerprint(Value::Object(out))
wrap_header_fingerprint(Value::Object(generated.clone()))
}
#[cfg(test)]
@@ -282,101 +105,51 @@ mod tests {
}
#[test]
fn different_seeds_produce_different_fingerprints() {
fn different_seeds_produce_different_session_fingerprints() {
let fp1 = generate_fingerprint("key-1");
let fp2 = generate_fingerprint("key-2");
// At least one field should differ (statistically near-certain)
assert_ne!(fp1, fp2);
}
#[test]
fn generated_fingerprint_has_all_fields() {
fn generated_fingerprint_matches_current_identity_profile() {
let fp = generate_fingerprint("test-key");
let expected_keys = [
"impersonate",
"stainless_package_version",
"stainless_os",
"stainless_arch",
"stainless_runtime_version",
"stainless_timeout",
"node_version",
"chrome_version",
"electron_version",
"vscode_session_id",
"platform_info",
"user_agent",
];
let map = header_fingerprint_from_fingerprint(&fp).unwrap();
for key in expected_keys {
assert!(map.contains_key(key), "missing field: {key}");
let value = map[key].as_str().unwrap();
assert!(!value.is_empty(), "empty field: {key}");
}
}
let map = header_fingerprint_from_fingerprint(&fp).expect("header fingerprint");
#[test]
fn sanitize_preserves_user_overrides() {
let raw = serde_json::json!({
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"stainless_os": "MacOS",
"stainless_arch": "arm64",
"stainless_timeout": "900",
"user_agent": "Custom-Agent/1.0"
}
}
});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let map = header_fingerprint_from_fingerprint(&sanitized).unwrap();
assert_eq!(map["stainless_os"].as_str(), Some("MacOS"));
assert_eq!(map["stainless_arch"].as_str(), Some("arm64"));
assert_eq!(map["stainless_timeout"].as_str(), Some("900"));
assert_eq!(map["user_agent"].as_str(), Some("Custom-Agent/1.0"));
// Other fields should be filled from generation
assert!(map.contains_key("impersonate"));
assert!(map.contains_key("stainless_package_version"));
}
#[test]
fn sanitize_fills_missing_fields_from_seed() {
let raw = serde_json::json!({});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let generated = generate_header_fingerprint("test-key");
// All fields should match generated since raw is empty
let s = header_fingerprint_from_fingerprint(&sanitized).unwrap();
let g = generated.as_object().unwrap();
for key in g.keys() {
assert!(s.contains_key(key), "sanitized missing key: {key}");
assert!(
!s[key].as_str().unwrap().is_empty(),
"sanitized empty key: {key}"
);
}
}
#[test]
fn sanitize_normalizes_unknown_impersonate_profile() {
let raw = serde_json::json!({
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"impersonate": "firefox99"
}
}
});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let profile = header_fingerprint_from_fingerprint(&sanitized).unwrap()["impersonate"]
.as_str()
.unwrap();
assert!(
CHROME_IMPERSONATE_PROFILES
.iter()
.any(|p| p.eq_ignore_ascii_case(profile)),
"should normalize to known profile, got: {profile}"
assert_eq!(map["identity_profile_version"], "2026-04");
assert_eq!(map["cli_version"], "2.1.161");
assert_eq!(map["billing_cli_version"], "2.1.161");
assert_eq!(map["stainless_package_version"], "0.94.0");
assert_eq!(map["stainless_runtime_version"], "v24.3.0");
assert_eq!(map["user_agent"], "claude-cli/2.1.161 (external, cli)");
assert_eq!(
fp["transport_profile"]["extra"]["claude_code_identity_profile_version"],
"2026-04"
);
}
#[test]
fn sanitize_upgrades_stale_identity_as_one_version_set() {
let raw = serde_json::json!({
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"stainless_package_version": "0.68.0",
"stainless_runtime_version": "v20.18.1",
"user_agent": "Mozilla/5.0 stale",
"vscode_session_id": "existing-session"
}
}
});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let map = header_fingerprint_from_fingerprint(&sanitized).expect("header fingerprint");
assert_eq!(map["stainless_package_version"], "0.94.0");
assert_eq!(map["stainless_runtime_version"], "v24.3.0");
assert_eq!(map["user_agent"], "claude-cli/2.1.161 (external, cli)");
assert_eq!(map["vscode_session_id"], "existing-session");
}
#[test]
fn random_fingerprint_differs_each_call() {
let fp1 = generate_random_fingerprint();
@@ -1,6 +1,7 @@
mod auth;
mod fingerprint;
mod policy;
mod profile;
mod request;
mod url;
@@ -13,5 +14,13 @@ pub use policy::{
local_claude_code_transport_unsupported_reason_with_network,
supports_local_claude_code_transport_with_network,
};
pub use request::{build_claude_code_passthrough_headers, sanitize_claude_code_request_body};
pub use profile::{
current_claude_code_transport_identity_profile, ClaudeCodeBodyCapabilityGate,
ClaudeCodeTransportIdentityProfile, ClaudeCodeTransportIdentityProfileVersion,
CLAUDE_CODE_CONTEXT_MANAGEMENT_BETA, CLAUDE_CODE_TRANSPORT_IDENTITY_2026_04,
};
pub use request::{
build_claude_code_passthrough_headers, sanitize_claude_code_request_body,
sanitize_claude_code_request_body_for_beta_header,
};
pub use url::build_claude_code_messages_url;
@@ -0,0 +1,322 @@
use std::collections::{BTreeMap, BTreeSet};
use aether_ai_formats::ApiOperation;
pub const CLAUDE_CODE_CONTEXT_MANAGEMENT_BETA: &str = "context-management-2025-06-27";
const MESSAGE_BETAS_2026_04: &[&str] = &[
"claude-code-20250219",
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
"prompt-caching-scope-2026-01-05",
"effort-2025-11-24",
CLAUDE_CODE_CONTEXT_MANAGEMENT_BETA,
"extended-cache-ttl-2025-04-11",
];
const COUNT_TOKENS_BETAS_2026_04: &[&str] = &[
"claude-code-20250219",
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
"prompt-caching-scope-2026-01-05",
"effort-2025-11-24",
CLAUDE_CODE_CONTEXT_MANAGEMENT_BETA,
"extended-cache-ttl-2025-04-11",
"token-counting-2024-11-01",
];
const DROPPED_BETAS_2026_04: &[&str] = &[];
const BODY_CAPABILITY_GATES_2026_04: &[ClaudeCodeBodyCapabilityGate] =
&[ClaudeCodeBodyCapabilityGate {
body_field: "context_management",
beta_token: CLAUDE_CODE_CONTEXT_MANAGEMENT_BETA,
inject_when_thinking_enabled: true,
default_edit_type: Some("clear_thinking_20251015"),
}];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClaudeCodeTransportIdentityProfileVersion {
V2026_04,
}
impl ClaudeCodeTransportIdentityProfileVersion {
pub const fn as_str(self) -> &'static str {
match self {
Self::V2026_04 => "2026-04",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClaudeCodeBodyCapabilityGate {
pub body_field: &'static str,
pub beta_token: &'static str,
pub inject_when_thinking_enabled: bool,
pub default_edit_type: Option<&'static str>,
}
/// Versioned upstream identity used when Aether is intentionally acting as a
/// Claude Code transport. Native Anthropic transports never resolve this
/// profile and therefore keep their original headers and body untouched.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClaudeCodeTransportIdentityProfile {
version: ClaudeCodeTransportIdentityProfileVersion,
transport_profile_id: &'static str,
anthropic_version: &'static str,
cli_version: &'static str,
stainless_lang: &'static str,
stainless_package_version: &'static str,
stainless_os: &'static str,
stainless_arch: &'static str,
stainless_runtime: &'static str,
stainless_runtime_version: &'static str,
stainless_retry_count: &'static str,
stainless_timeout: &'static str,
message_required_betas: &'static [&'static str],
count_tokens_required_betas: &'static [&'static str],
preserve_incoming_betas: bool,
dropped_betas: &'static [&'static str],
body_capability_gates: &'static [ClaudeCodeBodyCapabilityGate],
}
pub const CLAUDE_CODE_TRANSPORT_IDENTITY_2026_04: ClaudeCodeTransportIdentityProfile =
ClaudeCodeTransportIdentityProfile {
version: ClaudeCodeTransportIdentityProfileVersion::V2026_04,
transport_profile_id: "claude_code_nodejs",
anthropic_version: "2023-06-01",
cli_version: "2.1.161",
stainless_lang: "js",
stainless_package_version: "0.94.0",
stainless_os: "Linux",
stainless_arch: "arm64",
stainless_runtime: "node",
stainless_runtime_version: "v24.3.0",
stainless_retry_count: "0",
stainless_timeout: "600",
message_required_betas: MESSAGE_BETAS_2026_04,
count_tokens_required_betas: COUNT_TOKENS_BETAS_2026_04,
preserve_incoming_betas: true,
dropped_betas: DROPPED_BETAS_2026_04,
body_capability_gates: BODY_CAPABILITY_GATES_2026_04,
};
pub const fn current_claude_code_transport_identity_profile(
) -> &'static ClaudeCodeTransportIdentityProfile {
&CLAUDE_CODE_TRANSPORT_IDENTITY_2026_04
}
impl ClaudeCodeTransportIdentityProfile {
pub const fn version(self) -> ClaudeCodeTransportIdentityProfileVersion {
self.version
}
pub const fn transport_profile_id(self) -> &'static str {
self.transport_profile_id
}
pub const fn cli_version(self) -> &'static str {
self.cli_version
}
pub const fn billing_cli_version(self) -> &'static str {
self.cli_version
}
pub fn user_agent(self) -> String {
format!("claude-cli/{} (external, cli)", self.cli_version)
}
pub const fn stainless_package_version(self) -> &'static str {
self.stainless_package_version
}
pub const fn stainless_lang(self) -> &'static str {
self.stainless_lang
}
pub const fn stainless_os(self) -> &'static str {
self.stainless_os
}
pub const fn stainless_arch(self) -> &'static str {
self.stainless_arch
}
pub const fn stainless_runtime(self) -> &'static str {
self.stainless_runtime
}
pub const fn stainless_runtime_version(self) -> &'static str {
self.stainless_runtime_version
}
pub const fn stainless_retry_count(self) -> &'static str {
self.stainless_retry_count
}
pub const fn stainless_timeout(self) -> &'static str {
self.stainless_timeout
}
pub fn required_beta_tokens(self, operation: Option<ApiOperation>) -> &'static [&'static str] {
if operation == Some(ApiOperation::ClaudeCountTokens) {
self.count_tokens_required_betas
} else {
self.message_required_betas
}
}
pub const fn preserves_incoming_betas(self) -> bool {
self.preserve_incoming_betas
}
pub const fn dropped_beta_tokens(self) -> &'static [&'static str] {
self.dropped_betas
}
pub const fn body_capability_gates(self) -> &'static [ClaudeCodeBodyCapabilityGate] {
self.body_capability_gates
}
pub fn body_capability_gate(self, field: &str) -> Option<ClaudeCodeBodyCapabilityGate> {
self.body_capability_gates
.iter()
.copied()
.find(|gate| gate.body_field == field)
}
pub fn apply_fixed_headers(self, headers: &mut BTreeMap<String, String>, stream: bool) {
for (name, value) in [
("accept", "application/json"),
("anthropic-version", self.anthropic_version),
("anthropic-dangerous-direct-browser-access", "true"),
("x-app", "cli"),
("x-stainless-lang", self.stainless_lang),
(
"x-stainless-package-version",
self.stainless_package_version,
),
("x-stainless-os", self.stainless_os),
("x-stainless-arch", self.stainless_arch),
("x-stainless-runtime", self.stainless_runtime),
(
"x-stainless-runtime-version",
self.stainless_runtime_version,
),
("x-stainless-retry-count", self.stainless_retry_count),
("x-stainless-timeout", self.stainless_timeout),
] {
headers.insert(name.to_string(), value.to_string());
}
headers.insert("user-agent".to_string(), self.user_agent());
if stream {
headers.insert(
"x-stainless-helper-method".to_string(),
"stream".to_string(),
);
} else {
headers.remove("x-stainless-helper-method");
}
}
pub fn apply_beta_policy(
self,
headers: &mut BTreeMap<String, String>,
operation: Option<ApiOperation>,
) {
let incoming = headers.get("anthropic-beta").map(String::as_str);
let merged = self.merge_beta_tokens(incoming, operation);
if merged.is_empty() {
headers.remove("anthropic-beta");
} else {
headers.insert("anthropic-beta".to_string(), merged);
}
}
pub fn merge_beta_tokens(
self,
incoming: Option<&str>,
operation: Option<ApiOperation>,
) -> String {
let mut seen = BTreeSet::new();
let mut merged = Vec::new();
for token in self.required_beta_tokens(operation) {
self.append_beta_token(&mut seen, &mut merged, token);
}
if self.preserve_incoming_betas {
for token in incoming.unwrap_or_default().split(',') {
self.append_beta_token(&mut seen, &mut merged, token);
}
}
merged.join(",")
}
pub fn beta_header_enables_body_field(self, beta_header: &str, field: &str) -> bool {
let Some(gate) = self.body_capability_gate(field) else {
return true;
};
beta_header
.split(',')
.map(str::trim)
.any(|token| token.eq_ignore_ascii_case(gate.beta_token))
}
fn append_beta_token(self, seen: &mut BTreeSet<String>, merged: &mut Vec<String>, token: &str) {
let token = token.trim();
if token.is_empty()
|| self
.dropped_betas
.iter()
.any(|dropped| token.eq_ignore_ascii_case(dropped))
{
return;
}
if seen.insert(token.to_ascii_lowercase()) {
merged.push(token.to_string());
}
}
}
#[cfg(test)]
mod tests {
use super::current_claude_code_transport_identity_profile;
use aether_ai_formats::ApiOperation;
#[test]
fn profile_versions_cli_user_agent_stainless_and_billing_together() {
let profile = *current_claude_code_transport_identity_profile();
assert_eq!(profile.version().as_str(), "2026-04");
assert_eq!(profile.cli_version(), "2.1.161");
assert_eq!(profile.billing_cli_version(), profile.cli_version());
assert_eq!(
profile.user_agent(),
format!("claude-cli/{} (external, cli)", profile.cli_version())
);
assert_eq!(profile.stainless_package_version(), "0.94.0");
assert_eq!(profile.stainless_runtime_version(), "v24.3.0");
}
#[test]
fn profile_preserves_context_1m_and_adds_operation_specific_betas() {
let profile = *current_claude_code_transport_identity_profile();
let messages = profile.merge_beta_tokens(Some("context-1m-2025-08-07,custom"), None);
assert!(messages
.split(',')
.any(|token| token == "context-1m-2025-08-07"));
assert!(messages.split(',').any(|token| token == "custom"));
assert!(!messages
.split(',')
.any(|token| token == "token-counting-2024-11-01"));
let count_tokens = profile.merge_beta_tokens(
Some("context-1m-2025-08-07"),
Some(ApiOperation::ClaudeCountTokens),
);
assert!(count_tokens
.split(',')
.any(|token| token == "token-counting-2024-11-01"));
assert!(profile.dropped_beta_tokens().is_empty());
assert!(profile.preserves_incoming_betas());
}
}
@@ -1,31 +1,15 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::sync::OnceLock;
use regex::Regex;
use serde_json::{Map, Value};
use super::super::auth::build_openai_passthrough_headers;
use super::fingerprint::header_fingerprint_from_fingerprint;
use super::profile::{
current_claude_code_transport_identity_profile, ClaudeCodeTransportIdentityProfile,
};
const DEFAULT_ANTHROPIC_VERSION: &str = "2023-06-01";
const DEFAULT_ACCEPT: &str = "application/json";
const STREAM_HELPER_METHOD: &str = "stream";
const DUMMY_THINKING_SIGNATURE: &str = "skip_thought_signature_validator";
const REQUIRED_BETA_TOKENS: &[&str] = &[
"claude-code-20250219",
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
];
const EXCLUDED_BETA_TOKENS: &[&str] = &["context-1m-2025-08-07"];
/// Fingerprint field -> HTTP header mapping.
/// Every stainless / identity dimension that can vary per-key is listed here.
const FINGERPRINT_HEADER_MAP: &[(&str, &str)] = &[
("stainless_package_version", "x-stainless-package-version"),
("stainless_os", "x-stainless-os"),
("stainless_arch", "x-stainless-arch"),
("stainless_runtime_version", "x-stainless-runtime-version"),
("stainless_timeout", "x-stainless-timeout"),
("user_agent", "user-agent"),
];
pub fn build_claude_code_passthrough_headers(
headers: &http::HeaderMap,
@@ -33,7 +17,6 @@ pub fn build_claude_code_passthrough_headers(
auth_value: &str,
extra_headers: &BTreeMap<String, String>,
stream: bool,
fingerprint: Option<&Value>,
) -> BTreeMap<String, String> {
let mut out = build_openai_passthrough_headers(
headers,
@@ -43,77 +26,58 @@ pub fn build_claude_code_passthrough_headers(
Some("application/json"),
);
// -- Anthropic protocol headers --
out.insert("accept".to_string(), DEFAULT_ACCEPT.to_string());
out.insert(
"anthropic-version".to_string(),
DEFAULT_ANTHROPIC_VERSION.to_string(),
);
// Read incoming anthropic-beta directly from the original HeaderMap because the
// upstream passthrough filter now strips `anthropic-*` headers to avoid leaking
// them to non-Anthropic upstreams.
let incoming_anthropic_beta = headers
// The common passthrough filter intentionally strips Anthropic identity
// headers. Restore only the client beta input; the versioned profile owns
// all fixed identity values and the final beta policy.
let mut incoming_beta_values = headers
.get("anthropic-beta")
.and_then(|value| value.to_str().ok());
out.insert(
"anthropic-beta".to_string(),
merge_anthropic_beta_tokens(incoming_anthropic_beta),
.and_then(|value| value.to_str().ok())
.map(str::trim)
.filter(|value| !value.is_empty())
.into_iter()
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
incoming_beta_values.extend(
extra_headers
.iter()
.filter(|(name, _)| name.eq_ignore_ascii_case("anthropic-beta"))
.map(|(_, value)| value.trim())
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned),
);
out.insert(
"anthropic-dangerous-direct-browser-access".to_string(),
"true".to_string(),
);
out.insert("x-app".to_string(), "cli".to_string());
// -- Stainless SDK identity headers --
// Fixed values: these don't vary per fingerprint.
out.insert("x-stainless-lang".to_string(), "js".to_string());
out.insert("x-stainless-runtime".to_string(), "node".to_string());
out.insert("x-stainless-retry-count".to_string(), "0".to_string());
// Defaults for fingerprint-overridable fields (used when no fingerprint is present).
out.insert(
"x-stainless-package-version".to_string(),
"0.70.0".to_string(),
);
out.insert("x-stainless-os".to_string(), "Linux".to_string());
out.insert("x-stainless-arch".to_string(), "arm64".to_string());
out.insert(
"x-stainless-runtime-version".to_string(),
"v24.13.0".to_string(),
);
out.insert("x-stainless-timeout".to_string(), "600".to_string());
if stream {
out.insert(
"x-stainless-helper-method".to_string(),
STREAM_HELPER_METHOD.to_string(),
);
} else {
out.remove("x-stainless-helper-method");
if !incoming_beta_values.is_empty() {
out.insert("anthropic-beta".to_string(), incoming_beta_values.join(","));
}
// Override from the formal transport profile header fingerprint.
if let Some(fp) = fingerprint.and_then(header_fingerprint_from_fingerprint) {
for &(fp_key, header_key) in FINGERPRINT_HEADER_MAP {
if let Some(value) = fp
.get(fp_key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|v| !v.is_empty())
{
out.insert(header_key.to_string(), value.to_string());
}
}
}
let profile = *current_claude_code_transport_identity_profile();
profile.apply_fixed_headers(&mut out, stream);
profile.apply_beta_policy(&mut out, None);
out
}
pub fn sanitize_claude_code_request_body(body: &mut Value) {
let profile = *current_claude_code_transport_identity_profile();
let beta_header = profile.merge_beta_tokens(None, None);
sanitize_claude_code_request_body_for_beta_header(body, &beta_header, profile);
}
pub fn sanitize_claude_code_request_body_for_beta_header(
body: &mut Value,
beta_header: &str,
profile: ClaudeCodeTransportIdentityProfile,
) {
let Some(body_object) = body.as_object_mut() else {
return;
};
synchronize_billing_header_version(body_object, profile.billing_cli_version());
for gate in profile.body_capability_gates() {
if !profile.beta_header_enables_body_field(beta_header, gate.body_field) {
body_object.remove(gate.body_field);
}
}
let thinking_enabled = body_object
.get("thinking")
.and_then(Value::as_object)
@@ -122,6 +86,26 @@ pub fn sanitize_claude_code_request_body(body: &mut Value) {
.map(str::trim)
.is_some_and(|value| matches!(value.to_ascii_lowercase().as_str(), "enabled" | "adaptive"));
if let Some(gate) = profile.body_capability_gate("context_management") {
if thinking_enabled
&& gate.inject_when_thinking_enabled
&& profile.beta_header_enables_body_field(beta_header, gate.body_field)
&& !body_object.contains_key(gate.body_field)
{
if let Some(default_edit_type) = gate.default_edit_type {
body_object.insert(
gate.body_field.to_string(),
serde_json::json!({
"edits": [{
"type": default_edit_type,
"keep": "all"
}]
}),
);
}
}
}
let Some(messages) = body_object
.get_mut("messages")
.and_then(Value::as_array_mut)
@@ -160,6 +144,37 @@ pub fn sanitize_claude_code_request_body(body: &mut Value) {
}
}
fn synchronize_billing_header_version(body: &mut Map<String, Value>, cli_version: &str) {
static CC_VERSION: OnceLock<Regex> = OnceLock::new();
let Some(system) = body.get_mut("system").and_then(Value::as_array_mut) else {
return;
};
let regex = CC_VERSION.get_or_init(|| {
Regex::new(r"cc_version=\d+\.\d+\.\d+").expect("billing version regex must compile")
});
let replacement = format!("cc_version={cli_version}");
for block in system {
let Some(block) = block.as_object_mut() else {
continue;
};
let Some(text) = block
.get("text")
.and_then(Value::as_str)
.map(ToOwned::to_owned)
else {
continue;
};
if !text.starts_with("x-anthropic-billing-header") {
continue;
}
let updated = regex.replace_all(&text, replacement.as_str());
if updated != text {
block.insert("text".to_string(), Value::String(updated.into_owned()));
}
}
}
fn keep_claude_code_block(
block_object: &Map<String, Value>,
role: &str,
@@ -187,46 +202,18 @@ fn keep_claude_code_block(
true
}
fn merge_anthropic_beta_tokens(incoming: Option<&str>) -> String {
let mut seen = BTreeSet::new();
let mut merged = Vec::new();
for token in REQUIRED_BETA_TOKENS {
append_beta_token(&mut seen, &mut merged, token);
}
for token in incoming.unwrap_or_default().split(',') {
let token = token.trim();
if EXCLUDED_BETA_TOKENS
.iter()
.any(|excluded| token.eq_ignore_ascii_case(excluded))
{
continue;
}
append_beta_token(&mut seen, &mut merged, token);
}
merged.join(",")
}
fn append_beta_token(seen: &mut BTreeSet<String>, merged: &mut Vec<String>, token: &str) {
let normalized = token.trim();
if normalized.is_empty() {
return;
}
let key = normalized.to_ascii_lowercase();
if seen.insert(key) {
merged.push(normalized.to_string());
}
}
#[cfg(test)]
mod tests {
use super::{build_claude_code_passthrough_headers, sanitize_claude_code_request_body};
use super::{
build_claude_code_passthrough_headers, sanitize_claude_code_request_body,
sanitize_claude_code_request_body_for_beta_header,
};
use crate::claude_code::current_claude_code_transport_identity_profile;
use serde_json::json;
use std::collections::BTreeMap;
#[test]
fn claude_code_headers_use_transport_profile_header_fingerprint_and_merge_required_betas() {
fn claude_code_headers_use_versioned_identity_and_merge_preserved_betas() {
let mut headers = http::HeaderMap::new();
headers.insert(
"anthropic-beta",
@@ -242,23 +229,12 @@ mod tests {
"Bearer upstream-token",
&BTreeMap::new(),
true,
Some(&json!({
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"user_agent":"Claude-Code/9.9",
"stainless_package_version":"1.0.5",
"stainless_runtime_version":"v22.12.0",
"stainless_timeout":"900"
}
}
})),
);
assert_eq!(
built.get("anthropic-beta").map(String::as_str),
Some(
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,custom-beta"
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,effort-2025-11-24,context-management-2025-06-27,extended-cache-ttl-2025-04-11,context-1m-2025-08-07,custom-beta"
)
);
assert_eq!(
@@ -276,19 +252,19 @@ mod tests {
assert_eq!(built.get("x-app").map(String::as_str), Some("cli"));
assert_eq!(
built.get("x-stainless-package-version").map(String::as_str),
Some("1.0.5")
Some("0.94.0")
);
assert_eq!(
built.get("x-stainless-runtime-version").map(String::as_str),
Some("v22.12.0")
Some("v24.3.0")
);
assert_eq!(
built.get("x-stainless-timeout").map(String::as_str),
Some("900")
Some("600")
);
assert_eq!(
built.get("user-agent").map(String::as_str),
Some("Claude-Code/9.9")
Some("claude-cli/2.1.161 (external, cli)")
);
assert_eq!(
built.get("authorization").map(String::as_str),
@@ -324,4 +300,74 @@ mod tests {
])
);
}
#[test]
fn context_management_body_is_gated_by_the_matching_beta_token() {
let profile = *current_claude_code_transport_identity_profile();
let original = json!({
"context_management": {
"edits": [{"type":"clear_thinking_20251015", "keep":"all"}]
},
"messages": []
});
let mut without_beta = original.clone();
sanitize_claude_code_request_body_for_beta_header(
&mut without_beta,
"oauth-2025-04-20",
profile,
);
assert!(without_beta.get("context_management").is_none());
let mut with_beta = original.clone();
sanitize_claude_code_request_body_for_beta_header(
&mut with_beta,
"oauth-2025-04-20, context-management-2025-06-27",
profile,
);
assert_eq!(with_beta, original);
}
#[test]
fn default_profile_keeps_header_and_injected_context_management_in_sync() {
let headers = build_claude_code_passthrough_headers(
&http::HeaderMap::new(),
"authorization",
"Bearer upstream-token",
&BTreeMap::new(),
false,
);
let mut body = json!({
"thinking": {"type":"adaptive"},
"messages": []
});
sanitize_claude_code_request_body(&mut body);
assert!(headers["anthropic-beta"]
.split(',')
.any(|token| token == "context-management-2025-06-27"));
assert_eq!(
body["context_management"],
json!({"edits":[{"type":"clear_thinking_20251015", "keep":"all"}]})
);
}
#[test]
fn billing_attribution_uses_the_profile_cli_version() {
let mut body = json!({
"system": [{
"type":"text",
"text":"x-anthropic-billing-header: cc_version=2.0.0.abc; cc_entrypoint=cli;"
}],
"messages": []
});
sanitize_claude_code_request_body(&mut body);
assert_eq!(
body["system"][0]["text"],
"x-anthropic-billing-header: cc_version=2.1.161.abc; cc_entrypoint=cli;"
);
}
}
@@ -5,14 +5,19 @@ use url::form_urlencoded;
pub fn build_claude_code_messages_url(upstream_base_url: &str, query: Option<&str>) -> String {
let (trimmed_base_url, base_query) = split_query(upstream_base_url.trim());
let trimmed_base_url = trimmed_base_url.trim_end_matches('/');
let mut url =
if trimmed_base_url.ends_with("/v1/messages") || trimmed_base_url.ends_with("/messages") {
trimmed_base_url.to_string()
} else if trimmed_base_url.ends_with("/v1") {
format!("{trimmed_base_url}/messages")
} else {
format!("{trimmed_base_url}/v1/messages")
};
let mut url = if trimmed_base_url.ends_with("/messages/count_tokens") {
trimmed_base_url
.strip_suffix("/count_tokens")
.unwrap_or(trimmed_base_url)
.to_string()
} else if trimmed_base_url.ends_with("/v1/messages") || trimmed_base_url.ends_with("/messages")
{
trimmed_base_url.to_string()
} else if trimmed_base_url.ends_with("/v1") {
format!("{trimmed_base_url}/messages")
} else {
format!("{trimmed_base_url}/v1/messages")
};
append_merged_query(&mut url, base_query, query);
url
}
@@ -66,6 +71,13 @@ mod tests {
build_claude_code_messages_url("https://api.anthropic.com/v1/messages", None),
"https://api.anthropic.com/v1/messages"
);
assert_eq!(
build_claude_code_messages_url(
"https://api.anthropic.com/v1/messages/count_tokens?tenant=base",
Some("trace=1"),
),
"https://api.anthropic.com/v1/messages?tenant=base&trace=1"
);
}
#[test]