mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 00:47:48 +08:00
feat(gateway): add Codex Live and OpenAI Realtime
Implement preflighted Live/Realtime WebSocket transports, protocol-aware authentication, usage auditing, UI filtering, and legacy Codex permission migration.
This commit is contained in:
@@ -65,6 +65,45 @@ fn parse_users_me_usage_offset(query: Option<&str>) -> Result<usize, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
struct UsersMeUsageRecordFilter {
|
||||
statuses: Option<Vec<String>>,
|
||||
is_stream: Option<bool>,
|
||||
is_websocket: Option<bool>,
|
||||
error_only: bool,
|
||||
}
|
||||
|
||||
fn parse_users_me_usage_record_filter(query: Option<&str>) -> UsersMeUsageRecordFilter {
|
||||
let Some(status) = query_param_value(query, "status")
|
||||
.map(|value| value.trim().to_ascii_lowercase())
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return UsersMeUsageRecordFilter::default();
|
||||
};
|
||||
|
||||
let mut filter = UsersMeUsageRecordFilter::default();
|
||||
match status.as_str() {
|
||||
"stream" => {
|
||||
filter.is_stream = Some(true);
|
||||
filter.is_websocket = Some(false);
|
||||
}
|
||||
"standard" => {
|
||||
filter.is_stream = Some(false);
|
||||
filter.is_websocket = Some(false);
|
||||
}
|
||||
"websocket" | "ws" => filter.is_websocket = Some(true),
|
||||
"error" | "failed" => filter.error_only = true,
|
||||
"active" => {
|
||||
filter.statuses = Some(vec!["pending".to_string(), "streaming".to_string()]);
|
||||
}
|
||||
"pending" | "streaming" | "completed" | "cancelled" => {
|
||||
filter.statuses = Some(vec![status]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
filter
|
||||
}
|
||||
|
||||
fn parse_users_me_usage_hours(query: Option<&str>) -> Result<u32, String> {
|
||||
match query_param_value(query, "hours") {
|
||||
Some(value) => parse_bounded_u32("hours", &value, 1, 720),
|
||||
@@ -480,6 +519,11 @@ fn build_users_me_usage_record_payload(
|
||||
"first_byte_time_ms": item.first_byte_time_ms,
|
||||
"is_stream": item.is_stream,
|
||||
"is_websocket": item.is_websocket(),
|
||||
"websocket_transport": item.websocket_transport(),
|
||||
"usage_available": item.usage_available(),
|
||||
"usage_pricing_available": item.usage_pricing_available(),
|
||||
"input_audio_tokens": item.realtime_input_audio_tokens(),
|
||||
"output_audio_tokens": item.realtime_output_audio_tokens(),
|
||||
"upstream_is_stream": upstream_is_stream,
|
||||
"client_requested_stream": client_is_stream,
|
||||
"client_is_stream": client_is_stream,
|
||||
@@ -564,6 +608,11 @@ fn build_users_me_usage_active_payload(item: &StoredRequestUsageAudit) -> serde_
|
||||
"endpoint_api_format": item.endpoint_api_format,
|
||||
"is_stream": item.is_stream,
|
||||
"is_websocket": item.is_websocket(),
|
||||
"websocket_transport": item.websocket_transport(),
|
||||
"usage_available": item.usage_available(),
|
||||
"usage_pricing_available": item.usage_pricing_available(),
|
||||
"input_audio_tokens": item.realtime_input_audio_tokens(),
|
||||
"output_audio_tokens": item.realtime_output_audio_tokens(),
|
||||
"upstream_is_stream": upstream_is_stream,
|
||||
"client_requested_stream": client_is_stream,
|
||||
"client_is_stream": client_is_stream,
|
||||
@@ -947,6 +996,7 @@ pub(super) async fn handle_users_me_usage_get(
|
||||
Ok(value) => value,
|
||||
Err(detail) => return admin_stats_bad_request_response(detail),
|
||||
};
|
||||
let record_filter = parse_users_me_usage_record_filter(query);
|
||||
|
||||
// When no time range is specified, default to 7 days to avoid full-table scans.
|
||||
let effective_time_range = time_range.or_else(|| {
|
||||
@@ -1087,10 +1137,11 @@ pub(super) async fn handle_users_me_usage_get(
|
||||
api_format: None,
|
||||
client_family: None,
|
||||
exclude_unknown_model_or_provider: false,
|
||||
statuses: None,
|
||||
statuses: record_filter.statuses.clone(),
|
||||
exclude_status_codes: Vec::new(),
|
||||
is_stream: None,
|
||||
error_only: false,
|
||||
is_stream: record_filter.is_stream,
|
||||
is_websocket: record_filter.is_websocket,
|
||||
error_only: record_filter.error_only,
|
||||
keywords,
|
||||
matched_user_ids_by_keyword: Vec::new(),
|
||||
auth_user_reader_available: false,
|
||||
@@ -1143,10 +1194,11 @@ pub(super) async fn handle_users_me_usage_get(
|
||||
api_format: None,
|
||||
client_family: None,
|
||||
exclude_unknown_model_or_provider: false,
|
||||
statuses: None,
|
||||
statuses: record_filter.statuses.clone(),
|
||||
exclude_status_codes: Vec::new(),
|
||||
is_stream: None,
|
||||
error_only: false,
|
||||
is_stream: record_filter.is_stream,
|
||||
is_websocket: record_filter.is_websocket,
|
||||
error_only: record_filter.error_only,
|
||||
limit: None,
|
||||
offset: None,
|
||||
newest_first: true,
|
||||
@@ -1172,10 +1224,11 @@ pub(super) async fn handle_users_me_usage_get(
|
||||
api_format: None,
|
||||
client_family: None,
|
||||
exclude_unknown_model_or_provider: false,
|
||||
statuses: None,
|
||||
statuses: record_filter.statuses.clone(),
|
||||
exclude_status_codes: Vec::new(),
|
||||
is_stream: None,
|
||||
error_only: false,
|
||||
is_stream: record_filter.is_stream,
|
||||
is_websocket: record_filter.is_websocket,
|
||||
error_only: record_filter.error_only,
|
||||
limit: Some(limit),
|
||||
offset: Some(offset),
|
||||
newest_first: true,
|
||||
@@ -1314,6 +1367,7 @@ pub(super) async fn handle_users_me_usage_active_get(
|
||||
statuses: Some(vec!["pending".to_string(), "streaming".to_string()]),
|
||||
exclude_status_codes: Vec::new(),
|
||||
is_stream: None,
|
||||
is_websocket: None,
|
||||
error_only: false,
|
||||
limit: Some(50),
|
||||
offset: None,
|
||||
@@ -1568,10 +1622,34 @@ mod tests {
|
||||
|
||||
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,
|
||||
users_me_usage_terminal_candidate_state_override, users_me_usage_upstream_is_stream,
|
||||
parse_users_me_usage_record_filter, users_me_usage_client_is_stream,
|
||||
users_me_usage_is_failed, users_me_usage_terminal_candidate_state_override,
|
||||
users_me_usage_upstream_is_stream,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn users_me_usage_transport_statuses_are_disjoint_server_side_filters() {
|
||||
for status in ["websocket", "ws", "WS"] {
|
||||
let filter = parse_users_me_usage_record_filter(Some(
|
||||
format!("limit=20&status={status}").as_str(),
|
||||
));
|
||||
assert_eq!(filter.is_websocket, Some(true));
|
||||
assert_eq!(filter.is_stream, None);
|
||||
assert_eq!(filter.statuses, None);
|
||||
assert!(!filter.error_only);
|
||||
}
|
||||
|
||||
for (status, expected_stream) in [("stream", true), ("standard", false)] {
|
||||
let filter = parse_users_me_usage_record_filter(Some(
|
||||
format!("limit=20&status={status}").as_str(),
|
||||
));
|
||||
assert_eq!(filter.is_stream, Some(expected_stream));
|
||||
assert_eq!(filter.is_websocket, Some(false));
|
||||
assert_eq!(filter.statuses, None);
|
||||
assert!(!filter.error_only);
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_usage(status: &str) -> StoredRequestUsageAudit {
|
||||
StoredRequestUsageAudit::new(
|
||||
"usage-1".to_string(),
|
||||
@@ -1731,6 +1809,12 @@ mod tests {
|
||||
request_metadata: Some(json!({
|
||||
"websocket_mode": true,
|
||||
"websocket_transport": "responses",
|
||||
"usage_available": false,
|
||||
"usage_pricing_available": false,
|
||||
"realtime_session": {
|
||||
"input_audio_tokens": 7,
|
||||
"output_audio_tokens": 3,
|
||||
},
|
||||
})),
|
||||
..sample_usage("completed")
|
||||
};
|
||||
@@ -1740,6 +1824,16 @@ mod tests {
|
||||
|
||||
assert_eq!(record["is_websocket"], true);
|
||||
assert_eq!(active["is_websocket"], true);
|
||||
assert_eq!(record["websocket_transport"], "responses");
|
||||
assert_eq!(active["websocket_transport"], "responses");
|
||||
assert_eq!(record["usage_available"], false);
|
||||
assert_eq!(active["usage_available"], false);
|
||||
assert_eq!(record["usage_pricing_available"], false);
|
||||
assert_eq!(active["usage_pricing_available"], false);
|
||||
assert_eq!(record["input_audio_tokens"], 7);
|
||||
assert_eq!(active["input_audio_tokens"], 7);
|
||||
assert_eq!(record["output_audio_tokens"], 3);
|
||||
assert_eq!(active["output_audio_tokens"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user