mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 16:37:46 +08:00
refactor: 移除独立 hub/proxy/executor/gateway crate,统一为 gateway tunnel 架构
- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置 - 删除 crates/aether-executor 和 crates/aether-gateway 全部模块 - 新增 apps/ 目录作为应用入口 - 将 hub 概念重构为 gateway tunnel transport - 将 executor 重构为 execution runtime - 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块 - 更新 Python 服务层和测试适配新架构命名
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn rust_auth_terminates_provider_credentials(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
) -> bool {
|
||||
decision.is_some_and(|decision| {
|
||||
decision.route_class.as_deref() == Some("ai_public") && decision.auth_context.is_some()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn mark_external_models_official_providers(
|
||||
value: &serde_json::Value,
|
||||
) -> Option<serde_json::Value> {
|
||||
let providers = value.as_object()?;
|
||||
Some(serde_json::Value::Object(
|
||||
providers
|
||||
.iter()
|
||||
.map(|(provider_id, provider_value)| {
|
||||
let updated = match provider_value.as_object() {
|
||||
Some(object) => {
|
||||
let mut cloned = object.clone();
|
||||
cloned.insert(
|
||||
"official".to_string(),
|
||||
serde_json::Value::Bool(
|
||||
OFFICIAL_EXTERNAL_MODEL_PROVIDERS
|
||||
.iter()
|
||||
.any(|value| value == provider_id),
|
||||
),
|
||||
);
|
||||
serde_json::Value::Object(cloned)
|
||||
}
|
||||
None => provider_value.clone(),
|
||||
};
|
||||
(provider_id.clone(), updated)
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn should_strip_forwarded_provider_credential_header(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
header_name: &HeaderName,
|
||||
) -> bool {
|
||||
if !rust_auth_terminates_provider_credentials(decision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
matches!(
|
||||
header_name.as_str(),
|
||||
"authorization" | "x-api-key" | "api-key" | "x-goog-api-key"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn should_strip_forwarded_trusted_admin_header(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
header_name: &HeaderName,
|
||||
) -> bool {
|
||||
let Some(decision) = decision else {
|
||||
return false;
|
||||
};
|
||||
if decision.route_class.as_deref() != Some("admin_proxy") {
|
||||
return false;
|
||||
}
|
||||
|
||||
matches!(
|
||||
header_name.as_str(),
|
||||
TRUSTED_ADMIN_USER_ID_HEADER
|
||||
| TRUSTED_ADMIN_USER_ROLE_HEADER
|
||||
| TRUSTED_ADMIN_SESSION_ID_HEADER
|
||||
| TRUSTED_ADMIN_MANAGEMENT_TOKEN_ID_HEADER
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn sanitize_upstream_path_and_query(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
default_path_and_query: &str,
|
||||
) -> String {
|
||||
let base = decision
|
||||
.map(GatewayControlDecision::proxy_path_and_query)
|
||||
.unwrap_or_else(|| default_path_and_query.to_string());
|
||||
let Some(decision) = decision else {
|
||||
return base;
|
||||
};
|
||||
if !rust_auth_terminates_provider_credentials(Some(decision))
|
||||
|| decision.route_family.as_deref() != Some("gemini")
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
strip_query_param(&base, "key")
|
||||
}
|
||||
|
||||
pub(crate) fn strip_query_param(path_and_query: &str, key_to_strip: &str) -> String {
|
||||
let Some((path, query)) = path_and_query.split_once('?') else {
|
||||
return path_and_query.to_string();
|
||||
};
|
||||
|
||||
let mut serializer = form_urlencoded::Serializer::new(String::new());
|
||||
let mut kept_any = false;
|
||||
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||
if key == key_to_strip {
|
||||
continue;
|
||||
}
|
||||
serializer.append_pair(key.as_ref(), value.as_ref());
|
||||
kept_any = true;
|
||||
}
|
||||
|
||||
if !kept_any {
|
||||
return path.to_string();
|
||||
}
|
||||
|
||||
format!("{path}?{}", serializer.finish())
|
||||
}
|
||||
|
||||
pub(crate) fn query_param_bool(query: Option<&str>, key: &str, default: bool) -> bool {
|
||||
let Some(query) = query else {
|
||||
return default;
|
||||
};
|
||||
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||
if entry_key == key {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
return matches!(normalized.as_str(), "1" | "true" | "yes" | "on");
|
||||
}
|
||||
}
|
||||
default
|
||||
}
|
||||
|
||||
pub(crate) fn query_param_optional_bool(query: Option<&str>, key: &str) -> Option<bool> {
|
||||
let query = query?;
|
||||
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||
if entry_key == key {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
return match normalized.as_str() {
|
||||
"1" | "true" | "yes" | "on" => Some(true),
|
||||
"0" | "false" | "no" | "off" => Some(false),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn query_param_value(query: Option<&str>, key: &str) -> Option<String> {
|
||||
let query = query?;
|
||||
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||
if entry_key == key {
|
||||
let value = value.trim();
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn request_enables_control_execute(headers: &http::HeaderMap) -> bool {
|
||||
[
|
||||
CONTROL_EXECUTE_FALLBACK_HEADER,
|
||||
LEGACY_INTERNAL_GATEWAY_HEADER,
|
||||
]
|
||||
.into_iter()
|
||||
.any(|header| {
|
||||
header_value_str(headers, header).is_some_and(|value| {
|
||||
matches!(
|
||||
value.to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn unix_secs_to_rfc3339(unix_secs: u64) -> Option<String> {
|
||||
let timestamp = i64::try_from(unix_secs).ok()?;
|
||||
Some(
|
||||
chrono::DateTime::<Utc>::from_timestamp(timestamp, 0)?
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn json_string_list(value: Option<&serde_json::Value>) -> Vec<String> {
|
||||
value
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(serde_json::Value::as_str)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn admin_proxy_local_requires_buffered_body(
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> bool {
|
||||
request_context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.is_some_and(|decision| {
|
||||
if decision.route_class.as_deref() != Some("admin_proxy") {
|
||||
return false;
|
||||
}
|
||||
|
||||
match (
|
||||
decision.route_family.as_deref(),
|
||||
request_context.request_method.clone(),
|
||||
decision.route_kind.as_deref(),
|
||||
) {
|
||||
(Some("endpoints_manage"), http::Method::POST, Some("create_provider_key"))
|
||||
| (Some("endpoints_manage"), http::Method::POST, Some("create_endpoint"))
|
||||
| (Some("endpoints_manage"), http::Method::POST, Some("batch_delete_keys"))
|
||||
| (Some("endpoints_manage"), http::Method::PUT, Some("update_key"))
|
||||
| (Some("endpoints_manage"), http::Method::PUT, Some("update_endpoint"))
|
||||
| (Some("modules_manage"), http::Method::PUT, Some("set_enabled"))
|
||||
| (Some("oauth_manage"), http::Method::PUT, Some("upsert_provider"))
|
||||
| (Some("oauth_manage"), http::Method::POST, Some("test_provider"))
|
||||
| (Some("provider_oauth_manage"), http::Method::POST, Some("complete_key_oauth"))
|
||||
| (
|
||||
Some("provider_oauth_manage"),
|
||||
http::Method::POST,
|
||||
Some("complete_provider_oauth"),
|
||||
)
|
||||
| (
|
||||
Some("provider_oauth_manage"),
|
||||
http::Method::POST,
|
||||
Some("import_refresh_token"),
|
||||
)
|
||||
| (Some("provider_oauth_manage"), http::Method::POST, Some("batch_import_oauth"))
|
||||
| (
|
||||
Some("provider_oauth_manage"),
|
||||
http::Method::POST,
|
||||
Some("start_batch_import_oauth_task"),
|
||||
)
|
||||
| (Some("provider_oauth_manage"), http::Method::POST, Some("device_authorize"))
|
||||
| (Some("provider_oauth_manage"), http::Method::POST, Some("device_poll"))
|
||||
| (Some("system_manage"), http::Method::PUT, Some("settings_set"))
|
||||
| (Some("system_manage"), http::Method::PUT, Some("config_set"))
|
||||
| (Some("system_manage"), http::Method::PUT, Some("email_template_set"))
|
||||
| (Some("system_manage"), http::Method::POST, Some("email_template_preview"))
|
||||
| (
|
||||
Some("provider_models_manage"),
|
||||
http::Method::POST,
|
||||
Some("create_provider_model"),
|
||||
)
|
||||
| (
|
||||
Some("provider_models_manage"),
|
||||
http::Method::PATCH,
|
||||
Some("update_provider_model"),
|
||||
)
|
||||
| (
|
||||
Some("provider_models_manage"),
|
||||
http::Method::POST,
|
||||
Some("batch_create_provider_models"),
|
||||
)
|
||||
| (
|
||||
Some("provider_models_manage"),
|
||||
http::Method::POST,
|
||||
Some("assign_global_models"),
|
||||
)
|
||||
| (
|
||||
Some("provider_models_manage"),
|
||||
http::Method::POST,
|
||||
Some("import_from_upstream"),
|
||||
)
|
||||
| (
|
||||
Some("provider_ops_manage"),
|
||||
http::Method::POST,
|
||||
Some("execute_provider_action"),
|
||||
)
|
||||
| (Some("provider_ops_manage"), http::Method::POST, Some("batch_balance"))
|
||||
| (Some("provider_ops_manage"), http::Method::POST, Some("connect_provider"))
|
||||
| (Some("provider_ops_manage"), http::Method::POST, Some("verify_provider"))
|
||||
| (Some("provider_ops_manage"), http::Method::PUT, Some("save_provider_config"))
|
||||
| (Some("announcements_manage"), http::Method::POST, Some("create_announcement"))
|
||||
| (Some("announcements_manage"), http::Method::PUT, Some("update_announcement"))
|
||||
| (
|
||||
Some("provider_strategy_manage"),
|
||||
http::Method::PUT,
|
||||
Some("update_provider_billing"),
|
||||
)
|
||||
| (
|
||||
Some("provider_query_manage"),
|
||||
http::Method::POST,
|
||||
Some("query_models" | "test_model" | "test_model_failover"),
|
||||
)
|
||||
| (Some("billing_manage"), http::Method::POST, Some("apply_preset"))
|
||||
| (Some("billing_manage"), http::Method::POST, Some("create_rule"))
|
||||
| (Some("billing_manage"), http::Method::PUT, Some("update_rule"))
|
||||
| (Some("billing_manage"), http::Method::POST, Some("create_collector"))
|
||||
| (Some("billing_manage"), http::Method::PUT, Some("update_collector"))
|
||||
| (Some("payments_manage"), http::Method::POST, Some("credit_order"))
|
||||
| (Some("api_keys_manage"), http::Method::POST, Some("create_api_key"))
|
||||
| (Some("api_keys_manage"), http::Method::PUT, Some("update_api_key"))
|
||||
| (Some("api_keys_manage"), http::Method::PATCH, Some("toggle_api_key"))
|
||||
| (Some("adaptive_manage"), http::Method::PATCH, Some("toggle_mode"))
|
||||
| (Some("security_manage"), http::Method::POST, Some("blacklist_add"))
|
||||
| (Some("security_manage"), http::Method::POST, Some("whitelist_add"))
|
||||
| (Some("users_manage"), http::Method::POST, Some("create_user"))
|
||||
| (Some("users_manage"), http::Method::PUT, Some("update_user"))
|
||||
| (Some("users_manage"), http::Method::POST, Some("create_user_api_key"))
|
||||
| (Some("users_manage"), http::Method::PUT, Some("update_user_api_key"))
|
||||
| (Some("users_manage"), http::Method::PATCH, Some("lock_user_api_key"))
|
||||
| (Some("pool_manage"), http::Method::POST, Some("batch_import_keys"))
|
||||
| (Some("pool_manage"), http::Method::POST, Some("batch_action_keys"))
|
||||
| (Some("pool_manage"), http::Method::POST, Some("resolve_selection"))
|
||||
| (Some("usage_manage"), http::Method::POST, Some("replay"))
|
||||
| (Some("wallets_manage"), http::Method::POST, Some("adjust_balance"))
|
||||
| (Some("wallets_manage"), http::Method::POST, Some("recharge_balance"))
|
||||
| (Some("wallets_manage"), http::Method::POST, Some("process_refund"))
|
||||
| (Some("wallets_manage"), http::Method::POST, Some("complete_refund"))
|
||||
| (Some("wallets_manage"), http::Method::POST, Some("fail_refund"))
|
||||
| (Some("gemini_files_manage"), http::Method::POST, Some("upload"))
|
||||
| (Some("ldap_manage"), http::Method::PUT, Some("set_config"))
|
||||
| (Some("ldap_manage"), http::Method::POST, Some("test_connection"))
|
||||
| (Some("global_models_manage"), http::Method::POST, Some("create_global_model"))
|
||||
| (
|
||||
Some("global_models_manage"),
|
||||
http::Method::PATCH,
|
||||
Some("update_global_model"),
|
||||
)
|
||||
| (
|
||||
Some("global_models_manage"),
|
||||
http::Method::POST,
|
||||
Some("batch_delete_global_models"),
|
||||
)
|
||||
| (Some("global_models_manage"), http::Method::POST, Some("assign_to_providers"))
|
||||
| (Some("providers_manage"), http::Method::POST, Some("create_provider"))
|
||||
| (Some("providers_manage"), http::Method::PATCH, Some("update_provider")) => true,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn internal_proxy_local_requires_buffered_body(
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> bool {
|
||||
request_context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.is_some_and(|decision| {
|
||||
if decision.route_class.as_deref() != Some("internal_proxy")
|
||||
|| request_context.request_method != http::Method::POST
|
||||
{
|
||||
return false;
|
||||
}
|
||||
matches!(
|
||||
(
|
||||
decision.route_family.as_deref(),
|
||||
decision.route_kind.as_deref()
|
||||
),
|
||||
(
|
||||
Some(crate::gateway::TUNNEL_ROUTE_FAMILY),
|
||||
Some("heartbeat" | "node_status")
|
||||
) | (
|
||||
Some("gateway_legacy"),
|
||||
Some(
|
||||
"resolve"
|
||||
| "auth_context"
|
||||
| "decision_sync"
|
||||
| "decision_stream"
|
||||
| "execute_sync"
|
||||
| "execute_stream"
|
||||
| "plan_sync"
|
||||
| "plan_stream"
|
||||
| "report_sync"
|
||||
| "report_stream"
|
||||
| "finalize_sync"
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn public_support_local_requires_buffered_body(
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> bool {
|
||||
request_context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.is_some_and(|decision| {
|
||||
if decision.route_class.as_deref() != Some("public_support") {
|
||||
return false;
|
||||
}
|
||||
|
||||
matches!(
|
||||
(
|
||||
decision.route_family.as_deref(),
|
||||
request_context.request_method.clone(),
|
||||
decision.route_kind.as_deref(),
|
||||
),
|
||||
(
|
||||
Some("auth_legacy"),
|
||||
http::Method::POST,
|
||||
Some(
|
||||
"login"
|
||||
| "register"
|
||||
| "send_verification_code"
|
||||
| "verify_email"
|
||||
| "verification_status"
|
||||
),
|
||||
) | (
|
||||
Some("users_me_legacy"),
|
||||
http::Method::PUT,
|
||||
Some(
|
||||
"update_detail"
|
||||
| "model_capabilities_update"
|
||||
| "preferences_update"
|
||||
| "api_key_update"
|
||||
| "management_token_update"
|
||||
| "api_key_providers_update"
|
||||
| "api_key_capabilities_update",
|
||||
),
|
||||
) | (
|
||||
Some("users_me_legacy"),
|
||||
http::Method::PATCH,
|
||||
Some("password" | "session_update" | "api_key_patch"),
|
||||
) | (
|
||||
Some("users_me_legacy"),
|
||||
http::Method::POST,
|
||||
Some("api_keys_create" | "management_tokens_create"),
|
||||
) | (
|
||||
Some("wallet_legacy"),
|
||||
http::Method::POST,
|
||||
Some("create_refund" | "create_recharge_order"),
|
||||
) | (
|
||||
Some("payment_callback_legacy"),
|
||||
http::Method::POST,
|
||||
Some("callback"),
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn local_proxy_route_requires_buffered_body(
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> bool {
|
||||
admin_proxy_local_requires_buffered_body(request_context)
|
||||
|| internal_proxy_local_requires_buffered_body(request_context)
|
||||
|| public_support_local_requires_buffered_body(request_context)
|
||||
}
|
||||
Reference in New Issue
Block a user