mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
})),
|
||||
);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
))
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user