mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
Implement transport profile routing
This commit is contained in:
@@ -130,7 +130,7 @@ pub fn build_ai_execution_plan_from_decision(
|
||||
provider_api_format: parts.core.provider_api_format,
|
||||
model_name: payload.model_name.take(),
|
||||
proxy: payload.proxy.take(),
|
||||
tls_profile: payload.tls_profile.take(),
|
||||
transport_profile: payload.transport_profile.take(),
|
||||
timeouts: payload.timeouts.take(),
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ pub fn build_ai_execution_decision_from_plan(
|
||||
provider_api_format,
|
||||
model_name,
|
||||
proxy,
|
||||
tls_profile,
|
||||
transport_profile,
|
||||
timeouts,
|
||||
} = parts.plan;
|
||||
let auth_pair = parts
|
||||
@@ -209,7 +209,7 @@ pub fn build_ai_execution_decision_from_plan(
|
||||
provider_request_body_base64: body_bytes_b64,
|
||||
content_type,
|
||||
proxy,
|
||||
tls_profile,
|
||||
transport_profile,
|
||||
timeouts,
|
||||
upstream_is_stream: stream,
|
||||
report_kind: parts.report_kind,
|
||||
@@ -451,7 +451,7 @@ mod tests {
|
||||
provider_api_format: "claude:messages".to_string(),
|
||||
model_name: Some("mapped".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
|
||||
@@ -512,7 +512,7 @@ mod tests {
|
||||
provider_request_body_base64: None,
|
||||
content_type: None,
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
upstream_is_stream: false,
|
||||
report_kind: None,
|
||||
|
||||
@@ -4,7 +4,7 @@ use aether_ai_formats::api::{
|
||||
ExecutionRuntimeAuthContext, EXECUTION_RUNTIME_STREAM_DECISION_ACTION,
|
||||
EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
|
||||
};
|
||||
use aether_contracts::{ExecutionTimeouts, ProxySnapshot};
|
||||
use aether_contracts::{ExecutionTimeouts, ProxySnapshot, ResolvedTransportProfile};
|
||||
|
||||
use crate::{AiExecutionDecision, ConversionMode, ExecutionStrategy};
|
||||
|
||||
@@ -34,7 +34,7 @@ pub struct AiExecutionDecisionResponseParts {
|
||||
pub provider_request_body_base64: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub proxy: Option<ProxySnapshot>,
|
||||
pub tls_profile: Option<String>,
|
||||
pub transport_profile: Option<ResolvedTransportProfile>,
|
||||
pub timeouts: Option<ExecutionTimeouts>,
|
||||
pub upstream_is_stream: bool,
|
||||
pub report_kind: Option<String>,
|
||||
@@ -74,7 +74,7 @@ pub fn build_ai_execution_decision_response(
|
||||
provider_request_body_base64: parts.provider_request_body_base64,
|
||||
content_type: parts.content_type,
|
||||
proxy: parts.proxy,
|
||||
tls_profile: parts.tls_profile,
|
||||
transport_profile: parts.transport_profile,
|
||||
timeouts: parts.timeouts,
|
||||
upstream_is_stream: parts.upstream_is_stream,
|
||||
report_kind: parts.report_kind,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use aether_ai_formats::api::ExecutionRuntimeAuthContext;
|
||||
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot, ResolvedTransportProfile};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -117,7 +117,7 @@ pub struct AiExecutionDecision {
|
||||
#[serde(default)]
|
||||
pub proxy: Option<ProxySnapshot>,
|
||||
#[serde(default)]
|
||||
pub tls_profile: Option<String>,
|
||||
pub transport_profile: Option<ResolvedTransportProfile>,
|
||||
#[serde(default)]
|
||||
pub timeouts: Option<ExecutionTimeouts>,
|
||||
#[serde(default)]
|
||||
@@ -217,7 +217,7 @@ mod tests {
|
||||
provider_request_body_base64: None,
|
||||
content_type: None,
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
upstream_is_stream: false,
|
||||
report_kind: None,
|
||||
|
||||
@@ -101,7 +101,7 @@ mod tests {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("model".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ mod usage;
|
||||
pub use error::{ExecutionError, ExecutionErrorKind, ExecutionPhase};
|
||||
pub use frame::{StreamFrame, StreamFramePayload, StreamFrameType};
|
||||
pub use plan::{
|
||||
ExecutionPlan, ExecutionTimeouts, ProxySnapshot, RequestBody,
|
||||
ExecutionPlan, ExecutionTimeouts, ProxySnapshot, RequestBody, ResolvedTransportProfile,
|
||||
EXECUTION_REQUEST_FOLLOW_REDIRECTS_HEADER, EXECUTION_REQUEST_HTTP1_ONLY_HEADER,
|
||||
TRANSPORT_BACKEND_HYPER_RUSTLS, TRANSPORT_BACKEND_REQWEST_RUSTLS, TRANSPORT_HTTP_MODE_AUTO,
|
||||
TRANSPORT_HTTP_MODE_HTTP1_ONLY, TRANSPORT_POOL_SCOPE_KEY,
|
||||
};
|
||||
pub use result::{ExecutionResult, ExecutionTelemetry, ResponseBody};
|
||||
pub use usage::{ExecutionStreamTerminalSummary, StandardizedUsage};
|
||||
|
||||
@@ -59,6 +59,47 @@ pub struct ProxySnapshot {
|
||||
pub extra: Option<Value>,
|
||||
}
|
||||
|
||||
pub const TRANSPORT_BACKEND_REQWEST_RUSTLS: &str = "reqwest_rustls";
|
||||
pub const TRANSPORT_BACKEND_HYPER_RUSTLS: &str = "hyper_rustls";
|
||||
pub const TRANSPORT_HTTP_MODE_AUTO: &str = "auto";
|
||||
pub const TRANSPORT_HTTP_MODE_HTTP1_ONLY: &str = "http1_only";
|
||||
pub const TRANSPORT_POOL_SCOPE_KEY: &str = "key";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(default)]
|
||||
pub struct ResolvedTransportProfile {
|
||||
pub profile_id: String,
|
||||
pub backend: String,
|
||||
pub http_mode: String,
|
||||
pub pool_scope: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra: Option<Value>,
|
||||
}
|
||||
|
||||
impl Default for ResolvedTransportProfile {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
profile_id: String::new(),
|
||||
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(),
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ExecutionPlan {
|
||||
pub request_id: String,
|
||||
@@ -88,7 +129,7 @@ pub struct ExecutionPlan {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub proxy: Option<ProxySnapshot>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tls_profile: Option<String>,
|
||||
pub transport_profile: Option<ResolvedTransportProfile>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub timeouts: Option<ExecutionTimeouts>,
|
||||
}
|
||||
@@ -117,7 +158,7 @@ mod tests {
|
||||
provider_api_format: "openai:chat".into(),
|
||||
model_name: Some("gpt-test".into()),
|
||||
proxy: None,
|
||||
tls_profile: Some("chrome".into()),
|
||||
transport_profile: None,
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(30_000),
|
||||
read_ms: Some(3_600_000),
|
||||
|
||||
@@ -171,6 +171,12 @@ pub enum ProtocolError {
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RequestMeta {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub provider_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub endpoint_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub key_id: Option<String>,
|
||||
pub method: String,
|
||||
pub url: String,
|
||||
pub headers: std::collections::HashMap<String, String>,
|
||||
@@ -180,6 +186,8 @@ pub struct RequestMeta {
|
||||
pub follow_redirects: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub http1_only: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub transport_profile: Option<crate::ResolvedTransportProfile>,
|
||||
}
|
||||
|
||||
fn default_timeout() -> u64 {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use aether_contracts::{ExecutionPlan, ExecutionResult, RequestBody};
|
||||
use aether_provider_transport::{
|
||||
is_vertex_api_key_transport_context, resolve_transport_execution_timeouts,
|
||||
resolve_transport_tls_profile, GatewayProviderTransportSnapshot,
|
||||
resolve_transport_profile, GatewayProviderTransportSnapshot,
|
||||
};
|
||||
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||
use base64::Engine as _;
|
||||
@@ -499,6 +499,8 @@ async fn exchange_vertex_service_account_token(
|
||||
let body = format!(
|
||||
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion={assertion}"
|
||||
);
|
||||
let transport_profile = resolve_transport_profile(transport);
|
||||
|
||||
let plan = ExecutionPlan {
|
||||
request_id: format!("req-model-fetch-{}-vertex-sa-token", transport.key.id),
|
||||
candidate_id: None,
|
||||
@@ -524,7 +526,7 @@ async fn exchange_vertex_service_account_token(
|
||||
provider_api_format: "vertex_ai:service_account_token".to_string(),
|
||||
model_name: Some("token".to_string()),
|
||||
proxy: runtime.resolve_model_fetch_proxy(transport).await,
|
||||
tls_profile: resolve_transport_tls_profile(transport),
|
||||
transport_profile,
|
||||
timeouts: resolve_transport_execution_timeouts(transport),
|
||||
};
|
||||
let result = runtime.execute_model_fetch_execution_plan(&plan).await?;
|
||||
|
||||
@@ -11,7 +11,7 @@ use aether_provider_transport::auth::{
|
||||
};
|
||||
use aether_provider_transport::vertex::resolve_local_vertex_api_key_query_auth;
|
||||
use aether_provider_transport::{
|
||||
apply_local_header_rules, resolve_transport_execution_timeouts, resolve_transport_tls_profile,
|
||||
apply_local_header_rules, resolve_transport_execution_timeouts, resolve_transport_profile,
|
||||
GatewayProviderTransportSnapshot, LocalResolvedOAuthRequestAuth,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
@@ -295,6 +295,8 @@ async fn build_execution_plan(
|
||||
model_name,
|
||||
} = request;
|
||||
|
||||
let transport_profile = resolve_transport_profile(transport);
|
||||
|
||||
Ok(ExecutionPlan {
|
||||
request_id: format!(
|
||||
"req-model-fetch-{}-{}",
|
||||
@@ -317,7 +319,7 @@ async fn build_execution_plan(
|
||||
provider_api_format,
|
||||
model_name,
|
||||
proxy: runtime.resolve_model_fetch_proxy(transport).await,
|
||||
tls_profile: resolve_transport_tls_profile(transport),
|
||||
transport_profile,
|
||||
timeouts: resolve_transport_execution_timeouts(transport),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::super::snapshot::GatewayProviderTransportSnapshot;
|
||||
use super::super::{
|
||||
body_rules_are_locally_supported, header_rules_are_locally_supported,
|
||||
resolve_transport_tls_profile, supports_local_oauth_request_auth_resolution,
|
||||
resolve_transport_profile, supports_local_oauth_request_auth_resolution,
|
||||
transport_proxy_is_locally_supported,
|
||||
};
|
||||
use super::auth::supports_local_claude_code_auth;
|
||||
@@ -52,7 +52,7 @@ 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_tls_profile(transport).is_none() {
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return Some("transport_tls_profile_unsupported");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ use crate::conversion::{
|
||||
request_conversion_enabled_for_transport, request_conversion_transport_unsupported_reason,
|
||||
request_pair_allowed_for_transport,
|
||||
};
|
||||
use crate::network::{resolve_transport_tls_profile, transport_proxy_is_locally_supported};
|
||||
use crate::network::{
|
||||
resolve_transport_profile, resolve_transport_tls_profile, transport_proxy_is_locally_supported,
|
||||
};
|
||||
use crate::policy::{
|
||||
local_gemini_transport_unsupported_reason_with_network,
|
||||
local_openai_chat_transport_unsupported_reason,
|
||||
@@ -67,6 +69,9 @@ pub fn build_transport_diagnostics(
|
||||
provider_api_format: &str,
|
||||
) -> Value {
|
||||
let resolved_tls_profile = resolve_transport_tls_profile(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
|
||||
@@ -75,6 +80,23 @@ pub fn build_transport_diagnostics(
|
||||
.and_then(|value| value.get("tls_profile"))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
let configured_key_transport_profile = transport
|
||||
.key
|
||||
.fingerprint
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("transport_profile"))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
let configured_provider_transport_profile = transport
|
||||
.provider
|
||||
.config
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("fingerprint"))
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("transport_profile"))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
let has_oauth_config = transport.key.decrypted_auth_config.is_some();
|
||||
let oauth_resolution_supported =
|
||||
!has_oauth_config || crate::supports_local_oauth_request_auth_resolution(transport);
|
||||
@@ -111,7 +133,10 @@ pub fn build_transport_diagnostics(
|
||||
},
|
||||
"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": resolved_transport_profile,
|
||||
"request_pair": {
|
||||
"client_api_format": client_api_format,
|
||||
"provider_api_format": provider_api_format,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::super::snapshot::GatewayProviderTransportSnapshot;
|
||||
use super::super::{resolve_transport_tls_profile, transport_proxy_is_locally_supported};
|
||||
use super::super::{resolve_transport_profile, 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,7 +48,7 @@ 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_tls_profile(transport).is_none() {
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return Some("transport_tls_profile_unsupported");
|
||||
}
|
||||
|
||||
|
||||
@@ -46,10 +46,10 @@ 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_proxy_snapshot,
|
||||
resolve_transport_proxy_snapshot_with_tunnel_affinity, resolve_transport_tls_profile,
|
||||
transport_proxy_is_locally_supported, TransportTunnelAffinityLookup,
|
||||
TransportTunnelAttachmentOwner,
|
||||
resolve_transport_execution_timeouts, resolve_transport_profile,
|
||||
resolve_transport_proxy_snapshot, resolve_transport_proxy_snapshot_with_tunnel_affinity,
|
||||
resolve_transport_tls_profile, transport_proxy_is_locally_supported,
|
||||
TransportTunnelAffinityLookup, TransportTunnelAttachmentOwner,
|
||||
};
|
||||
pub use oauth_refresh::{
|
||||
supports_local_oauth_request_auth_resolution, CachedOAuthEntry, LocalOAuthHttpExecutor,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use aether_contracts::{ExecutionTimeouts, ProxySnapshot};
|
||||
use aether_contracts::{
|
||||
ExecutionTimeouts, ProxySnapshot, ResolvedTransportProfile, TRANSPORT_BACKEND_REQWEST_RUSTLS,
|
||||
TRANSPORT_HTTP_MODE_AUTO, TRANSPORT_POOL_SCOPE_KEY,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::{json, Map, Value};
|
||||
use tracing::warn;
|
||||
@@ -141,15 +144,83 @@ pub fn transport_proxy_is_locally_supported(transport: &GatewayProviderTransport
|
||||
pub fn resolve_transport_tls_profile(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> Option<String> {
|
||||
transport
|
||||
.key
|
||||
.fingerprint
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("tls_profile"))
|
||||
resolve_transport_profile(transport).map(|profile| profile.profile_id)
|
||||
}
|
||||
|
||||
pub fn resolve_transport_profile(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> Option<ResolvedTransportProfile> {
|
||||
resolve_transport_profile_from_fingerprint(transport.key.fingerprint.as_ref()).or_else(|| {
|
||||
resolve_transport_profile_from_provider_config(transport.provider.config.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_transport_profile_from_provider_config(
|
||||
config: Option<&Value>,
|
||||
) -> Option<ResolvedTransportProfile> {
|
||||
let fingerprint = config?.get("fingerprint");
|
||||
resolve_transport_profile_from_fingerprint(fingerprint)
|
||||
}
|
||||
|
||||
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(ToOwned::to_owned)
|
||||
.map(ResolvedTransportProfile::from_legacy_tls_profile)
|
||||
}
|
||||
|
||||
fn parse_transport_profile_value(value: &Value) -> Option<ResolvedTransportProfile> {
|
||||
if let Some(profile_id) = value
|
||||
.as_str()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
return Some(ResolvedTransportProfile {
|
||||
profile_id: profile_id.to_string(),
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
let object = value.as_object()?;
|
||||
let profile_id = json_string_field(object, "profile_id")
|
||||
.or_else(|| json_string_field(object, "id"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let backend = json_string_field(object, "backend")
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| TRANSPORT_BACKEND_REQWEST_RUSTLS.to_string());
|
||||
let http_mode = json_string_field(object, "http_mode")
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| TRANSPORT_HTTP_MODE_AUTO.to_string());
|
||||
let pool_scope = json_string_field(object, "pool_scope")
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| TRANSPORT_POOL_SCOPE_KEY.to_string());
|
||||
let extra = object.get("extra").cloned();
|
||||
|
||||
Some(ResolvedTransportProfile {
|
||||
profile_id,
|
||||
backend,
|
||||
http_mode,
|
||||
pool_scope,
|
||||
extra,
|
||||
})
|
||||
}
|
||||
|
||||
fn effective_proxy_config(transport: &GatewayProviderTransportSnapshot) -> Option<&Value> {
|
||||
@@ -225,9 +296,10 @@ mod tests {
|
||||
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
|
||||
};
|
||||
use super::{
|
||||
resolve_transport_proxy_snapshot, resolve_transport_proxy_snapshot_with_tunnel_affinity,
|
||||
resolve_transport_tls_profile, transport_proxy_is_locally_supported,
|
||||
TransportTunnelAffinityLookup, TransportTunnelAttachmentOwner,
|
||||
resolve_transport_profile, resolve_transport_proxy_snapshot,
|
||||
resolve_transport_proxy_snapshot_with_tunnel_affinity, resolve_transport_tls_profile,
|
||||
transport_proxy_is_locally_supported, TransportTunnelAffinityLookup,
|
||||
TransportTunnelAttachmentOwner,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -391,4 +463,60 @@ mod tests {
|
||||
);
|
||||
assert!(transport_proxy_is_locally_supported(&sample_transport()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_transport_profile_from_key_fingerprint_before_provider_default() {
|
||||
let mut transport = sample_transport();
|
||||
transport.provider.config = Some(json!({
|
||||
"fingerprint": {"transport_profile": "provider_profile"}
|
||||
}));
|
||||
transport.key.fingerprint = Some(json!({
|
||||
"transport_profile": {
|
||||
"profile_id": "key_profile",
|
||||
"backend": "reqwest_rustls",
|
||||
"http_mode": "http1_only"
|
||||
}
|
||||
}));
|
||||
|
||||
let profile = resolve_transport_profile(&transport).expect("profile");
|
||||
|
||||
assert_eq!(profile.profile_id, "key_profile");
|
||||
assert_eq!(profile.backend, "reqwest_rustls");
|
||||
assert_eq!(profile.http_mode, "http1_only");
|
||||
assert_eq!(profile.pool_scope, "key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_transport_profile_from_provider_default() {
|
||||
let mut transport = sample_transport();
|
||||
transport.key.fingerprint = None;
|
||||
transport.provider.config = Some(json!({
|
||||
"fingerprint": {"transport_profile": "provider_profile"}
|
||||
}));
|
||||
|
||||
let profile = resolve_transport_profile(&transport).expect("profile");
|
||||
|
||||
assert_eq!(profile.profile_id, "provider_profile");
|
||||
assert_eq!(profile.backend, "reqwest_rustls");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_legacy_tls_profile_to_transport_profile() {
|
||||
let profile = resolve_transport_profile(&sample_transport()).expect("profile");
|
||||
|
||||
assert_eq!(profile.profile_id, "chrome_136");
|
||||
assert_eq!(profile.backend, "reqwest_rustls");
|
||||
assert_eq!(profile.http_mode, "auto");
|
||||
assert_eq!(profile.pool_scope, "key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_no_transport_profile_without_fingerprint_configuration() {
|
||||
let mut transport = sample_transport();
|
||||
transport.key.fingerprint = None;
|
||||
transport.provider.config = None;
|
||||
|
||||
assert!(resolve_transport_profile(&transport).is_none());
|
||||
assert!(resolve_transport_tls_profile(&transport).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::provider_types::{
|
||||
use super::snapshot::GatewayProviderTransportSnapshot;
|
||||
use super::{
|
||||
body_rules_are_locally_supported, header_rules_are_locally_supported,
|
||||
resolve_transport_tls_profile, supports_local_oauth_request_auth_resolution,
|
||||
resolve_transport_profile, supports_local_oauth_request_auth_resolution,
|
||||
transport_proxy_is_locally_supported,
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ 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_tls_profile(transport).is_none() {
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return Some("transport_tls_profile_unsupported");
|
||||
}
|
||||
if !provider_type_supports_local_openai_chat_transport(&transport.provider.provider_type) {
|
||||
@@ -175,8 +175,7 @@ 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_tls_profile(transport).is_none()
|
||||
{
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return Some("transport_tls_profile_unsupported");
|
||||
}
|
||||
} else if transport.provider.proxy.is_some()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::super::snapshot::GatewayProviderTransportSnapshot;
|
||||
use super::super::{
|
||||
body_rules_are_locally_supported, header_rules_are_locally_supported,
|
||||
resolve_transport_tls_profile, transport_proxy_is_locally_supported,
|
||||
resolve_transport_profile, resolve_transport_tls_profile, transport_proxy_is_locally_supported,
|
||||
};
|
||||
use super::auth::resolve_local_vertex_api_key_query_auth;
|
||||
|
||||
@@ -46,7 +46,7 @@ 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_tls_profile(transport).is_none() {
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return Some("transport_tls_profile_unsupported");
|
||||
}
|
||||
|
||||
@@ -133,8 +133,7 @@ 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_tls_profile(transport).is_none()
|
||||
{
|
||||
if transport.key.fingerprint.is_some() && resolve_transport_profile(transport).is_none() {
|
||||
return false;
|
||||
}
|
||||
} else if transport.provider.proxy.is_some()
|
||||
|
||||
@@ -818,7 +818,7 @@ mod tests {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ fn execution_plan(url: String, stream: bool) -> ExecutionPlan {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(2_000),
|
||||
read_ms: Some(10_000),
|
||||
@@ -608,6 +608,10 @@ fn relay_envelope() -> Vec<u8> {
|
||||
timeout: 30,
|
||||
follow_redirects: None,
|
||||
http1_only: false,
|
||||
provider_id: None,
|
||||
endpoint_id: None,
|
||||
key_id: None,
|
||||
transport_profile: None,
|
||||
};
|
||||
let meta_json = serde_json::to_vec(&meta).expect("hub relay metadata should serialize");
|
||||
let body = br#"{"model":"gpt-5","messages":[{"role":"user","content":"hello"}]}"#;
|
||||
|
||||
@@ -100,6 +100,10 @@ fn relay_envelope() -> Vec<u8> {
|
||||
timeout: 30,
|
||||
follow_redirects: None,
|
||||
http1_only: false,
|
||||
provider_id: None,
|
||||
endpoint_id: None,
|
||||
key_id: None,
|
||||
transport_profile: None,
|
||||
};
|
||||
let meta_json = serde_json::to_vec(&meta).expect("tunnel relay metadata should serialize");
|
||||
let body = br#"{"model":"gpt-5","messages":[{"role":"user","content":"hello"}]}"#;
|
||||
|
||||
@@ -386,7 +386,7 @@ fn execution_plan(url: String) -> ExecutionPlan {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(2_000),
|
||||
read_ms: Some(10_000),
|
||||
|
||||
@@ -256,6 +256,10 @@ fn relay_envelope() -> Vec<u8> {
|
||||
timeout: 30,
|
||||
follow_redirects: None,
|
||||
http1_only: false,
|
||||
provider_id: None,
|
||||
endpoint_id: None,
|
||||
key_id: None,
|
||||
transport_profile: None,
|
||||
};
|
||||
let meta_json = serde_json::to_vec(&meta).expect("owner relay metadata should serialize");
|
||||
let body = br#"{"model":"gpt-5","messages":[{"role":"user","content":"owner relay"}]}"#;
|
||||
|
||||
@@ -257,7 +257,7 @@ fn execution_plan(url: String, stream: bool) -> ExecutionPlan {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(2_000),
|
||||
read_ms: Some(10_000),
|
||||
|
||||
@@ -376,7 +376,7 @@ mod tests {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2483,7 +2483,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
|
||||
@@ -2550,7 +2550,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
|
||||
@@ -2595,7 +2595,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
|
||||
@@ -2652,7 +2652,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -2715,7 +2715,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewayStreamReportRequest {
|
||||
@@ -2810,7 +2810,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let mut standardized_usage = StandardizedUsage::new();
|
||||
@@ -2881,7 +2881,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let mut partial_summary_usage = StandardizedUsage::new();
|
||||
@@ -2987,7 +2987,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let sse_body = concat!(
|
||||
@@ -3111,7 +3111,7 @@ mod tests {
|
||||
provider_api_format: "gemini:generate_content".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3218,7 +3218,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3326,7 +3326,7 @@ mod tests {
|
||||
provider_api_format: "gemini:generate_content".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3398,7 +3398,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3461,7 +3461,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewayStreamReportRequest {
|
||||
@@ -3547,7 +3547,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewayStreamReportRequest {
|
||||
@@ -3613,7 +3613,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3667,7 +3667,7 @@ mod tests {
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
let payload = GatewaySyncReportRequest {
|
||||
@@ -3915,7 +3915,7 @@ mod tests {
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ impl GeminiVideoTaskSeed {
|
||||
provider_api_format: "gemini:video".to_string(),
|
||||
model_name: Some(self.model.clone()),
|
||||
proxy: self.transport.proxy.clone(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
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(),
|
||||
tls_profile: self.transport.tls_profile.clone(),
|
||||
transport_profile: None,
|
||||
timeouts: self.transport.timeouts.clone(),
|
||||
},
|
||||
report_kind: Some("openai_video_remix_sync_finalize".to_string()),
|
||||
|
||||
@@ -278,7 +278,7 @@ pub fn build_internal_finalize_video_plan(
|
||||
url: None,
|
||||
extra: None,
|
||||
}),
|
||||
tls_profile: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl LocalVideoTaskTransport {
|
||||
content_type: plan.content_type.clone(),
|
||||
model_name: plan.model_name.clone(),
|
||||
proxy: plan.proxy.clone(),
|
||||
tls_profile: plan.tls_profile.clone(),
|
||||
tls_profile: None,
|
||||
timeouts: plan.timeouts.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user