Refactor provider transport modules

This commit is contained in:
fawney19
2026-05-08 02:34:45 +08:00
parent c52ef1993f
commit 080784cd9f
15 changed files with 307 additions and 72 deletions

View File

@@ -12,6 +12,7 @@ use aether_scheduler_core::{
ClientSessionAffinity, SchedulerMinimalCandidateSelectionCandidate, SchedulerRankingOutcome, ClientSessionAffinity, SchedulerMinimalCandidateSelectionCandidate, SchedulerRankingOutcome,
}; };
use crate::ai_serving::transport::provider_types::provider_runtime_policy;
use crate::ai_serving::{ use crate::ai_serving::{
candidate_common_transport_skip_reason, candidate_transport_pair_skip_reason, candidate_common_transport_skip_reason, candidate_transport_pair_skip_reason,
CandidateTransportPolicyFacts, GatewayAuthApiKeySnapshot, GatewayProviderTransportSnapshot, CandidateTransportPolicyFacts, GatewayAuthApiKeySnapshot, GatewayProviderTransportSnapshot,
@@ -398,6 +399,8 @@ pub(crate) fn candidate_auth_channel_skip_reason(
let request_auth_channel = normalize_request_auth_channel(request_auth_channel?)?; let request_auth_channel = normalize_request_auth_channel(request_auth_channel?)?;
let upstream_auth_channel = resolve_transport_request_auth_channel(transport)?; let upstream_auth_channel = resolve_transport_request_auth_channel(transport)?;
if request_auth_channel == upstream_auth_channel if request_auth_channel == upstream_auth_channel
|| provider_runtime_policy(&transport.provider.provider_type)
.allow_auth_channel_mismatch_by_default
|| allow_auth_channel_mismatch_for_format(transport) || allow_auth_channel_mismatch_for_format(transport)
{ {
None None
@@ -418,12 +421,11 @@ fn resolve_transport_request_auth_channel(
transport: &GatewayProviderTransportSnapshot, transport: &GatewayProviderTransportSnapshot,
) -> Option<&'static str> { ) -> Option<&'static str> {
let auth_type = resolve_transport_auth_type_for_endpoint_format(transport); let auth_type = resolve_transport_auth_type_for_endpoint_format(transport);
let provider_policy = provider_runtime_policy(&transport.provider.provider_type);
match auth_type.as_str() { match auth_type.as_str() {
"api_key" => Some("api_key"), "api_key" => Some("api_key"),
"bearer" => Some("bearer_like"), "bearer" => Some("bearer_like"),
"oauth" if provider_uses_bearer_like_oauth(&transport.provider.provider_type) => { "oauth" if provider_policy.oauth_is_bearer_like => Some("bearer_like"),
Some("bearer_like")
}
_ => None, _ => None,
} }
} }
@@ -450,13 +452,6 @@ fn resolve_transport_auth_type_for_endpoint_format(
.unwrap_or(default_auth_type) .unwrap_or(default_auth_type)
} }
fn provider_uses_bearer_like_oauth(provider_type: &str) -> bool {
matches!(
provider_type.trim().to_ascii_lowercase().as_str(),
"claude_code" | "chatgpt_web" | "gemini_cli" | "antigravity" | "kiro"
)
}
fn allow_auth_channel_mismatch_for_format(transport: &GatewayProviderTransportSnapshot) -> bool { fn allow_auth_channel_mismatch_for_format(transport: &GatewayProviderTransportSnapshot) -> bool {
let api_format = crate::ai_serving::normalize_api_format_alias(&transport.endpoint.api_format); let api_format = crate::ai_serving::normalize_api_format_alias(&transport.endpoint.api_format);
transport transport
@@ -622,6 +617,16 @@ mod tests {
); );
} }
#[test]
fn auth_channel_gate_allows_kiro_provider_mismatch_by_default() {
let mut transport = sample_transport("oauth");
transport.provider.provider_type = "kiro".to_string();
assert_eq!(
candidate_auth_channel_skip_reason(&transport, Some("api_key")),
None
);
}
#[test] #[test]
fn pool_group_common_gate_ignores_representative_key_model_policy() { fn pool_group_common_gate_ignores_representative_key_model_policy() {
let candidate = sample_candidate(); let candidate = sample_candidate();

View File

@@ -2008,7 +2008,7 @@ fn ai_serving_video_routes_request_preparation_through_request_payload_seams() {
} }
let provider_transport_video = let provider_transport_video =
read_workspace_file("crates/aether-provider-transport/src/video.rs"); read_workspace_file("crates/aether-provider-transport/src/video/mod.rs");
for pattern in [ for pattern in [
"pub enum ProviderVideoCreateFamily", "pub enum ProviderVideoCreateFamily",
"pub fn video_create_transport_unsupported_reason(", "pub fn video_create_transport_unsupported_reason(",
@@ -2083,7 +2083,7 @@ fn ai_serving_files_routes_request_preparation_through_request_payload_seams() {
} }
let provider_transport_files = let provider_transport_files =
read_workspace_file("crates/aether-provider-transport/src/gemini_files.rs"); read_workspace_file("crates/aether-provider-transport/src/gemini_files/mod.rs");
for pattern in [ for pattern in [
"pub fn gemini_files_transport_unsupported_reason(", "pub fn gemini_files_transport_unsupported_reason(",
"pub fn resolve_gemini_files_auth(", "pub fn resolve_gemini_files_auth(",
@@ -2169,7 +2169,7 @@ fn ai_serving_image_routes_split_surface_normalization_and_transport_policy() {
} }
let provider_transport_image = let provider_transport_image =
read_workspace_file("crates/aether-provider-transport/src/openai_image.rs"); read_workspace_file("crates/aether-provider-transport/src/openai_image/mod.rs");
for pattern in [ for pattern in [
"pub fn openai_image_transport_unsupported_reason(", "pub fn openai_image_transport_unsupported_reason(",
"pub fn resolve_openai_image_auth(", "pub fn resolve_openai_image_auth(",
@@ -2622,7 +2622,7 @@ fn ai_serving_standard_attempts_consume_eligible_local_candidates_without_transp
} }
let provider_transport_standard = let provider_transport_standard =
read_workspace_file("crates/aether-provider-transport/src/standard.rs"); read_workspace_file("crates/aether-provider-transport/src/standard/mod.rs");
for pattern in [ for pattern in [
"pub struct StandardProviderRequestHeadersInput", "pub struct StandardProviderRequestHeadersInput",
"pub struct StandardProviderRequestHeaders", "pub struct StandardProviderRequestHeaders",
@@ -2642,7 +2642,7 @@ fn ai_serving_standard_attempts_consume_eligible_local_candidates_without_transp
} }
let provider_transport_request_url = let provider_transport_request_url =
read_workspace_file("crates/aether-provider-transport/src/request_url.rs"); read_workspace_file("crates/aether-provider-transport/src/request_url/mod.rs");
assert!( assert!(
provider_transport_request_url.contains("pub fn build_kiro_cross_format_upstream_url("), provider_transport_request_url.contains("pub fn build_kiro_cross_format_upstream_url("),
"provider-transport request_url.rs should own Kiro cross-format URL hook" "provider-transport request_url.rs should own Kiro cross-format URL hook"
@@ -2704,7 +2704,7 @@ fn ai_serving_standard_plan_builders_delegate_fallback_transport_policy() {
} }
let provider_transport_standard = let provider_transport_standard =
read_workspace_file("crates/aether-provider-transport/src/standard.rs"); read_workspace_file("crates/aether-provider-transport/src/standard/mod.rs");
for pattern in [ for pattern in [
"pub enum StandardPlanFallbackAcceptPolicy", "pub enum StandardPlanFallbackAcceptPolicy",
"pub struct StandardPlanFallbackHeadersInput", "pub struct StandardPlanFallbackHeadersInput",
@@ -2972,7 +2972,7 @@ fn ai_serving_same_format_provider_request_policy_owns_provider_type_behavior()
"apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request/policy.rs", "apps/aether-gateway/src/ai_serving/planner/passthrough/provider/family/request/policy.rs",
); );
let provider_transport_policy = let provider_transport_policy =
read_workspace_file("crates/aether-provider-transport/src/same_format_provider.rs"); read_workspace_file("crates/aether-provider-transport/src/same_format_provider/mod.rs");
for pattern in [ for pattern in [
"pub struct SameFormatProviderRequestBehavior {", "pub struct SameFormatProviderRequestBehavior {",
"pub struct SameFormatProviderRequestBodyInput", "pub struct SameFormatProviderRequestBodyInput",

View File

@@ -7,6 +7,22 @@ mod refresh;
mod request; mod request;
mod url; mod url;
use crate::provider_types::{
ProviderApiFormatInheritance, ProviderLocalEmbeddingSupport, ProviderRuntimePolicy,
};
pub const RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuthOrConfiguredBearer,
enable_format_conversion_by_default: true,
allow_auth_channel_mismatch_by_default: true,
oauth_is_bearer_like: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
supports_local_same_format_transport: false,
local_embedding_support: ProviderLocalEmbeddingSupport::None,
};
pub use auth::{ pub use auth::{
build_kiro_request_auth_from_config, is_kiro_claude_messages_transport, build_kiro_request_auth_from_config, is_kiro_claude_messages_transport,
is_kiro_provider_transport, resolve_local_kiro_bearer_auth, resolve_local_kiro_request_auth, is_kiro_provider_transport, resolve_local_kiro_bearer_auth, resolve_local_kiro_request_auth,

View File

@@ -42,12 +42,117 @@ pub struct FixedProviderEndpointTemplate {
pub config_defaults: &'static [FixedProviderEndpointConfigDefault], pub config_defaults: &'static [FixedProviderEndpointConfigDefault],
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderApiFormatInheritance {
None,
OAuth,
OAuthOrBearer,
OAuthOrConfiguredBearer,
}
impl ProviderApiFormatInheritance {
pub fn key_inherits_api_formats(
self,
auth_type: &str,
decrypted_auth_config: Option<&str>,
) -> bool {
let auth_type = auth_type.trim().to_ascii_lowercase();
match self {
Self::None => false,
Self::OAuth => auth_type == "oauth",
Self::OAuthOrBearer => auth_type == "oauth" || auth_type == "bearer",
Self::OAuthOrConfiguredBearer => {
auth_type == "oauth"
|| auth_type == "bearer"
&& decrypted_auth_config
.map(str::trim)
.is_some_and(|value| !value.is_empty())
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderLocalEmbeddingSupport {
None,
AnyKnown,
OpenAi,
Gemini,
Jina,
Doubao,
}
impl ProviderLocalEmbeddingSupport {
pub fn supports_api_format(self, api_format: &str) -> bool {
let api_format = aether_ai_formats::normalize_api_format_alias(api_format);
match self {
Self::None => false,
Self::AnyKnown => matches!(
api_format.as_str(),
"openai:embedding"
| "openai:rerank"
| "gemini:embedding"
| "jina:embedding"
| "jina:rerank"
| "doubao:embedding"
),
Self::OpenAi => matches!(api_format.as_str(), "openai:embedding" | "openai:rerank"),
Self::Gemini => api_format == "gemini:embedding",
Self::Jina => matches!(api_format.as_str(), "jina:embedding" | "jina:rerank"),
Self::Doubao => api_format == "doubao:embedding",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProviderRuntimePolicy {
pub fixed_provider: bool,
pub api_format_inheritance: ProviderApiFormatInheritance,
pub enable_format_conversion_by_default: bool,
pub allow_auth_channel_mismatch_by_default: bool,
pub oauth_is_bearer_like: bool,
pub supports_model_fetch: bool,
pub supports_local_openai_chat_transport: bool,
pub supports_local_same_format_transport: bool,
pub local_embedding_support: ProviderLocalEmbeddingSupport,
}
impl ProviderRuntimePolicy {
pub const fn standard() -> Self {
Self {
fixed_provider: false,
api_format_inheritance: ProviderApiFormatInheritance::None,
enable_format_conversion_by_default: false,
allow_auth_channel_mismatch_by_default: false,
oauth_is_bearer_like: false,
supports_model_fetch: true,
supports_local_openai_chat_transport: true,
supports_local_same_format_transport: true,
local_embedding_support: ProviderLocalEmbeddingSupport::None,
}
}
pub fn key_inherits_api_formats(
self,
auth_type: &str,
decrypted_auth_config: Option<&str>,
) -> bool {
self.api_format_inheritance
.key_inherits_api_formats(auth_type, decrypted_auth_config)
}
pub fn supports_local_embedding_transport(self, api_format: &str) -> bool {
self.local_embedding_support.supports_api_format(api_format)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FixedProviderTemplate { pub struct FixedProviderTemplate {
pub provider_type: &'static str, pub provider_type: &'static str,
pub version: u32, pub version: u32,
pub base_url: &'static str, pub base_url: &'static str,
pub endpoints: &'static [FixedProviderEndpointTemplate], pub endpoints: &'static [FixedProviderEndpointTemplate],
pub runtime_policy: ProviderRuntimePolicy,
} }
const EMPTY_ENDPOINT_CONFIG_DEFAULTS: &[FixedProviderEndpointConfigDefault] = &[]; const EMPTY_ENDPOINT_CONFIG_DEFAULTS: &[FixedProviderEndpointConfigDefault] = &[];
@@ -57,6 +162,83 @@ const FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS: &[FixedProviderEndpointConfigDefaul
value: FixedProviderEndpointConfigValue::String("force_stream"), value: FixedProviderEndpointConfigValue::String("force_stream"),
}]; }];
const STANDARD_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy::standard();
const CUSTOM_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
local_embedding_support: ProviderLocalEmbeddingSupport::AnyKnown,
..STANDARD_RUNTIME_POLICY
};
const OPENAI_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
local_embedding_support: ProviderLocalEmbeddingSupport::OpenAi,
..STANDARD_RUNTIME_POLICY
};
const GEMINI_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
local_embedding_support: ProviderLocalEmbeddingSupport::Gemini,
..STANDARD_RUNTIME_POLICY
};
const JINA_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
local_embedding_support: ProviderLocalEmbeddingSupport::Jina,
..STANDARD_RUNTIME_POLICY
};
const DOUBAO_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
local_embedding_support: ProviderLocalEmbeddingSupport::Doubao,
..STANDARD_RUNTIME_POLICY
};
const CLAUDE_CODE_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuth,
enable_format_conversion_by_default: true,
oauth_is_bearer_like: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
supports_local_same_format_transport: false,
..STANDARD_RUNTIME_POLICY
};
const CODEX_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuth,
enable_format_conversion_by_default: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
..STANDARD_RUNTIME_POLICY
};
const CHATGPT_WEB_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuthOrBearer,
enable_format_conversion_by_default: true,
oauth_is_bearer_like: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
supports_local_same_format_transport: false,
..STANDARD_RUNTIME_POLICY
};
const GEMINI_CLI_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuth,
oauth_is_bearer_like: true,
supports_local_openai_chat_transport: false,
..STANDARD_RUNTIME_POLICY
};
const VERTEX_AI_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuth,
enable_format_conversion_by_default: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
supports_local_same_format_transport: false,
..STANDARD_RUNTIME_POLICY
};
const ANTIGRAVITY_RUNTIME_POLICY: ProviderRuntimePolicy = ProviderRuntimePolicy {
fixed_provider: true,
api_format_inheritance: ProviderApiFormatInheritance::OAuth,
enable_format_conversion_by_default: true,
oauth_is_bearer_like: true,
supports_model_fetch: false,
supports_local_openai_chat_transport: false,
supports_local_same_format_transport: false,
..STANDARD_RUNTIME_POLICY
};
const CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
provider_type: "claude_code", provider_type: "claude_code",
version: 1, version: 1,
@@ -67,6 +249,7 @@ const CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProvider
custom_path: None, custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS, config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
}], }],
runtime_policy: CLAUDE_CODE_RUNTIME_POLICY,
}; };
const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -93,6 +276,7 @@ const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTempla
config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS, config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS,
}, },
], ],
runtime_policy: CODEX_RUNTIME_POLICY,
}; };
const CHATGPT_WEB_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const CHATGPT_WEB_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -105,6 +289,7 @@ const CHATGPT_WEB_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProvider
custom_path: None, custom_path: None,
config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS, config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS,
}], }],
runtime_policy: CHATGPT_WEB_RUNTIME_POLICY,
}; };
const KIRO_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const KIRO_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -117,6 +302,7 @@ const KIRO_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplat
custom_path: None, custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS, config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
}], }],
runtime_policy: crate::kiro::RUNTIME_POLICY,
}; };
const GEMINI_CLI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const GEMINI_CLI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -129,6 +315,7 @@ const GEMINI_CLI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderT
custom_path: None, custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS, config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
}], }],
runtime_policy: GEMINI_CLI_RUNTIME_POLICY,
}; };
const VERTEX_AI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const VERTEX_AI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -149,6 +336,7 @@ const VERTEX_AI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTe
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS, config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
}, },
], ],
runtime_policy: VERTEX_AI_RUNTIME_POLICY,
}; };
const ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate { const ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplate {
@@ -161,19 +349,11 @@ const ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProvider
custom_path: None, custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS, config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
}], }],
runtime_policy: ANTIGRAVITY_RUNTIME_POLICY,
}; };
pub fn provider_type_is_fixed(provider_type: &str) -> bool { pub fn provider_type_is_fixed(provider_type: &str) -> bool {
matches!( provider_runtime_policy(provider_type).fixed_provider
provider_type.trim().to_ascii_lowercase().as_str(),
"claude_code"
| "kiro"
| "codex"
| "chatgpt_web"
| "gemini_cli"
| "antigravity"
| "vertex_ai"
)
} }
pub fn fixed_provider_key_inherits_api_formats( pub fn fixed_provider_key_inherits_api_formats(
@@ -181,23 +361,35 @@ pub fn fixed_provider_key_inherits_api_formats(
auth_type: &str, auth_type: &str,
decrypted_auth_config: Option<&str>, decrypted_auth_config: Option<&str>,
) -> bool { ) -> bool {
let provider_type = provider_type.trim().to_ascii_lowercase(); provider_runtime_policy(provider_type)
let auth_type = auth_type.trim().to_ascii_lowercase(); .key_inherits_api_formats(auth_type, decrypted_auth_config)
provider_type_is_fixed(&provider_type)
&& (auth_type == "oauth"
|| provider_type == "chatgpt_web" && auth_type == "bearer"
|| provider_type == "kiro"
&& auth_type == "bearer"
&& decrypted_auth_config
.map(str::trim)
.is_some_and(|value| !value.is_empty()))
} }
pub fn provider_type_enables_format_conversion_by_default(provider_type: &str) -> bool { pub fn provider_type_enables_format_conversion_by_default(provider_type: &str) -> bool {
matches!( provider_runtime_policy(provider_type).enable_format_conversion_by_default
provider_type.trim().to_ascii_lowercase().as_str(), }
"claude_code" | "kiro" | "codex" | "chatgpt_web" | "antigravity" | "vertex_ai"
) pub fn provider_type_allows_auth_channel_mismatch_by_default(provider_type: &str) -> bool {
provider_runtime_policy(provider_type).allow_auth_channel_mismatch_by_default
}
pub fn provider_type_oauth_is_bearer_like(provider_type: &str) -> bool {
provider_runtime_policy(provider_type).oauth_is_bearer_like
}
pub fn provider_runtime_policy(provider_type: &str) -> ProviderRuntimePolicy {
if let Some(template) = fixed_provider_template(provider_type) {
return template.runtime_policy;
}
match provider_type.trim().to_ascii_lowercase().as_str() {
"custom" => CUSTOM_RUNTIME_POLICY,
"openai" => OPENAI_RUNTIME_POLICY,
"gemini" | "google" => GEMINI_RUNTIME_POLICY,
"jina" => JINA_RUNTIME_POLICY,
"doubao" | "volcengine" => DOUBAO_RUNTIME_POLICY,
_ => STANDARD_RUNTIME_POLICY,
}
} }
pub fn fixed_provider_template(provider_type: &str) -> Option<&'static FixedProviderTemplate> { pub fn fixed_provider_template(provider_type: &str) -> Option<&'static FixedProviderTemplate> {
@@ -225,48 +417,22 @@ pub fn fixed_provider_endpoint_template_by_api_format(
} }
pub fn provider_type_supports_model_fetch(provider_type: &str) -> bool { pub fn provider_type_supports_model_fetch(provider_type: &str) -> bool {
!matches!( provider_runtime_policy(provider_type).supports_model_fetch
provider_type.trim().to_ascii_lowercase().as_str(),
"vertex_ai" | "antigravity" | "codex" | "chatgpt_web" | "kiro" | "claude_code"
)
} }
pub fn provider_type_supports_local_openai_chat_transport(provider_type: &str) -> bool { pub fn provider_type_supports_local_openai_chat_transport(provider_type: &str) -> bool {
!matches!( provider_runtime_policy(provider_type).supports_local_openai_chat_transport
provider_type.trim().to_ascii_lowercase().as_str(),
"antigravity"
| "claude_code"
| "codex"
| "chatgpt_web"
| "gemini_cli"
| "kiro"
| "vertex_ai"
)
} }
pub fn provider_type_supports_local_same_format_transport(provider_type: &str) -> bool { pub fn provider_type_supports_local_same_format_transport(provider_type: &str) -> bool {
!matches!( provider_runtime_policy(provider_type).supports_local_same_format_transport
provider_type.trim().to_ascii_lowercase().as_str(),
"antigravity" | "chatgpt_web" | "claude_code" | "kiro" | "vertex_ai"
)
} }
pub fn provider_type_supports_local_embedding_transport( pub fn provider_type_supports_local_embedding_transport(
provider_type: &str, provider_type: &str,
api_format: &str, api_format: &str,
) -> bool { ) -> bool {
let provider_type = provider_type.trim().to_ascii_lowercase(); provider_runtime_policy(provider_type).supports_local_embedding_transport(api_format)
let api_format = aether_ai_formats::normalize_api_format_alias(api_format);
match api_format.as_str() {
"openai:embedding" => matches!(provider_type.as_str(), "custom" | "openai"),
"openai:rerank" => matches!(provider_type.as_str(), "custom" | "openai"),
"gemini:embedding" => matches!(provider_type.as_str(), "custom" | "gemini" | "google"),
"jina:embedding" => matches!(provider_type.as_str(), "custom" | "jina"),
"jina:rerank" => matches!(provider_type.as_str(), "custom" | "jina"),
"doubao:embedding" => matches!(provider_type.as_str(), "custom" | "doubao" | "volcengine"),
_ => false,
}
} }
pub fn is_codex_cli_backend_url(url: &str) -> bool { pub fn is_codex_cli_backend_url(url: &str) -> bool {
@@ -361,7 +527,9 @@ pub const ADMIN_PROVIDER_OAUTH_TEMPLATE_TYPES: &[&str] = &[
mod tests { mod tests {
use super::{ use super::{
fixed_provider_endpoint_template_by_api_format, fixed_provider_key_inherits_api_formats, fixed_provider_endpoint_template_by_api_format, fixed_provider_key_inherits_api_formats,
fixed_provider_template, provider_type_supports_local_embedding_transport, fixed_provider_template, provider_runtime_policy,
provider_type_allows_auth_channel_mismatch_by_default, provider_type_oauth_is_bearer_like,
provider_type_supports_local_embedding_transport,
provider_type_supports_local_same_format_transport, FixedProviderEndpointConfigValue, provider_type_supports_local_same_format_transport, FixedProviderEndpointConfigValue,
}; };
@@ -453,6 +621,52 @@ mod tests {
)); ));
} }
#[test]
fn kiro_allows_auth_channel_mismatch_by_default() {
let policy = provider_runtime_policy("kiro");
assert!(policy.fixed_provider);
assert!(policy.enable_format_conversion_by_default);
assert!(policy.oauth_is_bearer_like);
assert!(!policy.supports_model_fetch);
assert!(!policy.supports_local_openai_chat_transport);
assert!(!policy.supports_local_same_format_transport);
assert!(policy.key_inherits_api_formats("oauth", None));
assert!(policy.key_inherits_api_formats("bearer", Some("{}")));
assert!(!policy.key_inherits_api_formats("bearer", None));
assert!(provider_type_allows_auth_channel_mismatch_by_default(
"kiro"
));
assert!(provider_type_allows_auth_channel_mismatch_by_default(
" KIRO "
));
assert!(!provider_type_allows_auth_channel_mismatch_by_default(
"claude_code"
));
assert!(!provider_type_allows_auth_channel_mismatch_by_default(
"custom"
));
}
#[test]
fn runtime_policy_preserves_other_fixed_provider_behavior() {
let codex = provider_runtime_policy("codex");
assert!(codex.fixed_provider);
assert!(codex.enable_format_conversion_by_default);
assert!(!codex.oauth_is_bearer_like);
assert!(!codex.supports_model_fetch);
assert!(!codex.supports_local_openai_chat_transport);
assert!(codex.supports_local_same_format_transport);
let gemini_cli = provider_runtime_policy("gemini_cli");
assert!(gemini_cli.fixed_provider);
assert!(!gemini_cli.enable_format_conversion_by_default);
assert!(provider_type_oauth_is_bearer_like("gemini_cli"));
assert!(gemini_cli.supports_model_fetch);
assert!(!gemini_cli.supports_local_openai_chat_transport);
assert!(gemini_cli.supports_local_same_format_transport);
}
#[test] #[test]
fn chatgpt_web_does_not_use_generic_same_format_transport() { fn chatgpt_web_does_not_use_generic_same_format_transport() {
assert!(!provider_type_supports_local_same_format_transport( assert!(!provider_type_supports_local_same_format_transport(