Merge remote-tracking branch 'origin/pr/597'

This commit is contained in:
elky
2026-05-31 00:09:42 +08:00
24 changed files with 1151 additions and 91 deletions
@@ -9,9 +9,9 @@ use aether_admin::observability::usage::{
admin_usage_data_unavailable_response, admin_usage_has_fallback, admin_usage_is_failed,
admin_usage_matches_search, admin_usage_matches_username, admin_usage_parse_ids,
admin_usage_parse_limit, admin_usage_parse_offset, admin_usage_provider_key_name,
admin_usage_record_json, build_admin_usage_active_requests_response,
build_admin_usage_records_response, build_admin_usage_summary_stats_response_from_summary,
ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
admin_usage_record_json, attach_usage_server_now_header,
build_admin_usage_active_requests_response, build_admin_usage_records_response,
build_admin_usage_summary_stats_response_from_summary, ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
};
use aether_data::repository::users::StoredUserSummary;
use aether_data_contracts::repository::{
@@ -338,13 +338,15 @@ fn build_admin_usage_records_response_with_attempt_flags(
})
.collect();
Json(json!({
"records": records,
"total": total,
"limit": limit,
"offset": offset,
}))
.into_response()
attach_usage_server_now_header(
Json(json!({
"records": records,
"total": total,
"limit": limit,
"offset": offset,
}))
.into_response(),
)
}
fn build_admin_usage_records_query(
@@ -4,6 +4,7 @@ use aether_ai_serving::UPSTREAM_IS_STREAM_KEY;
use aether_billing::{
normalize_input_tokens_for_billing, normalize_total_input_context_for_cache_hit_rate,
};
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use aether_data_contracts::repository::usage::{
StoredRequestUsageAudit, StoredUsageBreakdownSummaryRow, StoredUsageDailySummary,
UsageAuditKeywordSearchQuery, UsageAuditListQuery, UsageBreakdownGroupBy,
@@ -29,6 +30,21 @@ use super::{
const USERS_ME_USAGE_DATA_UNAVAILABLE_DETAIL: &str = "用户用量数据暂不可用";
fn users_me_usage_server_now_unix_ms() -> u64 {
u64::try_from(Utc::now().timestamp_millis()).unwrap_or_default()
}
fn attach_users_me_usage_server_now_header(mut response: Response<Body>) -> Response<Body> {
if let Ok(value) = http::HeaderValue::from_str(&users_me_usage_server_now_unix_ms().to_string())
{
response.headers_mut().insert(
http::HeaderName::from_static(USAGE_SERVER_NOW_UNIX_MS_HEADER),
value,
);
}
response
}
fn build_users_me_usage_reader_unavailable_response() -> Response<Body> {
build_auth_error_response(
http::StatusCode::SERVICE_UNAVAILABLE,
@@ -1094,7 +1110,7 @@ pub(super) async fn handle_users_me_usage_get(
&summary_by_provider
));
}
Json(payload).into_response()
attach_users_me_usage_server_now_header(Json(payload).into_response())
}
pub(super) async fn handle_users_me_usage_active_get(
@@ -1169,13 +1185,15 @@ pub(super) async fn handle_users_me_usage_active_get(
.collect::<Vec<_>>()
};
Json(json!({
"requests": items
.iter()
.map(build_users_me_usage_active_payload)
.collect::<Vec<_>>(),
}))
.into_response()
attach_users_me_usage_server_now_header(
Json(json!({
"requests": items
.iter()
.map(build_users_me_usage_active_payload)
.collect::<Vec<_>>(),
}))
.into_response(),
)
}
pub(super) async fn handle_users_me_usage_interval_timeline_get(
@@ -1364,12 +1382,20 @@ async fn build_usage_heatmap_summaries(
mod tests {
use std::collections::BTreeMap;
use aether_contracts::USAGE_SERVER_NOW_UNIX_MS_HEADER;
use aether_data_contracts::repository::usage::StoredRequestUsageAudit;
use axum::{
body::Body,
response::{IntoResponse, Response},
Json,
};
use chrono::Utc;
use serde_json::json;
use super::{
build_users_me_usage_active_payload, build_users_me_usage_record_payload,
users_me_usage_client_is_stream, users_me_usage_is_failed,
attach_users_me_usage_server_now_header, build_users_me_usage_active_payload,
build_users_me_usage_record_payload, users_me_usage_client_is_stream,
users_me_usage_is_failed, users_me_usage_server_now_unix_ms,
users_me_usage_upstream_is_stream,
};
@@ -1415,6 +1441,39 @@ mod tests {
.expect("usage should build")
}
#[test]
fn users_me_usage_server_now_unix_ms_uses_epoch_millis() {
let before = u64::try_from(Utc::now().timestamp_millis()).unwrap_or_default();
let value = users_me_usage_server_now_unix_ms();
let after = u64::try_from(Utc::now().timestamp_millis()).unwrap_or_default();
assert!(value >= before);
assert!(value <= after);
assert!(value > 1_000_000_000_000);
}
fn assert_users_me_usage_server_now_header(response: &Response<Body>) {
let value = response
.headers()
.get(USAGE_SERVER_NOW_UNIX_MS_HEADER)
.expect("user usage response should include server now header")
.to_str()
.expect("server now header should be valid ASCII")
.parse::<u64>()
.expect("server now header should be epoch millis");
assert!(value > 1_000_000_000_000);
}
#[test]
fn users_me_usage_server_now_header_is_added_to_response() {
let response = attach_users_me_usage_server_now_header(
Json(json!({ "requests": [] })).into_response(),
);
assert_users_me_usage_server_now_header(&response);
}
#[test]
fn user_usage_record_payload_rehydrates_cache_creation_total_from_classified_fields() {
let item = StoredRequestUsageAudit {