mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
280 lines
11 KiB
Rust
280 lines
11 KiB
Rust
use crate::handlers::admin::shared::unix_secs_to_rfc3339;
|
|
use crate::handlers::public::{request_candidate_event_unix_ms, request_candidate_status_label};
|
|
use crate::provider_key_auth::provider_key_effective_api_formats;
|
|
use aether_data_contracts::repository::candidates::{
|
|
RequestCandidateStatus, StoredRequestCandidate,
|
|
};
|
|
use aether_data_contracts::repository::provider_catalog::{
|
|
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
|
|
};
|
|
use serde_json::{json, Map, Value};
|
|
use std::collections::BTreeMap;
|
|
|
|
fn json_truthy(value: &serde_json::Value) -> bool {
|
|
match value {
|
|
serde_json::Value::Null => false,
|
|
serde_json::Value::Bool(value) => *value,
|
|
serde_json::Value::Number(value) => value.as_f64().is_some_and(|value| value != 0.0),
|
|
serde_json::Value::String(value) => !value.trim().is_empty(),
|
|
serde_json::Value::Array(value) => !value.is_empty(),
|
|
serde_json::Value::Object(value) => !value.is_empty(),
|
|
}
|
|
}
|
|
|
|
fn endpoint_timestamp_or_now(value: Option<u64>, now_unix_secs: u64) -> serde_json::Value {
|
|
unix_secs_to_rfc3339(value.unwrap_or(now_unix_secs))
|
|
.map(serde_json::Value::String)
|
|
.unwrap_or(serde_json::Value::Null)
|
|
}
|
|
|
|
fn finite_json_number(value: Option<&Value>) -> Option<f64> {
|
|
match value {
|
|
Some(Value::Number(number)) => number.as_f64().filter(|value| value.is_finite()),
|
|
Some(Value::String(value)) => value
|
|
.trim()
|
|
.parse::<f64>()
|
|
.ok()
|
|
.filter(|value| value.is_finite()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn finite_json_u64(value: Option<&Value>) -> Option<u64> {
|
|
finite_json_number(value).and_then(|value| {
|
|
if value >= 0.0 {
|
|
Some(value as u64)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
fn latest_key_balance_summary(keys: &[StoredProviderCatalogKey]) -> Value {
|
|
let mut selected: Option<(u64, &StoredProviderCatalogKey, &Map<String, Value>)> = None;
|
|
|
|
for key in keys {
|
|
let Some(balance) = key
|
|
.upstream_metadata
|
|
.as_ref()
|
|
.and_then(|metadata| metadata.get("balance_query"))
|
|
.and_then(Value::as_object)
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(updated_at) = finite_json_u64(balance.get("updated_at")) else {
|
|
continue;
|
|
};
|
|
let has_balance_value = ["total_available", "total_used", "total_granted"]
|
|
.into_iter()
|
|
.any(|field| finite_json_number(balance.get(field)).is_some());
|
|
if !has_balance_value {
|
|
continue;
|
|
}
|
|
|
|
if selected
|
|
.as_ref()
|
|
.is_none_or(|(selected_updated_at, _, _)| updated_at > *selected_updated_at)
|
|
{
|
|
selected = Some((updated_at, key, balance));
|
|
}
|
|
}
|
|
|
|
let Some((updated_at, key, balance)) = selected else {
|
|
return Value::Null;
|
|
};
|
|
|
|
json!({
|
|
"key_id": key.id.clone(),
|
|
"key_name": key.name.clone(),
|
|
"updated_at": updated_at,
|
|
"architecture_id": balance.get("architecture_id").cloned().unwrap_or(Value::Null),
|
|
"status": balance.get("status").cloned().unwrap_or_else(|| json!("success")),
|
|
"executed_at": balance.get("executed_at").cloned().unwrap_or(Value::Null),
|
|
"response_time_ms": balance.get("response_time_ms").cloned().unwrap_or(Value::Null),
|
|
"total_available": balance.get("total_available").cloned().unwrap_or(Value::Null),
|
|
"total_used": balance.get("total_used").cloned().unwrap_or(Value::Null),
|
|
"total_granted": balance.get("total_granted").cloned().unwrap_or(Value::Null),
|
|
"currency": balance.get("currency").cloned().unwrap_or_else(|| json!("USD")),
|
|
"plan_name": balance.get("plan_name").cloned().unwrap_or(Value::Null),
|
|
"query_config": balance.get("query_config").cloned().unwrap_or(Value::Null),
|
|
"extra": balance.get("extra").cloned().unwrap_or(Value::Null),
|
|
})
|
|
}
|
|
|
|
pub(crate) fn build_admin_provider_summary_value(
|
|
provider: &StoredProviderCatalogProvider,
|
|
endpoints: &[StoredProviderCatalogEndpoint],
|
|
keys: &[StoredProviderCatalogKey],
|
|
quota_snapshot: Option<&aether_data_contracts::repository::quota::StoredProviderQuotaSnapshot>,
|
|
model_stats: Option<
|
|
&aether_data_contracts::repository::global_models::StoredProviderModelStats,
|
|
>,
|
|
active_global_model_ids: Vec<String>,
|
|
now_unix_secs: u64,
|
|
) -> serde_json::Value {
|
|
let total_endpoints = endpoints.len();
|
|
let active_endpoints = endpoints
|
|
.iter()
|
|
.filter(|endpoint| endpoint.is_active)
|
|
.count();
|
|
let total_keys = keys.len();
|
|
let active_keys = keys.iter().filter(|key| key.is_active).count();
|
|
let total_models = model_stats
|
|
.map(|stats| stats.total_models as usize)
|
|
.unwrap_or(0);
|
|
let active_models = model_stats
|
|
.map(|stats| stats.active_models as usize)
|
|
.unwrap_or(0);
|
|
let api_formats = endpoints
|
|
.iter()
|
|
.map(|endpoint| endpoint.api_format.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
let format_to_endpoint_id = endpoints
|
|
.iter()
|
|
.map(|endpoint| (endpoint.api_format.clone(), endpoint.id.clone()))
|
|
.collect::<BTreeMap<_, _>>();
|
|
let mut keys_by_endpoint = BTreeMap::<String, Vec<&StoredProviderCatalogKey>>::new();
|
|
for endpoint in endpoints {
|
|
keys_by_endpoint.entry(endpoint.id.clone()).or_default();
|
|
}
|
|
for key in keys {
|
|
for api_format in
|
|
provider_key_effective_api_formats(key, &provider.provider_type, endpoints)
|
|
{
|
|
if let Some(endpoint_id) = format_to_endpoint_id.get(&api_format) {
|
|
keys_by_endpoint
|
|
.entry(endpoint_id.clone())
|
|
.or_default()
|
|
.push(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut endpoint_health_scores = Vec::with_capacity(endpoints.len());
|
|
let endpoint_health_details = endpoints
|
|
.iter()
|
|
.map(|endpoint| {
|
|
let endpoint_keys = keys_by_endpoint
|
|
.get(&endpoint.id)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
let health_score = if endpoint_keys.is_empty() {
|
|
1.0
|
|
} else {
|
|
let mut scores = Vec::new();
|
|
for key in &endpoint_keys {
|
|
let score = key
|
|
.health_by_format
|
|
.as_ref()
|
|
.and_then(|value| value.get(&endpoint.api_format))
|
|
.and_then(|value| value.get("health_score"))
|
|
.and_then(serde_json::Value::as_f64)
|
|
.unwrap_or(1.0);
|
|
scores.push(score);
|
|
}
|
|
scores.iter().sum::<f64>() / scores.len() as f64
|
|
};
|
|
endpoint_health_scores.push(health_score);
|
|
json!({
|
|
"api_format": endpoint.api_format,
|
|
"health_score": health_score,
|
|
"is_active": endpoint.is_active,
|
|
"total_keys": endpoint_keys.len(),
|
|
"active_keys": endpoint_keys.iter().filter(|key| key.is_active).count(),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let avg_health_score = if endpoint_health_scores.is_empty() {
|
|
1.0
|
|
} else {
|
|
endpoint_health_scores.iter().sum::<f64>() / endpoint_health_scores.len() as f64
|
|
};
|
|
let unhealthy_endpoints = endpoint_health_scores
|
|
.iter()
|
|
.filter(|score| **score < 0.5)
|
|
.count();
|
|
|
|
let provider_config = provider.config.clone();
|
|
let config = provider_config
|
|
.as_ref()
|
|
.and_then(serde_json::Value::as_object);
|
|
let provider_ops_config = config.and_then(|cfg| cfg.get("provider_ops"));
|
|
let ops_configured = provider_ops_config.is_some_and(json_truthy);
|
|
let ops_architecture_id = provider_ops_config
|
|
.and_then(serde_json::Value::as_object)
|
|
.and_then(|cfg| cfg.get("architecture_id"))
|
|
.and_then(serde_json::Value::as_str)
|
|
.map(ToOwned::to_owned);
|
|
let kiro_simulated_cache_enabled = config
|
|
.and_then(|cfg| cfg.get("kiro"))
|
|
.and_then(serde_json::Value::as_object)
|
|
.and_then(|cfg| cfg.get("simulated_cache_enabled"))
|
|
.and_then(serde_json::Value::as_bool)
|
|
.unwrap_or(false);
|
|
let billing_type = quota_snapshot
|
|
.map(|quota| quota.billing_type.clone())
|
|
.or_else(|| provider.billing_type.clone());
|
|
let monthly_quota_usd = quota_snapshot
|
|
.and_then(|quota| quota.monthly_quota_usd)
|
|
.or(provider.monthly_quota_usd);
|
|
let monthly_used_usd = quota_snapshot
|
|
.map(|quota| quota.monthly_used_usd)
|
|
.or(provider.monthly_used_usd);
|
|
let quota_reset_day = quota_snapshot
|
|
.and_then(|quota| quota.quota_reset_day)
|
|
.or(provider.quota_reset_day);
|
|
let quota_last_reset_at = quota_snapshot
|
|
.and_then(|quota| quota.quota_last_reset_at_unix_secs)
|
|
.or(provider.quota_last_reset_at_unix_secs)
|
|
.and_then(unix_secs_to_rfc3339);
|
|
let quota_expires_at = quota_snapshot
|
|
.and_then(|quota| quota.quota_expires_at_unix_secs)
|
|
.or(provider.quota_expires_at_unix_secs)
|
|
.and_then(unix_secs_to_rfc3339);
|
|
let key_balance_summary = latest_key_balance_summary(keys);
|
|
|
|
json!({
|
|
"id": provider.id.clone(),
|
|
"name": provider.name.clone(),
|
|
"provider_type": provider.provider_type.clone(),
|
|
"description": provider.description.clone(),
|
|
"website": provider.website.clone(),
|
|
"provider_priority": provider.provider_priority,
|
|
"keep_priority_on_conversion": provider.keep_priority_on_conversion,
|
|
"enable_format_conversion": provider.enable_format_conversion,
|
|
"is_active": provider.is_active,
|
|
"billing_type": billing_type,
|
|
"monthly_quota_usd": monthly_quota_usd,
|
|
"monthly_used_usd": monthly_used_usd,
|
|
"quota_reset_day": quota_reset_day,
|
|
"quota_last_reset_at": quota_last_reset_at,
|
|
"quota_expires_at": quota_expires_at,
|
|
"max_retries": provider.max_retries,
|
|
"proxy": provider.proxy.clone(),
|
|
"stream_first_byte_timeout": provider.stream_first_byte_timeout_secs,
|
|
"request_timeout": provider.request_timeout_secs,
|
|
"claude_code_advanced": config.and_then(|cfg| cfg.get("claude_code_advanced")).cloned(),
|
|
"pool_advanced": config.and_then(|cfg| cfg.get("pool_advanced")).cloned(),
|
|
"failover_rules": config.and_then(|cfg| cfg.get("failover_rules")).cloned(),
|
|
"chat_pii_redaction": config.and_then(|cfg| cfg.get("chat_pii_redaction")).cloned(),
|
|
"total_endpoints": total_endpoints,
|
|
"active_endpoints": active_endpoints,
|
|
"total_keys": total_keys,
|
|
"active_keys": active_keys,
|
|
"total_models": total_models,
|
|
"active_models": active_models,
|
|
"global_model_ids": active_global_model_ids,
|
|
"avg_health_score": avg_health_score,
|
|
"unhealthy_endpoints": unhealthy_endpoints,
|
|
"api_formats": api_formats,
|
|
"endpoint_health_details": endpoint_health_details,
|
|
"ops_configured": ops_configured,
|
|
"ops_architecture_id": ops_architecture_id,
|
|
"key_balance_summary": key_balance_summary,
|
|
"kiro_simulated_cache_enabled": kiro_simulated_cache_enabled,
|
|
"created_at": endpoint_timestamp_or_now(provider.created_at_unix_ms, now_unix_secs),
|
|
"updated_at": endpoint_timestamp_or_now(provider.updated_at_unix_secs, now_unix_secs),
|
|
})
|
|
}
|