use aether_ai_formats::formats::matrix::{ api_data_format_id, request_conversion_kind, request_conversion_requires_enable_flag, RequestConversionKind, }; use aether_ai_formats::normalize_api_format_alias; use crate::auth::{ resolve_local_gemini_auth, resolve_local_openai_bearer_auth, resolve_local_standard_auth, }; use crate::kiro::{ is_kiro_claude_messages_transport, local_kiro_request_transport_unsupported_reason_with_network, }; use crate::policy::{ local_gemini_transport_unsupported_reason_with_network, local_openai_chat_transport_unsupported_reason, local_standard_transport_unsupported_reason_with_network, }; use crate::vertex::{ is_vertex_api_key_transport_context, is_vertex_transport_context, local_vertex_gemini_transport_unsupported_reason_with_network, resolve_local_vertex_api_key_query_auth, VERTEX_API_KEY_QUERY_PARAM, }; use crate::GatewayProviderTransportSnapshot; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CandidateTransportPolicyFacts<'a> { pub endpoint_api_format: &'a str, pub global_model_name: &'a str, pub selected_provider_model_name: &'a str, pub mapping_matched_model: Option<&'a str>, } pub fn request_conversion_enabled_for_transport( transport: &GatewayProviderTransportSnapshot, client_api_format: &str, provider_api_format: &str, ) -> bool { let client_api_format = normalize_api_format_alias(client_api_format); let provider_api_format = normalize_api_format_alias(provider_api_format); if client_api_format == provider_api_format { return true; } let conversion_kind = request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str()); if conversion_kind.is_none() && !same_data_format_transport_pair( client_api_format.as_str(), provider_api_format.as_str(), ) { return false; } if !request_conversion_requires_enable_flag( client_api_format.as_str(), provider_api_format.as_str(), ) { return true; } transport.provider.enable_format_conversion || endpoint_accepts_client_api_format(transport, client_api_format.as_str()) } pub fn request_pair_allowed_for_transport( transport: &GatewayProviderTransportSnapshot, client_api_format: &str, provider_api_format: &str, ) -> bool { let client_api_format = normalize_api_format_alias(client_api_format); let provider_api_format = normalize_api_format_alias(provider_api_format); if client_api_format == provider_api_format { return true; } let conversion_kind = request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str()); if conversion_kind.is_none() && !same_data_format_transport_pair( client_api_format.as_str(), provider_api_format.as_str(), ) { return false; } if is_kiro_claude_messages_transport(transport, &provider_api_format) { return request_conversion_enabled_for_transport( transport, client_api_format.as_str(), provider_api_format.as_str(), ) && local_kiro_request_transport_unsupported_reason_with_network(transport) .is_none(); } request_conversion_enabled_for_transport( transport, client_api_format.as_str(), provider_api_format.as_str(), ) } fn same_data_format_transport_pair(client_api_format: &str, provider_api_format: &str) -> bool { if aether_ai_formats::api_format_alias_matches(client_api_format, provider_api_format) { return false; } matches!( ( api_data_format_id(client_api_format), api_data_format_id(provider_api_format) ), (Some("embedding"), Some("embedding")) | (Some("rerank"), Some("rerank")) ) } pub fn request_conversion_transport_supported( transport: &GatewayProviderTransportSnapshot, kind: RequestConversionKind, ) -> bool { request_conversion_transport_unsupported_reason(transport, kind).is_none() } pub fn request_conversion_transport_unsupported_reason( transport: &GatewayProviderTransportSnapshot, _kind: RequestConversionKind, ) -> Option<&'static str> { if is_kiro_claude_messages_transport(transport, &transport.endpoint.api_format) { return local_kiro_request_transport_unsupported_reason_with_network(transport); } match normalize_api_format_alias(&transport.endpoint.api_format).as_str() { "openai:chat" => local_openai_chat_transport_unsupported_reason(transport), "openai:responses" | "openai:responses:compact" => { local_standard_transport_unsupported_reason_with_network( transport, transport.endpoint.api_format.trim(), ) } "claude:messages" => { local_standard_transport_unsupported_reason_with_network(transport, "claude:messages") } "gemini:generate_content" if is_vertex_transport_context(transport) => { local_vertex_gemini_transport_unsupported_reason_with_network(transport) } "gemini:generate_content" => local_gemini_transport_unsupported_reason_with_network( transport, "gemini:generate_content", ), _ => Some("transport_api_format_unsupported"), } } pub fn request_pair_transport_unsupported_reason( transport: &GatewayProviderTransportSnapshot, client_api_format: &str, provider_api_format: &str, ) -> Option<&'static str> { let client_api_format = normalize_api_format_alias(client_api_format); let provider_api_format = normalize_api_format_alias(provider_api_format); if let Some(kind) = request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str()) { return request_conversion_transport_unsupported_reason(transport, kind); } if !same_data_format_transport_pair(client_api_format.as_str(), provider_api_format.as_str()) { return Some("transport_api_format_unsupported"); } match provider_api_format.as_str() { "gemini:embedding" => { if is_vertex_transport_context(transport) { local_vertex_gemini_transport_unsupported_reason_with_network(transport) } else { local_gemini_transport_unsupported_reason_with_network( transport, "gemini:embedding", ) } } "openai:embedding" | "jina:embedding" | "doubao:embedding" | "openai:rerank" | "jina:rerank" => local_standard_transport_unsupported_reason_with_network( transport, provider_api_format.as_str(), ), _ => Some("transport_api_format_unsupported"), } } pub fn request_conversion_direct_auth( transport: &GatewayProviderTransportSnapshot, _kind: RequestConversionKind, ) -> Option<(String, String)> { request_direct_auth_for_provider_format(transport, transport.endpoint.api_format.as_str()) } pub fn request_pair_direct_auth( transport: &GatewayProviderTransportSnapshot, provider_api_format: &str, ) -> Option<(String, String)> { request_direct_auth_for_provider_format(transport, provider_api_format) } fn request_direct_auth_for_provider_format( transport: &GatewayProviderTransportSnapshot, provider_api_format: &str, ) -> Option<(String, String)> { match normalize_api_format_alias(provider_api_format).as_str() { "openai:chat" | "openai:responses" | "openai:responses:compact" | "openai:embedding" | "jina:embedding" | "doubao:embedding" | "openai:rerank" | "jina:rerank" => resolve_local_openai_bearer_auth(transport), "gemini:generate_content" | "gemini:embedding" => { if is_vertex_api_key_transport_context(transport) { resolve_local_vertex_api_key_query_auth(transport) .map(|auth| (VERTEX_API_KEY_QUERY_PARAM.to_string(), auth.value)) } else { resolve_local_gemini_auth(transport) } } "claude:messages" => resolve_local_standard_auth(transport), _ => None, } } pub fn candidate_common_transport_skip_reason( transport: &GatewayProviderTransportSnapshot, candidate: CandidateTransportPolicyFacts<'_>, requested_model: Option<&str>, ) -> Option<&'static str> { let requested_model = requested_model.unwrap_or_default(); if !transport.provider.is_active { return Some("provider_inactive"); } if !transport.endpoint.is_active { return Some("endpoint_inactive"); } if !transport.key.is_active { return Some("key_inactive"); } let endpoint_api_format = transport.endpoint.api_format.trim(); if !aether_ai_formats::api_format_alias_matches( candidate.endpoint_api_format, endpoint_api_format, ) { return Some("endpoint_api_format_changed"); } if !transport_key_supports_api_format(transport, endpoint_api_format) { return Some("key_api_format_disabled"); } if !transport_key_allows_candidate_model(transport, requested_model, candidate) { return Some("key_model_disabled"); } None } pub fn candidate_transport_pair_skip_reason( transport: &GatewayProviderTransportSnapshot, normalized_client_api_format: &str, ) -> Option<&'static str> { let endpoint_api_format = transport.endpoint.api_format.trim(); if aether_ai_formats::api_format_alias_matches( endpoint_api_format, normalized_client_api_format, ) { return None; } if let Some(skip_reason) = disabled_format_conversion_skip_reason(transport, normalized_client_api_format) { return Some(skip_reason); } if !request_pair_allowed_for_transport( transport, normalized_client_api_format, endpoint_api_format, ) { return Some("transport_unsupported"); } None } fn disabled_format_conversion_skip_reason( transport: &GatewayProviderTransportSnapshot, normalized_client_api_format: &str, ) -> Option<&'static str> { let endpoint_api_format = transport.endpoint.api_format.trim(); if aether_ai_formats::api_format_alias_matches( endpoint_api_format, normalized_client_api_format, ) { return None; } request_conversion_kind(normalized_client_api_format, endpoint_api_format)?; if request_conversion_requires_enable_flag(normalized_client_api_format, endpoint_api_format) && !request_conversion_enabled_for_transport( transport, normalized_client_api_format, endpoint_api_format, ) { return Some("format_conversion_disabled"); } None } fn transport_key_supports_api_format( transport: &GatewayProviderTransportSnapshot, endpoint_api_format: &str, ) -> bool { let inherits_provider_api_formats = crate::provider_types::fixed_provider_key_inherits_api_formats( transport.provider.provider_type.as_str(), transport.key.auth_type.as_str(), transport.key.decrypted_auth_config.as_deref(), ); if inherits_provider_api_formats { return true; } match transport.key.api_formats.as_deref() { None => true, Some(formats) => formats .iter() .any(|value| aether_ai_formats::api_format_alias_matches(value, endpoint_api_format)), } } fn transport_key_allows_candidate_model( transport: &GatewayProviderTransportSnapshot, requested_model: &str, candidate: CandidateTransportPolicyFacts<'_>, ) -> bool { let Some(allowed_models) = transport.key.allowed_models.as_deref() else { return true; }; let requested_model = requested_model.trim(); let global_model_name = candidate.global_model_name.trim(); let selected_provider_model_name = candidate.selected_provider_model_name.trim(); let mapping_matched_model = candidate .mapping_matched_model .map(str::trim) .filter(|value| !value.is_empty()); let requested_base_model = aether_ai_formats::model_directive_base_model(requested_model); for allowed_model in allowed_models.iter().map(String::as_str).map(str::trim) { if allowed_model.is_empty() { continue; } if allowed_model == requested_model || requested_base_model .as_deref() .is_some_and(|base_model| allowed_model == base_model) || allowed_model == global_model_name || allowed_model == selected_provider_model_name || mapping_matched_model.is_some_and(|value| value == allowed_model) { return true; } } false } fn endpoint_accepts_client_api_format( transport: &GatewayProviderTransportSnapshot, client_api_format: &str, ) -> bool { let Some(config) = transport .endpoint .format_acceptance_config .as_ref() .and_then(serde_json::Value::as_object) else { return false; }; if !config .get("enabled") .and_then(serde_json::Value::as_bool) .unwrap_or(false) { return false; } if config .get("reject_formats") .is_some_and(|value| json_format_list_contains(value, client_api_format)) { return false; } match config.get("accept_formats") { Some(value) => json_format_list_contains(value, client_api_format), None => true, } } fn json_format_list_contains(value: &serde_json::Value, api_format: &str) -> bool { let Some(items) = value.as_array() else { return false; }; items.iter().any(|item| { item.as_str().is_some_and(|candidate| { aether_ai_formats::api_format_alias_matches(candidate, api_format) }) }) } #[cfg(test)] mod tests { use super::{ candidate_common_transport_skip_reason, candidate_transport_pair_skip_reason, request_conversion_direct_auth, request_conversion_enabled_for_transport, request_conversion_transport_supported, request_pair_allowed_for_transport, request_pair_direct_auth, CandidateTransportPolicyFacts, }; use aether_ai_formats::formats::matrix::RequestConversionKind; use serde_json::json; use crate::snapshot::{ GatewayProviderTransportEndpoint, GatewayProviderTransportKey, GatewayProviderTransportProvider, GatewayProviderTransportSnapshot, }; fn transport_snapshot( provider_type: &str, api_format: &str, auth_type: &str, enable_format_conversion: bool, format_acceptance_config: Option, ) -> GatewayProviderTransportSnapshot { GatewayProviderTransportSnapshot { provider: GatewayProviderTransportProvider { id: format!("provider-{provider_type}"), name: provider_type.to_string(), provider_type: provider_type.to_string(), website: None, is_active: true, keep_priority_on_conversion: false, enable_format_conversion, concurrent_limit: None, max_retries: None, proxy: None, request_timeout_secs: None, stream_first_byte_timeout_secs: None, config: None, }, endpoint: GatewayProviderTransportEndpoint { id: "endpoint-1".to_string(), provider_id: format!("provider-{provider_type}"), api_format: api_format.to_string(), api_family: None, endpoint_kind: None, is_active: true, base_url: if provider_type == "kiro" { "https://q.{region}.amazonaws.com".to_string() } else if provider_type == "vertex_ai" { "https://aiplatform.googleapis.com".to_string() } else { "https://api.example.com".to_string() }, header_rules: None, body_rules: None, max_retries: None, custom_path: None, config: None, format_acceptance_config, proxy: None, }, key: GatewayProviderTransportKey { id: "key-1".to_string(), provider_id: format!("provider-{provider_type}"), name: "key".to_string(), auth_type: auth_type.to_string(), is_active: true, api_formats: Some(vec![api_format.to_string()]), auth_type_by_format: None, allow_auth_channel_mismatch_formats: None, allowed_models: None, capabilities: None, rate_multipliers: None, global_priority_by_format: None, expires_at_unix_secs: None, proxy: None, fingerprint: None, decrypted_api_key: "secret".to_string(), decrypted_auth_config: None, }, } } fn candidate_facts(api_format: &str) -> CandidateTransportPolicyFacts<'_> { CandidateTransportPolicyFacts { endpoint_api_format: api_format, global_model_name: "gpt-4.1", selected_provider_model_name: "provider-gpt-4.1", mapping_matched_model: Some("gpt-4.1-mini"), } } #[test] fn conversion_helpers_follow_transport_api_format() { let transport = transport_snapshot("openai", "openai:chat", "bearer", true, None); assert!(request_conversion_transport_supported( &transport, RequestConversionKind::ToOpenAIChat )); assert_eq!( request_conversion_direct_auth(&transport, RequestConversionKind::ToOpenAIChat), Some(("authorization".to_string(), "Bearer secret".to_string())) ); } #[test] fn endpoint_level_format_acceptance_enables_cross_format_pair_without_provider_flag() { let transport = transport_snapshot( "custom", "openai:responses", "bearer", false, Some(json!({ "enabled": true, "accept_formats": ["claude:messages"], })), ); assert!(request_conversion_enabled_for_transport( &transport, "claude:messages", "openai:responses" )); assert!(request_pair_allowed_for_transport( &transport, "claude:messages", "openai:responses" )); assert!(!request_pair_allowed_for_transport( &transport, "gemini:generate_content", "openai:responses" )); } #[test] fn endpoint_reject_formats_override_endpoint_cross_format_enablement() { let transport = transport_snapshot( "custom", "openai:responses", "bearer", false, Some(json!({ "enabled": true, "reject_formats": ["claude:messages"], })), ); assert!(!request_conversion_enabled_for_transport( &transport, "claude:messages", "openai:responses" )); } #[test] fn vertex_gemini_transport_supports_cross_format_conversion_with_query_auth() { let transport = transport_snapshot( "vertex_ai", "gemini:generate_content", "api_key", true, None, ); assert!(request_conversion_transport_supported( &transport, RequestConversionKind::ToGeminiStandard )); assert_eq!( request_conversion_direct_auth(&transport, RequestConversionKind::ToGeminiStandard), Some(("key".to_string(), "secret".to_string())) ); } #[test] fn vertex_gemini_embedding_transport_supports_openai_embedding_conversion() { let transport = transport_snapshot("vertex_ai", "gemini:embedding", "api_key", true, None); assert!(request_pair_allowed_for_transport( &transport, "openai:embedding", "gemini:embedding" )); assert_eq!( request_pair_direct_auth(&transport, "gemini:embedding"), Some(("key".to_string(), "secret".to_string())) ); } #[test] fn kiro_claude_messages_transport_supports_cross_format_conversion_via_envelope() { let transport = transport_snapshot("kiro", "claude:messages", "bearer", true, None); assert!(request_pair_allowed_for_transport( &transport, "openai:chat", "claude:messages" )); assert!(request_conversion_transport_supported( &transport, RequestConversionKind::ToClaudeStandard )); } #[test] fn candidate_common_transport_policy_checks_active_state_format_and_allowed_models() { let mut transport = transport_snapshot("custom", "openai:chat", "bearer", true, None); transport.key.allowed_models = Some(vec!["gpt-4.1-mini".to_string()]); assert_eq!( candidate_common_transport_skip_reason( &transport, candidate_facts("openai:chat"), Some("gpt-4.1") ), None ); assert_eq!( candidate_common_transport_skip_reason( &transport, candidate_facts("claude:messages"), Some("gpt-4.1") ), Some("endpoint_api_format_changed") ); transport.key.allowed_models = Some(vec!["other-model".to_string()]); assert_eq!( candidate_common_transport_skip_reason( &transport, candidate_facts("openai:chat"), Some("gpt-4.1") ), Some("key_model_disabled") ); } #[test] fn candidate_common_transport_policy_allows_model_directive_base_model() { let mut transport = transport_snapshot("custom", "openai:responses", "bearer", true, None); transport.key.allowed_models = Some(vec!["gpt-5.5".to_string()]); assert_eq!( candidate_common_transport_skip_reason( &transport, CandidateTransportPolicyFacts { endpoint_api_format: "openai:responses", global_model_name: "gpt-5", selected_provider_model_name: "provider-gpt-5", mapping_matched_model: None, }, Some("gpt-5.5-xhigh"), ), None ); } #[test] fn fixed_provider_oauth_keys_inherit_endpoint_api_formats_for_candidate_policy() { let mut transport = transport_snapshot("codex", "openai:responses", "oauth", true, None); transport.key.api_formats = Some(vec!["openai:image".to_string()]); assert_eq!( candidate_common_transport_skip_reason( &transport, candidate_facts("openai:responses"), None, ), None ); } #[test] fn candidate_transport_pair_policy_reports_disabled_conversion_and_unsupported_pairs() { let transport = transport_snapshot("custom", "openai:responses", "bearer", false, None); assert_eq!( candidate_transport_pair_skip_reason(&transport, "openai:chat"), Some("format_conversion_disabled") ); assert_eq!( candidate_transport_pair_skip_reason(&transport, "gemini:video"), Some("transport_unsupported") ); let enabled_transport = transport_snapshot("custom", "openai:responses", "bearer", true, None); assert_eq!( candidate_transport_pair_skip_reason(&enabled_transport, "openai:chat"), None ); } }