refactor: 大规模模块拆分与代码精简,新增 ai-pipeline/data-contracts 独立 crate

- 新增 aether-ai-pipeline 和 aether-data-contracts crate,将 pipeline 逻辑与数据契约从 gateway 中解耦
- 重构 admin handlers:拆分单体模块为 auth/billing/endpoint/features/model/observability/provider/system 等独立子模块
- 合并 chat/cli 重复代码路径:精简 conversion、finalize、planner 中的 sync/chat/cli 分支
- 重构 scheduler/executor/data 层,引入 facade 模式降低模块间耦合
- 移除冗余的 intent 模块,将 plan_fallback/policy/stream_path/sync_path 迁移至 executor
- 前端适配:调整 admin API 调用和 provider 模型测试对话框
This commit is contained in:
fawney19
2026-04-07 02:50:19 +08:00
parent 763ff03a7b
commit 5d96d6673b
732 changed files with 28593 additions and 20666 deletions

View File

@@ -1,627 +0,0 @@
use crate::{AppState, LocalProviderDeleteTaskState};
use serde_json::json;
pub(crate) fn admin_provider_id_for_keys(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/endpoints/providers/")?
.strip_suffix("/keys")
.map(ToOwned::to_owned)
}
pub(crate) fn is_admin_provider_ops_architectures_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/provider-ops/architectures" | "/api/admin/provider-ops/architectures/"
)
}
pub(crate) fn admin_provider_ops_architecture_id_from_path(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/provider-ops/architectures/")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_provider_id_for_provider_ops_status(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/status")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_config(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/config")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_disconnect(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/disconnect")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_connect(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/connect")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_verify(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/verify")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_balance(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/balance")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_ops_checkin(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-ops/providers/")?
.strip_suffix("/checkin")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn is_admin_provider_strategy_strategies_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/provider-strategy/strategies" | "/api/admin/provider-strategy/strategies/"
)
}
pub(crate) fn admin_provider_id_for_provider_strategy_billing(
request_path: &str,
) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-strategy/providers/")?
.strip_suffix("/billing")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_strategy_stats(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-strategy/providers/")?
.strip_suffix("/stats")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_id_for_provider_strategy_quota(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-strategy/providers/")?
.strip_suffix("/quota")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_provider_ops_action_route_parts(
request_path: &str,
) -> Option<(String, String)> {
let raw = request_path.strip_prefix("/api/admin/provider-ops/providers/")?;
let (provider_id, action_type) = raw.split_once("/actions/")?;
let provider_id = provider_id.trim().trim_matches('/');
let action_type = action_type.trim().trim_matches('/');
if provider_id.is_empty()
|| action_type.is_empty()
|| provider_id.contains('/')
|| action_type.contains('/')
{
None
} else {
Some((provider_id.to_string(), action_type.to_string()))
}
}
pub(crate) fn is_admin_provider_ops_batch_balance_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/provider-ops/batch/balance" | "/api/admin/provider-ops/batch/balance/"
)
}
pub(crate) fn admin_provider_id_for_refresh_quota(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/endpoints/providers/")?
.strip_suffix("/refresh-quota")
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_id_for_health_monitor(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let raw = raw.strip_suffix("/health-monitor")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_provider_id_for_mapping_preview(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let raw = raw.strip_suffix("/mapping-preview")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_provider_id_for_pool_status(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/pool-status")
}
pub(crate) fn admin_provider_pool_key_route_parts(
request_path: &str,
marker: &str,
) -> Option<(String, String)> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let (provider_id, key_id) = raw.split_once(marker)?;
let provider_id = provider_id.trim().trim_matches('/');
let key_id = key_id.trim().trim_matches('/');
if provider_id.is_empty()
|| key_id.is_empty()
|| provider_id.contains('/')
|| key_id.contains('/')
{
None
} else {
Some((provider_id.to_string(), key_id.to_string()))
}
}
pub(crate) fn admin_provider_clear_pool_cooldown_parts(
request_path: &str,
) -> Option<(String, String)> {
admin_provider_pool_key_route_parts(request_path, "/pool/clear-cooldown/")
}
pub(crate) fn admin_provider_reset_pool_cost_parts(request_path: &str) -> Option<(String, String)> {
admin_provider_pool_key_route_parts(request_path, "/pool/reset-cost/")
}
pub(crate) fn admin_provider_delete_task_parts(request_path: &str) -> Option<(String, String)> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let (provider_id, task_id) = raw.split_once("/delete-task/")?;
let provider_id = provider_id.trim().trim_matches('/');
let task_id = task_id.trim().trim_matches('/');
if provider_id.is_empty()
|| task_id.is_empty()
|| provider_id.contains('/')
|| task_id.contains('/')
{
None
} else {
Some((provider_id.to_string(), task_id.to_string()))
}
}
pub(crate) fn admin_provider_id_for_summary(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let raw = raw.strip_suffix("/summary")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn build_admin_provider_delete_task_payload(
task: &LocalProviderDeleteTaskState,
) -> serde_json::Value {
json!({
"task_id": task.task_id,
"provider_id": task.provider_id,
"status": task.status,
"stage": task.stage,
"total_keys": task.total_keys,
"deleted_keys": task.deleted_keys,
"total_endpoints": task.total_endpoints,
"deleted_endpoints": task.deleted_endpoints,
"message": task.message,
})
}
pub(crate) fn put_admin_provider_delete_task(
state: &AppState,
task: &LocalProviderDeleteTaskState,
) {
state.put_provider_delete_task(task.clone());
}
pub(crate) fn admin_provider_id_for_manage_path(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn is_admin_providers_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/providers" | "/api/admin/providers/"
)
}
pub(crate) fn is_admin_management_tokens_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/management-tokens" | "/api/admin/management-tokens/"
)
}
pub(crate) fn is_admin_gemini_files_mappings_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/gemini-files/mappings" | "/api/admin/gemini-files/mappings/"
)
}
pub(crate) fn is_admin_gemini_files_stats_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/gemini-files/stats" | "/api/admin/gemini-files/stats/"
)
}
pub(crate) fn is_admin_gemini_files_capable_keys_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/gemini-files/capable-keys" | "/api/admin/gemini-files/capable-keys/"
)
}
pub(crate) fn is_admin_gemini_files_upload_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/gemini-files/upload" | "/api/admin/gemini-files/upload/"
)
}
pub(crate) fn is_admin_system_configs_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/system/configs" | "/api/admin/system/configs/"
)
}
pub(crate) fn is_admin_system_email_templates_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/system/email/templates" | "/api/admin/system/email/templates/"
)
}
pub(crate) fn admin_system_config_key_from_path(request_path: &str) -> Option<String> {
let value = request_path
.strip_prefix("/api/admin/system/configs/")?
.trim()
.trim_matches('/')
.to_string();
if value.is_empty() || value.contains('/') {
None
} else {
Some(value)
}
}
pub(crate) fn admin_system_email_template_type_from_path(request_path: &str) -> Option<String> {
let value = request_path
.strip_prefix("/api/admin/system/email/templates/")?
.trim()
.trim_matches('/')
.to_string();
if value.is_empty() || value.contains('/') {
None
} else {
Some(value)
}
}
pub(crate) fn admin_system_email_template_preview_type_from_path(
request_path: &str,
) -> Option<String> {
request_path
.strip_prefix("/api/admin/system/email/templates/")?
.strip_suffix("/preview")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_system_email_template_reset_type_from_path(
request_path: &str,
) -> Option<String> {
request_path
.strip_prefix("/api/admin/system/email/templates/")?
.strip_suffix("/reset")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_management_token_id_from_path(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/management-tokens/")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_management_token_status_id_from_path(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/management-tokens/")?
.strip_suffix("/status")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_gemini_file_mapping_id_from_path(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/gemini-files/mappings/")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_provider_id_for_models_list(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/models")
}
pub(crate) fn admin_provider_id_for_suffix(request_path: &str, suffix: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let raw = raw.strip_suffix(suffix)?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty() || normalized.contains('/') {
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_provider_model_route_parts(request_path: &str) -> Option<(String, String)> {
let raw = request_path.strip_prefix("/api/admin/providers/")?;
let (provider_id, model_id) = raw.split_once("/models/")?;
let provider_id = provider_id.trim().trim_matches('/');
let model_id = model_id.trim().trim_matches('/');
if provider_id.is_empty()
|| model_id.is_empty()
|| provider_id.contains('/')
|| model_id.contains('/')
{
None
} else {
Some((provider_id.to_string(), model_id.to_string()))
}
}
pub(crate) fn admin_provider_models_batch_path(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/models/batch")
}
pub(crate) fn admin_provider_available_source_models_path(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/available-source-models")
}
pub(crate) fn admin_provider_assign_global_models_path(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/assign-global-models")
}
pub(crate) fn admin_provider_import_models_path(request_path: &str) -> Option<String> {
admin_provider_id_for_suffix(request_path, "/import-from-upstream")
}
pub(crate) fn is_admin_global_models_root(request_path: &str) -> bool {
matches!(
request_path,
"/api/admin/models/global" | "/api/admin/models/global/"
)
}
pub(crate) fn admin_global_model_id_from_path(request_path: &str) -> Option<String> {
let raw = request_path.strip_prefix("/api/admin/models/global/")?;
let normalized = raw.trim().trim_matches('/');
if normalized.is_empty()
|| normalized.contains('/')
|| normalized == "batch-delete"
|| normalized.ends_with("/providers")
|| normalized.ends_with("/assign-to-providers")
|| normalized.ends_with("/routing")
{
None
} else {
Some(normalized.to_string())
}
}
pub(crate) fn admin_global_model_assign_to_providers_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/models/global/")?
.strip_suffix("/assign-to-providers")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_global_model_routing_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/models/global/")?
.strip_suffix("/routing")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_global_model_providers_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/models/global/")?
.strip_suffix("/providers")
.map(|value| value.trim().trim_matches('/').to_string())
.filter(|value| !value.is_empty() && !value.contains('/'))
}
pub(crate) fn admin_reveal_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/endpoints/keys/")?
.strip_suffix("/reveal")
.map(ToOwned::to_owned)
}
pub(crate) fn admin_export_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/endpoints/keys/")?
.strip_suffix("/export")
.map(ToOwned::to_owned)
}
pub(crate) fn admin_clear_oauth_invalid_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/endpoints/keys/")?
.strip_suffix("/clear-oauth-invalid")
.map(ToOwned::to_owned)
}
pub(crate) fn admin_update_key_id(request_path: &str) -> Option<String> {
let key_id = request_path.strip_prefix("/api/admin/endpoints/keys/")?;
(!key_id.is_empty() && !key_id.contains('/')).then_some(key_id.to_string())
}
pub(crate) fn admin_oauth_provider_type_from_path(request_path: &str) -> Option<String> {
let provider_type = request_path.strip_prefix("/api/admin/oauth/providers/")?;
(!provider_type.is_empty() && !provider_type.contains('/')).then_some(provider_type.to_string())
}
pub(crate) fn admin_oauth_test_provider_type_from_path(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/oauth/providers/")?
.strip_suffix("/test")
.filter(|provider_type| !provider_type.is_empty() && !provider_type.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_start_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/keys/")?
.strip_suffix("/start")
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_start_provider_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/start")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_complete_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/keys/")?
.strip_suffix("/complete")
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_refresh_key_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/keys/")?
.strip_suffix("/refresh")
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_complete_provider_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/complete")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_import_provider_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/import-refresh-token")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_batch_import_provider_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/batch-import")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_batch_import_task_provider_id(
request_path: &str,
) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/batch-import/tasks")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_batch_import_task_path(
request_path: &str,
) -> Option<(String, String)> {
let suffix = request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/")
.unwrap_or(request_path.strip_prefix("/api/admin/provider-oauth/providers/")?);
let (provider_id, task_path) = suffix.split_once("/batch-import/tasks/")?;
if provider_id.is_empty()
|| provider_id.contains('/')
|| task_path.is_empty()
|| task_path.contains('/')
{
return None;
}
Some((provider_id.to_string(), task_path.to_string()))
}
pub(crate) fn admin_provider_oauth_device_authorize_provider_id(
request_path: &str,
) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/device-authorize")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}
pub(crate) fn admin_provider_oauth_device_poll_provider_id(request_path: &str) -> Option<String> {
request_path
.strip_prefix("/api/admin/provider-oauth/providers/")?
.strip_suffix("/device-poll")
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
.map(ToOwned::to_owned)
}

View File

@@ -0,0 +1,52 @@
use crate::audit::attach_admin_audit_event;
use crate::control::GatewayPublicRequestContext;
use axum::{
body::Body,
http,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
pub(crate) fn build_unhandled_admin_proxy_response(
request_context: &GatewayPublicRequestContext,
) -> Response<Body> {
let decision = request_context.control_decision.as_ref();
(
http::StatusCode::NOT_IMPLEMENTED,
Json(json!({
"detail": "admin proxy route not implemented in rust frontdoor",
"route_family": decision.and_then(|value| value.route_family.as_deref()),
"route_kind": decision.and_then(|value| value.route_kind.as_deref()),
"request_path": request_context.request_path,
})),
)
.into_response()
}
pub(crate) fn build_admin_proxy_auth_required_response(
request_context: &GatewayPublicRequestContext,
) -> Response<Body> {
let decision = request_context.control_decision.as_ref();
(
http::StatusCode::UNAUTHORIZED,
Json(json!({
"detail": "admin authentication required",
"route_family": decision.and_then(|value| value.route_family.as_deref()),
"route_kind": decision.and_then(|value| value.route_kind.as_deref()),
"request_path": request_context.request_path,
})),
)
.into_response()
}
pub(crate) fn attach_admin_audit_response(
mut response: Response<Body>,
event_name: &'static str,
action: &'static str,
target_type: &'static str,
target_id: &str,
) -> Response<Body> {
attach_admin_audit_event(&mut response, event_name, action, target_type, target_id);
response
}

View File

@@ -1,11 +1,27 @@
use crate::handlers::{json_string_list, unix_secs_to_rfc3339};
use crate::handlers::shared::{json_string_list, unix_secs_to_rfc3339};
use crate::AppState;
#[cfg(test)]
use aether_crypto::DEVELOPMENT_ENCRYPTION_KEY;
use aether_crypto::{decrypt_python_fernet_ciphertext, encrypt_python_fernet_plaintext};
use aether_data::repository::provider_catalog::StoredProviderCatalogKey;
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
use serde_json::json;
pub(crate) fn provider_catalog_key_supports_format(
key: &StoredProviderCatalogKey,
api_format: &str,
) -> bool {
let Some(value) = key.api_formats.as_ref() else {
return true;
};
let Some(values) = value.as_array() else {
return true;
};
values
.iter()
.filter_map(serde_json::Value::as_str)
.any(|candidate| candidate.trim().eq_ignore_ascii_case(api_format))
}
pub(crate) fn decrypt_catalog_secret_with_fallbacks(
encryption_key: Option<&str>,
ciphertext: &str,

View File

@@ -0,0 +1,19 @@
pub(crate) const OFFICIAL_EXTERNAL_MODEL_PROVIDERS: &[&str] = &[
"anthropic",
"openai",
"google",
"google-vertex",
"azure",
"amazon-bedrock",
"xai",
"meta",
"deepseek",
"mistral",
"cohere",
"zhipuai",
"alibaba",
"minimax",
"moonshot",
"baichuan",
"ai21",
];

View File

@@ -1,66 +1,29 @@
mod admin_paths;
mod admin_proxy;
mod catalog;
mod external_models;
mod normalize;
mod payloads;
mod request_utils;
mod usage_stats;
pub(crate) use self::admin_paths::{
admin_clear_oauth_invalid_key_id, admin_export_key_id, admin_gemini_file_mapping_id_from_path,
admin_global_model_assign_to_providers_id, admin_global_model_id_from_path,
admin_global_model_providers_id, admin_global_model_routing_id,
admin_management_token_id_from_path, admin_management_token_status_id_from_path,
admin_oauth_provider_type_from_path, admin_oauth_test_provider_type_from_path,
admin_provider_assign_global_models_path, admin_provider_available_source_models_path,
admin_provider_clear_pool_cooldown_parts, admin_provider_delete_task_parts,
admin_provider_id_for_health_monitor, admin_provider_id_for_keys,
admin_provider_id_for_manage_path, admin_provider_id_for_mapping_preview,
admin_provider_id_for_models_list, admin_provider_id_for_pool_status,
admin_provider_id_for_provider_ops_balance, admin_provider_id_for_provider_ops_checkin,
admin_provider_id_for_provider_ops_config, admin_provider_id_for_provider_ops_connect,
admin_provider_id_for_provider_ops_disconnect, admin_provider_id_for_provider_ops_status,
admin_provider_id_for_provider_ops_verify, admin_provider_id_for_provider_strategy_billing,
admin_provider_id_for_provider_strategy_quota, admin_provider_id_for_provider_strategy_stats,
admin_provider_id_for_refresh_quota, admin_provider_id_for_suffix,
admin_provider_id_for_summary, admin_provider_import_models_path,
admin_provider_model_route_parts, admin_provider_models_batch_path,
admin_provider_oauth_batch_import_provider_id, admin_provider_oauth_batch_import_task_path,
admin_provider_oauth_batch_import_task_provider_id, admin_provider_oauth_complete_key_id,
admin_provider_oauth_complete_provider_id, admin_provider_oauth_device_authorize_provider_id,
admin_provider_oauth_device_poll_provider_id, admin_provider_oauth_import_provider_id,
admin_provider_oauth_refresh_key_id, admin_provider_oauth_start_key_id,
admin_provider_oauth_start_provider_id, admin_provider_ops_action_route_parts,
admin_provider_ops_architecture_id_from_path, admin_provider_pool_key_route_parts,
admin_provider_reset_pool_cost_parts, admin_reveal_key_id, admin_system_config_key_from_path,
admin_system_email_template_preview_type_from_path,
admin_system_email_template_reset_type_from_path, admin_system_email_template_type_from_path,
admin_update_key_id, build_admin_provider_delete_task_payload,
is_admin_gemini_files_capable_keys_root, is_admin_gemini_files_mappings_root,
is_admin_gemini_files_stats_root, is_admin_gemini_files_upload_root,
is_admin_global_models_root, is_admin_management_tokens_root,
is_admin_provider_ops_architectures_root, is_admin_provider_ops_batch_balance_root,
is_admin_provider_strategy_strategies_root, is_admin_providers_root,
is_admin_system_configs_root, is_admin_system_email_templates_root,
put_admin_provider_delete_task,
pub(crate) use self::admin_proxy::{
attach_admin_audit_response, build_admin_proxy_auth_required_response,
build_unhandled_admin_proxy_response,
};
pub(crate) use self::catalog::{
build_admin_provider_key_response, decrypt_catalog_secret_with_fallbacks,
default_provider_key_status_snapshot, effective_catalog_encryption_key,
encrypt_catalog_secret_with_fallbacks, masked_catalog_api_key, parse_catalog_auth_config_json,
provider_key_health_summary, provider_key_status_snapshot_payload,
provider_catalog_key_supports_format, provider_key_health_summary,
provider_key_status_snapshot_payload,
};
pub(crate) use self::external_models::OFFICIAL_EXTERNAL_MODEL_PROVIDERS;
pub(crate) use self::normalize::{
normalize_json_array, normalize_json_object, normalize_string_list,
};
pub(crate) use self::payloads::{
default_admin_endpoint_max_retries, AdminBatchAssignGlobalModelsRequest,
AdminBatchAssignToProvidersRequest, AdminBatchDeleteIdsRequest, AdminGlobalModelCreateRequest,
AdminGlobalModelUpdateRequest, AdminImportProviderModelsRequest,
AdminOAuthProviderUpsertRequest, AdminProviderCreateRequest,
AdminProviderEndpointCreateRequest, AdminProviderEndpointUpdateRequest,
AdminProviderKeyBatchDeleteRequest, AdminProviderKeyCreateRequest,
AdminProviderKeyUpdateRequest, AdminProviderModelCreateRequest,
AdminProviderModelUpdateRequest, AdminProviderQuotaRefreshRequest, AdminProviderUpdateRequest,
InternalGatewayAuthContextRequest, InternalGatewayExecuteRequest,
InternalGatewayResolveRequest, InternalTunnelHeartbeatRequest, InternalTunnelNodeStatusRequest,
ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH, CODEX_WHAM_USAGE_URL, KIRO_USAGE_LIMITS_PATH,
KIRO_USAGE_SDK_VERSION, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
};
pub(crate) use self::request_utils::{
admin_proxy_local_requires_buffered_body, internal_proxy_local_requires_buffered_body,
@@ -71,3 +34,7 @@ pub(crate) use self::request_utils::{
sanitize_upstream_path_and_query, should_strip_forwarded_provider_credential_header,
should_strip_forwarded_trusted_admin_header, strip_query_param, unix_secs_to_rfc3339,
};
pub(crate) use self::usage_stats::{
admin_stats_bad_request_response, list_usage_for_optional_range, parse_bounded_u32, round_to,
AdminStatsTimeRange, AdminStatsUsageFilter,
};

View File

@@ -0,0 +1,44 @@
use std::collections::BTreeSet;
pub(crate) fn normalize_string_list(values: Option<Vec<String>>) -> Option<Vec<String>> {
let mut out = Vec::new();
let mut seen = BTreeSet::new();
for value in values.into_iter().flatten() {
let trimmed = value.trim();
if trimmed.is_empty() || !seen.insert(trimmed.to_string()) {
continue;
}
out.push(trimmed.to_string());
}
(!out.is_empty()).then_some(out)
}
pub(crate) fn normalize_json_object(
value: Option<serde_json::Value>,
field_name: &str,
) -> Result<Option<serde_json::Value>, String> {
let Some(value) = value else {
return Ok(None);
};
match value {
serde_json::Value::Null => Ok(None),
serde_json::Value::Object(map) if map.is_empty() => Ok(None),
serde_json::Value::Object(map) => Ok(Some(serde_json::Value::Object(map))),
_ => Err(format!("{field_name} 必须是 JSON 对象")),
}
}
pub(crate) fn normalize_json_array(
value: Option<serde_json::Value>,
field_name: &str,
) -> Result<Option<serde_json::Value>, String> {
let Some(value) = value else {
return Ok(None);
};
match value {
serde_json::Value::Null => Ok(None),
serde_json::Value::Array(items) if items.is_empty() => Ok(None),
serde_json::Value::Array(items) => Ok(Some(serde_json::Value::Array(items))),
_ => Err(format!("{field_name} 必须是 JSON 数组")),
}
}

View File

@@ -1,126 +1,6 @@
use serde::Deserialize;
use std::collections::BTreeMap;
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderKeyCreateRequest {
#[serde(default)]
pub(crate) api_formats: Option<Vec<String>>,
#[serde(default)]
pub(crate) api_key: Option<String>,
#[serde(default)]
pub(crate) auth_type: Option<String>,
#[serde(default)]
pub(crate) auth_config: Option<serde_json::Value>,
pub(crate) name: String,
#[serde(default)]
pub(crate) rate_multipliers: Option<serde_json::Value>,
#[serde(default)]
pub(crate) internal_priority: Option<i32>,
#[serde(default)]
pub(crate) rpm_limit: Option<u32>,
#[serde(default)]
pub(crate) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(crate) capabilities: Option<serde_json::Value>,
#[serde(default)]
pub(crate) cache_ttl_minutes: Option<i32>,
#[serde(default)]
pub(crate) max_probe_interval_minutes: Option<i32>,
#[serde(default)]
pub(crate) note: Option<String>,
#[serde(default)]
pub(crate) auto_fetch_models: Option<bool>,
#[serde(default)]
pub(crate) locked_models: Option<Vec<String>>,
#[serde(default)]
pub(crate) model_include_patterns: Option<Vec<String>>,
#[serde(default)]
pub(crate) model_exclude_patterns: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderKeyUpdateRequest {
#[serde(default)]
pub(crate) api_formats: Option<Vec<String>>,
#[serde(default)]
pub(crate) api_key: Option<String>,
#[serde(default)]
pub(crate) auth_type: Option<String>,
#[serde(default)]
pub(crate) auth_config: Option<serde_json::Value>,
#[serde(default)]
pub(crate) name: Option<String>,
#[serde(default)]
pub(crate) rate_multipliers: Option<serde_json::Value>,
#[serde(default)]
pub(crate) internal_priority: Option<i32>,
#[serde(default)]
pub(crate) global_priority_by_format: Option<serde_json::Value>,
#[serde(default)]
pub(crate) rpm_limit: Option<u32>,
#[serde(default)]
pub(crate) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(crate) capabilities: Option<serde_json::Value>,
#[serde(default)]
pub(crate) cache_ttl_minutes: Option<i32>,
#[serde(default)]
pub(crate) max_probe_interval_minutes: Option<i32>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) note: Option<String>,
#[serde(default)]
pub(crate) auto_fetch_models: Option<bool>,
#[serde(default)]
pub(crate) locked_models: Option<Vec<String>>,
#[serde(default)]
pub(crate) model_include_patterns: Option<Vec<String>>,
#[serde(default)]
pub(crate) model_exclude_patterns: Option<Vec<String>>,
#[serde(default)]
pub(crate) proxy: Option<serde_json::Value>,
#[serde(default)]
pub(crate) fingerprint: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderKeyBatchDeleteRequest {
pub(crate) ids: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderQuotaRefreshRequest {
#[serde(default)]
pub(crate) key_ids: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminOAuthProviderUpsertRequest {
pub(crate) display_name: String,
pub(crate) client_id: String,
#[serde(default)]
pub(crate) client_secret: Option<String>,
#[serde(default)]
pub(crate) authorization_url_override: Option<String>,
#[serde(default)]
pub(crate) token_url_override: Option<String>,
#[serde(default)]
pub(crate) userinfo_url_override: Option<String>,
#[serde(default)]
pub(crate) scopes: Option<Vec<String>>,
pub(crate) redirect_uri: String,
pub(crate) frontend_callback_url: String,
#[serde(default)]
pub(crate) attribute_mapping: Option<serde_json::Value>,
#[serde(default)]
pub(crate) extra_config: Option<serde_json::Value>,
#[serde(default)]
pub(crate) is_enabled: bool,
#[serde(default)]
pub(crate) force: bool,
}
#[derive(Debug, Deserialize)]
pub(crate) struct InternalTunnelHeartbeatRequest {
pub(crate) node_id: String,
@@ -192,262 +72,3 @@ pub(crate) struct InternalGatewayExecuteRequest {
#[serde(default)]
pub(crate) auth_context: Option<crate::control::GatewayControlAuthContext>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderCreateRequest {
pub(crate) name: String,
#[serde(default)]
pub(crate) provider_type: Option<String>,
#[serde(default)]
pub(crate) description: Option<String>,
#[serde(default)]
pub(crate) website: Option<String>,
#[serde(default)]
pub(crate) billing_type: Option<String>,
#[serde(default)]
pub(crate) monthly_quota_usd: Option<f64>,
#[serde(default)]
pub(crate) quota_reset_day: Option<u64>,
#[serde(default)]
pub(crate) quota_last_reset_at: Option<String>,
#[serde(default)]
pub(crate) quota_expires_at: Option<String>,
#[serde(default)]
pub(crate) provider_priority: Option<i32>,
#[serde(default)]
pub(crate) keep_priority_on_conversion: Option<bool>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) concurrent_limit: Option<i32>,
#[serde(default)]
pub(crate) max_retries: Option<i32>,
#[serde(default)]
pub(crate) proxy: Option<serde_json::Value>,
#[serde(default)]
pub(crate) stream_first_byte_timeout: Option<f64>,
#[serde(default)]
pub(crate) request_timeout: Option<f64>,
#[serde(default)]
pub(crate) pool_advanced: Option<serde_json::Value>,
#[serde(default)]
pub(crate) claude_code_advanced: Option<serde_json::Value>,
#[serde(default)]
pub(crate) failover_rules: Option<serde_json::Value>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderUpdateRequest {
#[serde(default)]
pub(crate) name: Option<String>,
#[serde(default)]
pub(crate) provider_type: Option<String>,
#[serde(default)]
pub(crate) description: Option<String>,
#[serde(default)]
pub(crate) website: Option<String>,
#[serde(default)]
pub(crate) billing_type: Option<String>,
#[serde(default)]
pub(crate) monthly_quota_usd: Option<f64>,
#[serde(default)]
pub(crate) quota_reset_day: Option<u64>,
#[serde(default)]
pub(crate) quota_last_reset_at: Option<String>,
#[serde(default)]
pub(crate) quota_expires_at: Option<String>,
#[serde(default)]
pub(crate) provider_priority: Option<i32>,
#[serde(default)]
pub(crate) keep_priority_on_conversion: Option<bool>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) concurrent_limit: Option<i32>,
#[serde(default)]
pub(crate) max_retries: Option<i32>,
#[serde(default)]
pub(crate) proxy: Option<serde_json::Value>,
#[serde(default)]
pub(crate) stream_first_byte_timeout: Option<f64>,
#[serde(default)]
pub(crate) request_timeout: Option<f64>,
#[serde(default)]
pub(crate) pool_advanced: Option<serde_json::Value>,
#[serde(default)]
pub(crate) claude_code_advanced: Option<serde_json::Value>,
#[serde(default)]
pub(crate) failover_rules: Option<serde_json::Value>,
#[serde(default)]
pub(crate) enable_format_conversion: Option<bool>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
}
pub(crate) const CODEX_WHAM_USAGE_URL: &str = "https://chatgpt.com/backend-api/wham/usage";
pub(crate) const KIRO_USAGE_LIMITS_PATH: &str = "/getUsageLimits";
pub(crate) const KIRO_USAGE_SDK_VERSION: &str = "1.0.0";
pub(crate) const ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH: &str = "/v1internal:fetchAvailableModels";
pub(crate) const OAUTH_ACCOUNT_BLOCK_PREFIX: &str = "[ACCOUNT_BLOCK] ";
pub(crate) const OAUTH_REFRESH_FAILED_PREFIX: &str = "[REFRESH_FAILED] ";
pub(crate) const OAUTH_EXPIRED_PREFIX: &str = "[OAUTH_EXPIRED] ";
pub(crate) const OAUTH_REQUEST_FAILED_PREFIX: &str = "[REQUEST_FAILED] ";
pub(crate) fn default_admin_endpoint_max_retries() -> i32 {
2
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderEndpointCreateRequest {
pub(crate) provider_id: String,
pub(crate) api_format: String,
pub(crate) base_url: String,
#[serde(default)]
pub(crate) custom_path: Option<String>,
#[serde(default)]
pub(crate) header_rules: Option<serde_json::Value>,
#[serde(default)]
pub(crate) body_rules: Option<serde_json::Value>,
#[serde(default = "default_admin_endpoint_max_retries")]
pub(crate) max_retries: i32,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
#[serde(default)]
pub(crate) proxy: Option<serde_json::Value>,
#[serde(default)]
pub(crate) format_acceptance_config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderEndpointUpdateRequest {
#[serde(default)]
pub(crate) base_url: Option<String>,
#[serde(default)]
pub(crate) custom_path: Option<String>,
#[serde(default)]
pub(crate) header_rules: Option<serde_json::Value>,
#[serde(default)]
pub(crate) body_rules: Option<serde_json::Value>,
#[serde(default)]
pub(crate) max_retries: Option<i32>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
#[serde(default)]
pub(crate) proxy: Option<serde_json::Value>,
#[serde(default)]
pub(crate) format_acceptance_config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderModelCreateRequest {
pub(crate) provider_model_name: String,
#[serde(default)]
pub(crate) provider_model_mappings: Option<serde_json::Value>,
pub(crate) global_model_id: String,
#[serde(default)]
pub(crate) price_per_request: Option<f64>,
#[serde(default)]
pub(crate) tiered_pricing: Option<serde_json::Value>,
#[serde(default)]
pub(crate) supports_vision: Option<bool>,
#[serde(default)]
pub(crate) supports_function_calling: Option<bool>,
#[serde(default)]
pub(crate) supports_streaming: Option<bool>,
#[serde(default)]
pub(crate) supports_extended_thinking: Option<bool>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminProviderModelUpdateRequest {
#[serde(default)]
pub(crate) provider_model_name: Option<String>,
#[serde(default)]
pub(crate) provider_model_mappings: Option<serde_json::Value>,
#[serde(default)]
pub(crate) global_model_id: Option<String>,
#[serde(default)]
pub(crate) price_per_request: Option<f64>,
#[serde(default)]
pub(crate) tiered_pricing: Option<serde_json::Value>,
#[serde(default)]
pub(crate) supports_vision: Option<bool>,
#[serde(default)]
pub(crate) supports_function_calling: Option<bool>,
#[serde(default)]
pub(crate) supports_streaming: Option<bool>,
#[serde(default)]
pub(crate) supports_extended_thinking: Option<bool>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) is_available: Option<bool>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminGlobalModelCreateRequest {
pub(crate) name: String,
pub(crate) display_name: String,
#[serde(default)]
pub(crate) default_price_per_request: Option<f64>,
#[serde(default)]
pub(crate) default_tiered_pricing: Option<serde_json::Value>,
#[serde(default)]
pub(crate) supported_capabilities: Option<Vec<String>>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminGlobalModelUpdateRequest {
#[serde(default)]
pub(crate) display_name: Option<String>,
#[serde(default)]
pub(crate) is_active: Option<bool>,
#[serde(default)]
pub(crate) default_price_per_request: Option<f64>,
#[serde(default)]
pub(crate) default_tiered_pricing: Option<serde_json::Value>,
#[serde(default)]
pub(crate) supported_capabilities: Option<Vec<String>>,
#[serde(default)]
pub(crate) config: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminBatchDeleteIdsRequest {
pub(crate) ids: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminBatchAssignToProvidersRequest {
pub(crate) provider_ids: Vec<String>,
#[serde(default)]
pub(crate) create_models: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminBatchAssignGlobalModelsRequest {
pub(crate) global_model_ids: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AdminImportProviderModelsRequest {
pub(crate) model_ids: Vec<String>,
#[serde(default)]
pub(crate) tiered_pricing: Option<serde_json::Value>,
#[serde(default)]
pub(crate) price_per_request: Option<f64>,
}

View File

@@ -4,13 +4,14 @@ use crate::constants::{
};
use crate::control::GatewayControlDecision;
use crate::control::GatewayPublicRequestContext;
use crate::handlers::OFFICIAL_EXTERNAL_MODEL_PROVIDERS;
use crate::headers::header_value_str;
use crate::tunnel::TUNNEL_ROUTE_FAMILY;
use axum::http::{self, HeaderName};
use chrono::{SecondsFormat, Utc};
use url::form_urlencoded;
use super::OFFICIAL_EXTERNAL_MODEL_PROVIDERS;
pub(crate) fn rust_auth_terminates_provider_credentials(
decision: Option<&GatewayControlDecision>,
) -> bool {

View File

@@ -0,0 +1,4 @@
pub(crate) use crate::handlers::admin::observability::stats::{
admin_stats_bad_request_response, list_usage_for_optional_range, parse_bounded_u32, round_to,
AdminStatsTimeRange, AdminStatsUsageFilter,
};