Clean up transport fingerprint configuration

Remove legacy tls_profile handling, keep header fingerprint under transport profiles, and drop the duplicate auth_modules migration.
This commit is contained in:
fawney19
2026-05-06 13:54:33 +08:00
parent 6fbb867f5f
commit 68216bf868
53 changed files with 267 additions and 238 deletions

View File

@@ -74,6 +74,8 @@ pub struct ResolvedTransportProfile {
pub backend: String,
pub http_mode: String,
pub pool_scope: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub header_fingerprint: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<Value>,
}
@@ -85,18 +87,7 @@ impl Default for ResolvedTransportProfile {
backend: TRANSPORT_BACKEND_REQWEST_RUSTLS.to_string(),
http_mode: TRANSPORT_HTTP_MODE_AUTO.to_string(),
pool_scope: TRANSPORT_POOL_SCOPE_KEY.to_string(),
extra: None,
}
}
}
impl ResolvedTransportProfile {
pub fn from_legacy_tls_profile(profile_id: impl Into<String>) -> Self {
Self {
profile_id: profile_id.into(),
backend: TRANSPORT_BACKEND_REQWEST_RUSTLS.to_string(),
http_mode: TRANSPORT_HTTP_MODE_AUTO.to_string(),
pool_scope: TRANSPORT_POOL_SCOPE_KEY.to_string(),
header_fingerprint: None,
extra: None,
}
}

View File

@@ -1,8 +0,0 @@
CREATE TABLE IF NOT EXISTS public.auth_modules (
id character varying(36) PRIMARY KEY,
module_type character varying(128) NOT NULL UNIQUE,
enabled boolean DEFAULT true NOT NULL,
config json DEFAULT '{}'::json NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);

View File

@@ -7,7 +7,7 @@ use tracing::info;
// Generated by build.rs from schema/bootstrap/postgres.
pub(crate) static EMPTY_DATABASE_SNAPSHOT_SQL: &str =
include_str!(concat!(env!("OUT_DIR"), "/empty_database_snapshot.sql"));
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260506000000;
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260505130000;
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
SELECT COUNT(*)::BIGINT

View File

@@ -291,7 +291,6 @@ fn empty_database_snapshot_covers_current_cutoff_versions() {
20260502000000,
20260505000000,
20260505130000,
20260506000000,
]
);
}
@@ -1011,7 +1010,6 @@ fn pending_migrations_from_applied_skips_versions_already_applied() {
20260502000000,
20260505000000,
20260505130000,
20260506000000,
]
);
}

View File

@@ -6477,13 +6477,14 @@ ORDER BY request_count DESC, group_key ASC
query: &UsageAuditAggregationQuery,
) -> Result<Vec<StoredUsageAuditAggregation>, DataLayerError> {
let fragments = usage_audit_aggregation_sql_fragments(query.group_by);
let provider_extra_where = if matches!(query.group_by, UsageAuditAggregationGroupBy::Provider)
|| query.exclude_reserved_provider_labels
{
USAGE_RESERVED_PROVIDER_LABELS_FILTER_SQL
} else {
""
};
let provider_extra_where =
if matches!(query.group_by, UsageAuditAggregationGroupBy::Provider)
|| query.exclude_reserved_provider_labels
{
USAGE_RESERVED_PROVIDER_LABELS_FILTER_SQL
} else {
""
};
let sql = format!(
r#"
WITH filtered_usage AS (

View File

@@ -5,7 +5,10 @@ mod request;
mod url;
pub use auth::supports_local_claude_code_auth;
pub use fingerprint::{generate_fingerprint, generate_random_fingerprint, sanitize_fingerprint};
pub use fingerprint::{
generate_fingerprint, generate_random_fingerprint, header_fingerprint_from_fingerprint,
sanitize_fingerprint,
};
pub use policy::{
local_claude_code_transport_unsupported_reason_with_network,
supports_local_claude_code_transport_with_network,

View File

@@ -1,3 +1,6 @@
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;
@@ -53,6 +56,7 @@ const STAINLESS_PACKAGE_VERSIONS: &[&str] = &["0.68.0", "0.69.0", "0.70.0", "0.7
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.
@@ -122,9 +126,12 @@ fn resolve_platform_token(os: &str, arch: &str) -> &'static str {
}
}
/// Generate a complete fingerprint JSON from a seed (deterministic).
/// Compatible with the Python `generate_fingerprint(seed=...)` output.
/// Generate a complete Claude Code transport fingerprint from a seed.
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 =
@@ -163,6 +170,26 @@ pub fn generate_fingerprint(seed: &str) -> Value {
})
}
fn wrap_header_fingerprint(header_fingerprint: Value) -> Value {
serde_json::json!({
"transport_profile": {
"profile_id": CLAUDE_CODE_TRANSPORT_PROFILE_ID,
"backend": TRANSPORT_BACKEND_REQWEST_RUSTLS,
"http_mode": TRANSPORT_HTTP_MODE_AUTO,
"pool_scope": TRANSPORT_POOL_SCOPE_KEY,
"header_fingerprint": header_fingerprint,
}
})
}
pub fn header_fingerprint_from_fingerprint(fingerprint: &Value) -> Option<&Map<String, Value>> {
fingerprint
.get("transport_profile")
.and_then(Value::as_object)
.and_then(|profile| profile.get("header_fingerprint"))
.and_then(Value::as_object)
}
/// Generate a random (non-deterministic) fingerprint.
pub fn generate_random_fingerprint() -> Value {
let random_seed = Uuid::new_v4().to_string();
@@ -172,20 +199,16 @@ pub fn generate_random_fingerprint() -> Value {
/// Sanitize an existing fingerprint JSON, filling missing fields with
/// deterministic fallbacks derived from `key_id`.
pub fn sanitize_fingerprint(raw: &Value, key_id: &str) -> Value {
let generated = generate_fingerprint(key_id);
let generated = generate_header_fingerprint(key_id);
let gen_map = generated.as_object().unwrap();
let raw_map = match raw.as_object() {
Some(m) => m,
None => return generated,
};
let raw_map = header_fingerprint_from_fingerprint(raw);
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
.get(key)
.and_then(|raw_map| raw_map.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|v| !v.is_empty())
@@ -244,7 +267,7 @@ pub fn sanitize_fingerprint(raw: &Value, key_id: &str) -> Value {
);
}
Value::Object(out)
wrap_header_fingerprint(Value::Object(out))
}
#[cfg(test)]
@@ -283,7 +306,7 @@ mod tests {
"platform_info",
"user_agent",
];
let map = fp.as_object().unwrap();
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();
@@ -294,13 +317,18 @@ mod tests {
#[test]
fn sanitize_preserves_user_overrides() {
let raw = serde_json::json!({
"stainless_os": "MacOS",
"stainless_arch": "arm64",
"stainless_timeout": "900",
"user_agent": "Custom-Agent/1.0",
"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 = sanitized.as_object().unwrap();
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"));
@@ -314,9 +342,9 @@ mod tests {
fn sanitize_fills_missing_fields_from_seed() {
let raw = serde_json::json!({});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let generated = generate_fingerprint("test-key");
let generated = generate_header_fingerprint("test-key");
// All fields should match generated since raw is empty
let s = sanitized.as_object().unwrap();
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}");
@@ -330,10 +358,17 @@ mod tests {
#[test]
fn sanitize_normalizes_unknown_impersonate_profile() {
let raw = serde_json::json!({
"impersonate": "firefox99",
"transport_profile": {
"profile_id": "claude_code_nodejs",
"header_fingerprint": {
"impersonate": "firefox99"
}
}
});
let sanitized = sanitize_fingerprint(&raw, "test-key");
let profile = sanitized["impersonate"].as_str().unwrap();
let profile = header_fingerprint_from_fingerprint(&sanitized).unwrap()["impersonate"]
.as_str()
.unwrap();
assert!(
CHROME_IMPERSONATE_PROFILES
.iter()

View File

@@ -2,7 +2,7 @@ use super::super::snapshot::GatewayProviderTransportSnapshot;
use super::super::{
body_rules_are_locally_supported, header_rules_are_locally_supported,
resolve_transport_profile, supports_local_oauth_request_auth_resolution,
transport_proxy_is_locally_supported,
transport_profile_is_configured, transport_proxy_is_locally_supported,
};
use super::auth::supports_local_claude_code_auth;
@@ -52,8 +52,9 @@ pub fn local_claude_code_transport_unsupported_reason_with_network(
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
return Some("transport_tls_profile_unsupported");
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
None
@@ -128,7 +129,7 @@ mod tests {
global_priority_by_format: None,
expires_at_unix_secs: None,
proxy: None,
fingerprint: Some(json!({"tls_profile":"chrome_136"})),
fingerprint: Some(json!({"transport_profile":"chrome_136"})),
decrypted_api_key: "sk-ant-123".to_string(),
decrypted_auth_config: None,
},
@@ -136,7 +137,7 @@ mod tests {
}
#[test]
fn supports_claude_code_transport_when_auth_and_tls_are_valid() {
fn supports_claude_code_transport_when_auth_and_profile_are_valid() {
assert!(supports_local_claude_code_transport_with_network(
&sample_transport(),
"claude:messages"

View File

@@ -3,6 +3,7 @@ use std::collections::{BTreeMap, BTreeSet};
use serde_json::{Map, Value};
use super::super::auth::build_openai_passthrough_headers;
use super::fingerprint::header_fingerprint_from_fingerprint;
const DEFAULT_ANTHROPIC_VERSION: &str = "2023-06-01";
const DEFAULT_ACCEPT: &str = "application/json";
@@ -92,8 +93,8 @@ pub fn build_claude_code_passthrough_headers(
out.remove("x-stainless-helper-method");
}
// Override from fingerprint: all mapped fields are applied uniformly.
if let Some(fp) = fingerprint.and_then(Value::as_object) {
// 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)
@@ -225,7 +226,7 @@ mod tests {
use std::collections::BTreeMap;
#[test]
fn claude_code_headers_merge_required_betas_and_stream_helper() {
fn claude_code_headers_use_transport_profile_header_fingerprint_and_merge_required_betas() {
let mut headers = http::HeaderMap::new();
headers.insert(
"anthropic-beta",
@@ -242,10 +243,15 @@ mod tests {
&BTreeMap::new(),
true,
Some(&json!({
"user_agent":"Claude-Code/9.9",
"stainless_package_version":"1.0.5",
"stainless_runtime_version":"v22.12.0",
"stainless_timeout":"900"
"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"
}
}
})),
);

View File

@@ -9,7 +9,7 @@ use crate::conversion::{
request_pair_allowed_for_transport,
};
use crate::network::{
resolve_transport_profile, resolve_transport_tls_profile, transport_proxy_is_locally_supported,
resolve_transport_profile, resolve_transport_profile_id, transport_proxy_is_locally_supported,
};
use crate::policy::{
local_gemini_transport_unsupported_reason_with_network,
@@ -68,18 +68,10 @@ pub fn build_transport_diagnostics(
client_api_format: &str,
provider_api_format: &str,
) -> Value {
let resolved_tls_profile = resolve_transport_tls_profile(transport);
let resolved_transport_profile_id = resolve_transport_profile_id(transport);
let resolved_transport_profile = resolve_transport_profile(transport)
.and_then(|profile| serde_json::to_value(profile).ok())
.unwrap_or(Value::Null);
let configured_tls_profile = transport
.key
.fingerprint
.as_ref()
.and_then(Value::as_object)
.and_then(|value| value.get("tls_profile"))
.cloned()
.unwrap_or(Value::Null);
let configured_key_transport_profile = transport
.key
.fingerprint
@@ -132,10 +124,9 @@ pub fn build_transport_diagnostics(
"oauth_request_auth_resolution_supported": oauth_resolution_supported,
},
"fingerprint": transport.key.fingerprint,
"configured_tls_profile": configured_tls_profile,
"configured_key_transport_profile": configured_key_transport_profile,
"configured_provider_transport_profile": configured_provider_transport_profile,
"resolved_tls_profile": resolved_tls_profile,
"resolved_transport_profile_id": resolved_transport_profile_id,
"resolved_transport_profile": resolved_transport_profile,
"request_pair": {
"client_api_format": client_api_format,
@@ -332,8 +323,12 @@ mod tests {
expires_at_unix_secs: None,
proxy: None,
fingerprint: Some(json!({
"tls_profile": "chrome_136",
"user_agent": "Mozilla/5.0"
"transport_profile": {
"profile_id": "chrome_136",
"header_fingerprint": {
"user_agent": "Mozilla/5.0"
}
}
})),
decrypted_api_key: "sk-test".to_string(),
decrypted_auth_config: None,
@@ -402,8 +397,11 @@ mod tests {
build_transport_diagnostics(&sample_transport(), "claude:messages", "openai:responses");
assert_eq!(diagnostics["provider_type"], "codex");
assert_eq!(diagnostics["fingerprint"]["tls_profile"], "chrome_136");
assert_eq!(diagnostics["resolved_tls_profile"], "chrome_136");
assert_eq!(
diagnostics["fingerprint"]["transport_profile"]["profile_id"],
"chrome_136"
);
assert_eq!(diagnostics["resolved_transport_profile_id"], "chrome_136");
assert_eq!(
diagnostics["request_pair"]["conversion_enabled"],
Value::Bool(true)

View File

@@ -1,5 +1,8 @@
use super::super::snapshot::GatewayProviderTransportSnapshot;
use super::super::{resolve_transport_profile, transport_proxy_is_locally_supported};
use super::super::{
resolve_transport_profile, transport_profile_is_configured,
transport_proxy_is_locally_supported,
};
use super::{
body_rules_are_locally_supported, header_rules_are_locally_supported,
supports_local_kiro_request_auth_resolution, supports_local_kiro_request_shape, PROVIDER_TYPE,
@@ -48,8 +51,9 @@ pub fn local_kiro_request_transport_unsupported_reason_with_network(
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
return Some("transport_tls_profile_unsupported");
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
None

View File

@@ -46,9 +46,9 @@ pub use generic_oauth::{
};
pub use headers::{should_skip_request_header, should_skip_upstream_passthrough_header};
pub use network::{
resolve_transport_execution_timeouts, resolve_transport_profile,
resolve_transport_execution_timeouts, resolve_transport_profile, resolve_transport_profile_id,
resolve_transport_proxy_snapshot, resolve_transport_proxy_snapshot_with_tunnel_affinity,
resolve_transport_tls_profile, transport_proxy_is_locally_supported,
transport_profile_is_configured, transport_proxy_is_locally_supported,
TransportTunnelAffinityLookup, TransportTunnelAttachmentOwner,
};
pub use oauth_refresh::{

View File

@@ -141,7 +141,7 @@ pub fn transport_proxy_is_locally_supported(transport: &GatewayProviderTransport
.is_some_and(|value| !value.is_empty())
}
pub fn resolve_transport_tls_profile(
pub fn resolve_transport_profile_id(
transport: &GatewayProviderTransportSnapshot,
) -> Option<String> {
resolve_transport_profile(transport).map(|profile| profile.profile_id)
@@ -166,18 +166,25 @@ fn resolve_transport_profile_from_fingerprint(
fingerprint: Option<&Value>,
) -> Option<ResolvedTransportProfile> {
let fingerprint = fingerprint?;
if let Some(profile) = fingerprint.get("transport_profile") {
if let Some(resolved) = parse_transport_profile_value(profile) {
return Some(resolved);
}
}
fingerprint
.get("tls_profile")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ResolvedTransportProfile::from_legacy_tls_profile)
.get("transport_profile")
.and_then(parse_transport_profile_value)
}
pub fn transport_profile_is_configured(transport: &GatewayProviderTransportSnapshot) -> bool {
transport_profile_configured_in_fingerprint(transport.key.fingerprint.as_ref())
|| transport_profile_configured_in_provider_config(transport.provider.config.as_ref())
}
fn transport_profile_configured_in_provider_config(config: Option<&Value>) -> bool {
let fingerprint = config.and_then(|value| value.get("fingerprint"));
transport_profile_configured_in_fingerprint(fingerprint)
}
fn transport_profile_configured_in_fingerprint(fingerprint: Option<&Value>) -> bool {
fingerprint
.and_then(|value| value.get("transport_profile"))
.is_some_and(|value| !value.is_null())
}
fn parse_transport_profile_value(value: &Value) -> Option<ResolvedTransportProfile> {
@@ -191,6 +198,7 @@ fn parse_transport_profile_value(value: &Value) -> Option<ResolvedTransportProfi
backend: TRANSPORT_BACKEND_REQWEST_RUSTLS.to_string(),
http_mode: TRANSPORT_HTTP_MODE_AUTO.to_string(),
pool_scope: TRANSPORT_POOL_SCOPE_KEY.to_string(),
header_fingerprint: None,
extra: None,
});
}
@@ -212,6 +220,7 @@ fn parse_transport_profile_value(value: &Value) -> Option<ResolvedTransportProfi
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| TRANSPORT_POOL_SCOPE_KEY.to_string());
let header_fingerprint = object.get("header_fingerprint").cloned();
let extra = object.get("extra").cloned();
Some(ResolvedTransportProfile {
@@ -219,6 +228,7 @@ fn parse_transport_profile_value(value: &Value) -> Option<ResolvedTransportProfi
backend,
http_mode,
pool_scope,
header_fingerprint,
extra,
})
}
@@ -296,8 +306,8 @@ mod tests {
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
};
use super::{
resolve_transport_profile, resolve_transport_proxy_snapshot,
resolve_transport_proxy_snapshot_with_tunnel_affinity, resolve_transport_tls_profile,
resolve_transport_profile, resolve_transport_profile_id, resolve_transport_proxy_snapshot,
resolve_transport_proxy_snapshot_with_tunnel_affinity, transport_profile_is_configured,
transport_proxy_is_locally_supported, TransportTunnelAffinityLookup,
TransportTunnelAttachmentOwner,
};
@@ -379,7 +389,7 @@ mod tests {
global_priority_by_format: None,
expires_at_unix_secs: None,
proxy: Some(json!({"node_id":"proxy-node-1","kind":"manual"})),
fingerprint: Some(json!({"tls_profile":"chrome_136"})),
fingerprint: Some(json!({"transport_profile":"chrome_136"})),
decrypted_api_key: "sk-test".to_string(),
decrypted_auth_config: None,
},
@@ -456,9 +466,9 @@ mod tests {
}
#[test]
fn resolves_transport_tls_profile_from_key_fingerprint() {
fn resolves_transport_profile_id_from_key_fingerprint() {
assert_eq!(
resolve_transport_tls_profile(&sample_transport()).as_deref(),
resolve_transport_profile_id(&sample_transport()).as_deref(),
Some("chrome_136")
);
assert!(transport_proxy_is_locally_supported(&sample_transport()));
@@ -501,7 +511,7 @@ mod tests {
}
#[test]
fn maps_legacy_tls_profile_to_transport_profile() {
fn maps_string_transport_profile_to_resolved_profile() {
let profile = resolve_transport_profile(&sample_transport()).expect("profile");
assert_eq!(profile.profile_id, "chrome_136");
@@ -517,6 +527,6 @@ mod tests {
transport.provider.config = None;
assert!(resolve_transport_profile(&transport).is_none());
assert!(resolve_transport_tls_profile(&transport).is_none());
assert!(!transport_profile_is_configured(&transport));
}
}

View File

@@ -7,7 +7,7 @@ use super::snapshot::GatewayProviderTransportSnapshot;
use super::{
body_rules_are_locally_supported, header_rules_are_locally_supported,
resolve_transport_profile, supports_local_oauth_request_auth_resolution,
transport_proxy_is_locally_supported,
transport_profile_is_configured, transport_proxy_is_locally_supported,
};
pub fn supports_local_openai_chat_transport(transport: &GatewayProviderTransportSnapshot) -> bool {
@@ -76,8 +76,9 @@ pub fn local_openai_chat_transport_unsupported_reason(
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
return Some("transport_tls_profile_unsupported");
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
if !provider_type_supports_local_openai_chat_transport(&transport.provider.provider_type) {
return Some("transport_provider_type_unsupported");
@@ -175,21 +176,17 @@ fn local_same_format_transport_unsupported_reason(
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
return Some("transport_tls_profile_unsupported");
if transport_profile_is_configured(transport)
&& resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
} else if transport.provider.proxy.is_some()
|| transport.endpoint.proxy.is_some()
|| transport.key.proxy.is_some()
|| transport
.key
.fingerprint
.as_ref()
.and_then(|value| value.get("tls_profile"))
.and_then(|value| value.as_str())
.is_some_and(|value| !value.trim().is_empty())
|| transport_profile_is_configured(transport)
{
return Some("transport_proxy_or_tls_unsupported");
return Some("transport_proxy_or_profile_unsupported");
}
if !provider_type_supported(&transport.provider.provider_type) {

View File

@@ -318,7 +318,7 @@ mod tests {
Some(serde_json::json!(["gpt-4.1", "gpt-4.1-mini"])),
Some(1_800_000_000),
Some(serde_json::json!({"node_id":"proxy-node-1"})),
Some(serde_json::json!({"tls_profile":"chrome_136"})),
Some(serde_json::json!({"transport_profile":"chrome_136"})),
)
.expect("key transport fields should build")
}
@@ -397,7 +397,7 @@ mod tests {
global_priority_by_format: Some(serde_json::json!({"openai:chat": 1})),
expires_at_unix_secs: Some(1_800_000_000),
proxy: Some(serde_json::json!({"node_id":"proxy-node-1"})),
fingerprint: Some(serde_json::json!({"tls_profile":"chrome_136"})),
fingerprint: Some(serde_json::json!({"transport_profile":"chrome_136"})),
decrypted_api_key: "sk-live-openai".to_string(),
decrypted_auth_config: Some(
"{\"refresh_token\":\"rt-1\",\"project\":\"demo\"}".to_string(),

View File

@@ -1,7 +1,8 @@
use super::super::snapshot::GatewayProviderTransportSnapshot;
use super::super::{
body_rules_are_locally_supported, header_rules_are_locally_supported,
resolve_transport_profile, resolve_transport_tls_profile, transport_proxy_is_locally_supported,
resolve_transport_profile, transport_profile_is_configured,
transport_proxy_is_locally_supported,
};
use super::auth::resolve_local_vertex_api_key_query_auth;
@@ -46,8 +47,9 @@ pub fn local_vertex_api_key_gemini_transport_unsupported_reason_with_network(
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
return Some("transport_tls_profile_unsupported");
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
None
@@ -122,9 +124,6 @@ fn supports_local_vertex_api_key_same_format_transport(
.custom_path
.as_deref()
.is_some_and(|value: &str| !value.trim().is_empty());
let has_tls_profile = resolve_transport_tls_profile(transport)
.as_deref()
.is_some_and(|value: &str| !value.trim().is_empty());
if has_custom_path && !allow_network_passthrough {
return false;
}
@@ -133,13 +132,15 @@ fn supports_local_vertex_api_key_same_format_transport(
if !transport_proxy_is_locally_supported(transport) {
return false;
}
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
if transport_profile_is_configured(transport)
&& resolve_transport_profile(transport).is_none()
{
return false;
}
} else if transport.provider.proxy.is_some()
|| transport.endpoint.proxy.is_some()
|| transport.key.proxy.is_some()
|| has_tls_profile
|| transport_profile_is_configured(transport)
{
return false;
}
@@ -254,7 +255,7 @@ mod tests {
transport.endpoint.custom_path =
Some("/v1/publishers/google/models/gemini-2.5-pro:generateContent".to_string());
transport.key.proxy = Some(json!({"url":"http://proxy.example:8080"}));
transport.key.fingerprint = Some(json!({"tls_profile":"chrome_136"}));
transport.key.fingerprint = Some(json!({"transport_profile":"chrome_136"}));
assert!(!supports_local_vertex_api_key_gemini_transport(&transport));
assert!(supports_local_vertex_api_key_gemini_transport_with_network(
&transport

View File

@@ -11,7 +11,7 @@ use super::auth::{
build_passthrough_headers_with_auth, resolve_local_gemini_auth,
resolve_local_openai_bearer_auth,
};
use super::network::resolve_transport_execution_timeouts;
use super::network::{resolve_transport_execution_timeouts, resolve_transport_profile};
use super::policy::{
local_gemini_transport_unsupported_reason_with_network,
local_standard_transport_unsupported_reason_with_network, supports_local_gemini_transport,
@@ -81,7 +81,7 @@ pub fn resolve_local_video_task_transport(
content_type: Some("application/json".to_string()),
model_name,
proxy: None,
tls_profile: None,
transport_profile: resolve_transport_profile(transport),
timeouts: resolve_transport_execution_timeouts(transport),
},
))

View File

@@ -168,7 +168,7 @@ impl GeminiVideoTaskSeed {
provider_api_format: "gemini:video".to_string(),
model_name: Some(self.model.clone()),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
})
}
@@ -228,7 +228,7 @@ impl GeminiVideoTaskSeed {
provider_api_format: "gemini:video".to_string(),
model_name: Some(self.model.clone()),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("gemini_video_cancel_sync_finalize".to_string()),

View File

@@ -235,7 +235,7 @@ impl OpenAiVideoTaskSeed {
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
)))
@@ -340,7 +340,7 @@ impl OpenAiVideoTaskSeed {
provider_api_format: "openai:video".to_string(),
model_name: model_name.clone(),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_delete_sync_finalize".to_string()),
@@ -405,7 +405,7 @@ impl OpenAiVideoTaskSeed {
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
})
}
@@ -466,7 +466,7 @@ impl OpenAiVideoTaskSeed {
provider_api_format: "openai:video".to_string(),
model_name: model_name.clone(),
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_cancel_sync_finalize".to_string()),
@@ -561,7 +561,7 @@ impl OpenAiVideoTaskSeed {
provider_api_format: "openai:video".to_string(),
model_name,
proxy: self.transport.proxy.clone(),
transport_profile: None,
transport_profile: self.transport.transport_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_remix_sync_finalize".to_string()),

View File

@@ -30,7 +30,7 @@ impl LocalVideoTaskTransport {
content_type: plan.content_type.clone(),
model_name: plan.model_name.clone(),
proxy: plan.proxy.clone(),
tls_profile: None,
transport_profile: plan.transport_profile.clone(),
timeouts: plan.timeouts.clone(),
})
}
@@ -49,7 +49,7 @@ impl LocalVideoTaskTransport {
content_type: input.content_type,
model_name: input.model_name,
proxy: input.proxy,
tls_profile: input.tls_profile,
transport_profile: input.transport_profile,
timeouts: input.timeouts,
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot};
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot, ResolvedTransportProfile};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -100,7 +100,7 @@ pub struct LocalVideoTaskTransport {
pub content_type: Option<String>,
pub model_name: Option<String>,
pub proxy: Option<ProxySnapshot>,
pub tls_profile: Option<String>,
pub transport_profile: Option<ResolvedTransportProfile>,
pub timeouts: Option<ExecutionTimeouts>,
}
@@ -116,7 +116,7 @@ pub struct LocalVideoTaskTransportBridgeInput {
pub content_type: Option<String>,
pub model_name: Option<String>,
pub proxy: Option<ProxySnapshot>,
pub tls_profile: Option<String>,
pub transport_profile: Option<ResolvedTransportProfile>,
pub timeouts: Option<ExecutionTimeouts>,
}