mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Refactor provider transport modules
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user