mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
Merge remote-tracking branch 'origin/main' into codex/pool-key-bulk-management-20260714
# Conflicts: # apps/aether-gateway/src/handlers/admin/request/provider/tasks.rs # frontend/src/api/endpoints/pool.ts
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ mod types;
|
||||
|
||||
pub(crate) use credentials::extract_requested_model;
|
||||
pub(crate) use gate::{
|
||||
request_model_local_rejection, should_buffer_request_for_local_auth,
|
||||
trusted_auth_local_rejection, GatewayLocalAuthRejection,
|
||||
execution_plan_balance_capacity_rejection, request_model_local_rejection,
|
||||
should_buffer_request_for_local_auth, trusted_auth_local_rejection, GatewayLocalAuthRejection,
|
||||
};
|
||||
pub(crate) use resolution::{
|
||||
refresh_execution_runtime_auth_context, resolve_execution_runtime_auth_context,
|
||||
|
||||
@@ -1115,10 +1115,9 @@ fn normalize_api_format_alias(value: &str) -> String {
|
||||
|
||||
fn auth_gate_api_format(auth_endpoint_signature: &str) -> String {
|
||||
let normalized = normalize_api_format_alias(auth_endpoint_signature);
|
||||
if normalized == "antigravity:v1internal" {
|
||||
"gemini:generate_content".to_string()
|
||||
} else {
|
||||
normalized
|
||||
match normalized.as_str() {
|
||||
"antigravity:v1internal" => "gemini:generate_content".to_string(),
|
||||
_ => normalized,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ mod public;
|
||||
mod route;
|
||||
|
||||
pub(crate) use auth::{
|
||||
extract_requested_model, refresh_execution_runtime_auth_context, request_model_local_rejection,
|
||||
execution_plan_balance_capacity_rejection, extract_requested_model,
|
||||
refresh_execution_runtime_auth_context, request_model_local_rejection,
|
||||
resolve_execution_runtime_auth_context, should_buffer_request_for_local_auth,
|
||||
trusted_auth_local_rejection, GatewayAdminPrincipalContext, GatewayControlAuthContext,
|
||||
GatewayLocalAuthRejection,
|
||||
|
||||
@@ -1,59 +1,11 @@
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::headers::header_value_str;
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
use super::{resolve_control_route, GatewayControlDecision};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GatewayPublicRequestContext {
|
||||
pub(crate) trace_id: String,
|
||||
pub(crate) request_method: http::Method,
|
||||
pub(crate) request_path: String,
|
||||
pub(crate) request_query_string: Option<String>,
|
||||
pub(crate) request_content_type: Option<String>,
|
||||
pub(crate) host_header: Option<String>,
|
||||
pub(crate) control_decision: Option<GatewayControlDecision>,
|
||||
}
|
||||
|
||||
impl GatewayPublicRequestContext {
|
||||
pub(crate) fn from_request_parts(
|
||||
trace_id: impl Into<String>,
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
control_decision: Option<GatewayControlDecision>,
|
||||
) -> Self {
|
||||
let request_path = if uri.path().starts_with('/') {
|
||||
uri.path().to_string()
|
||||
} else {
|
||||
format!("/{}", uri.path())
|
||||
};
|
||||
let request_query_string = uri.query().map(ToOwned::to_owned);
|
||||
|
||||
Self {
|
||||
trace_id: trace_id.into(),
|
||||
request_method: method.clone(),
|
||||
request_path,
|
||||
request_query_string,
|
||||
request_content_type: header_value_str(headers, http::header::CONTENT_TYPE.as_str()),
|
||||
host_header: header_value_str(headers, http::header::HOST.as_str()),
|
||||
control_decision,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_path_and_query(&self) -> String {
|
||||
if let Some(query) = self
|
||||
.request_query_string
|
||||
.as_deref()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
format!("{}?{query}", self.request_path)
|
||||
} else {
|
||||
self.request_path.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) type GatewayPublicRequestContext =
|
||||
aether_gateway_control::PublicRequestContext<GatewayControlDecision>;
|
||||
|
||||
pub(crate) async fn resolve_public_request_context(
|
||||
state: &AppState,
|
||||
|
||||
@@ -54,6 +54,14 @@ pub(super) fn classify_ai_public_route(
|
||||
true,
|
||||
))
|
||||
}
|
||||
} else if method == http::Method::POST && normalized_path == "/v1/alpha/search" {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"openai",
|
||||
"search",
|
||||
"openai:search",
|
||||
true,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(crate) struct GatewayControlDecision {
|
||||
pub(crate) auth_context: Option<GatewayControlAuthContext>,
|
||||
pub(crate) admin_principal: Option<GatewayAdminPrincipalContext>,
|
||||
pub(crate) local_auth_rejection: Option<GatewayLocalAuthRejection>,
|
||||
pub(crate) model_directive_policy: crate::system_features::ModelDirectivePolicySnapshot,
|
||||
}
|
||||
|
||||
impl GatewayControlDecision {
|
||||
@@ -47,6 +48,7 @@ impl GatewayControlDecision {
|
||||
auth_context: None,
|
||||
admin_principal: None,
|
||||
local_auth_rejection: None,
|
||||
model_directive_policy: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +133,7 @@ impl ClassifiedRoute {
|
||||
auth_context: None,
|
||||
admin_principal: None,
|
||||
local_auth_rejection: None,
|
||||
model_directive_policy: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +149,10 @@ pub(crate) async fn resolve_control_route(
|
||||
return Ok(None);
|
||||
};
|
||||
decision.public_query_string = uri.query().map(ToOwned::to_owned);
|
||||
if decision.route_class.as_deref() == Some("ai_public") {
|
||||
decision.model_directive_policy =
|
||||
crate::system_features::ModelDirectivePolicySnapshot::load(state).await;
|
||||
}
|
||||
|
||||
match resolve_control_decision_auth(state, headers, uri, trace_id, decision).await? {
|
||||
ControlDecisionAuthResolution::Resolved(decision) => Ok(Some(decision)),
|
||||
@@ -197,6 +204,15 @@ pub(super) fn detect_public_models_auth_signature(uri: &Uri, headers: &http::Hea
|
||||
return "gemini:generate_content".to_string();
|
||||
}
|
||||
|
||||
let has_codex_client_version = uri.path() == "/v1/models"
|
||||
&& uri.query().is_some_and(|query| {
|
||||
url::form_urlencoded::parse(query.as_bytes())
|
||||
.any(|(key, value)| key == "client_version" && !value.trim().is_empty())
|
||||
});
|
||||
if has_codex_client_version {
|
||||
return "openai:responses".to_string();
|
||||
}
|
||||
|
||||
if uri.path().starts_with("/v1beta/models") {
|
||||
return "gemini:generate_content".to_string();
|
||||
}
|
||||
|
||||
@@ -454,6 +454,25 @@ fn classifies_admin_codex_reset_credit_consume_as_admin_proxy_route() {
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_codex_reset_credit_consume_buffers_idempotency_key_body() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-codex/codex-reset-credit/consume"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
let context = GatewayPublicRequestContext::from_request_parts(
|
||||
"trace-codex-reset-credit-consume",
|
||||
&http::Method::POST,
|
||||
&uri,
|
||||
&headers,
|
||||
Some(decision),
|
||||
);
|
||||
|
||||
assert!(local_proxy_route_requires_buffered_body(&context));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_refresh_provider_quota_buffers_request_body_for_key_selection() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
@@ -56,6 +56,28 @@ fn classifies_openai_rerank_as_rerank_not_chat() {
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_openai_search_as_its_own_sync_endpoint() {
|
||||
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
||||
let uri: Uri = "/v1/alpha/search".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("openai"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("search"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("openai:search")
|
||||
);
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
assert!(classify_control_route(&http::Method::GET, &uri, &headers).is_none());
|
||||
|
||||
let upstream_uri: Uri = "/backend-api/codex/alpha/search"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
assert!(classify_control_route(&http::Method::POST, &upstream_uri, &headers).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_openai_chat_and_responses_separately_from_embedding() {
|
||||
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
||||
|
||||
@@ -20,6 +20,39 @@ fn classifies_models_list_as_public_support_route() {
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_codex_models_list_with_responses_auth_signature() {
|
||||
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
||||
let uri: Uri = "/v1/models?client_version=0.144.1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("models"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("openai:responses")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_codex_client_version_keeps_standard_openai_models_signature() {
|
||||
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
||||
let uri: Uri = "/v1/models?client_version="
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("openai:chat")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_v1beta_models_as_gemini_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
Reference in New Issue
Block a user