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:
ZheFox
2026-08-21 04:27:34 +08:00
parent fe38dcd294
commit 2c89202001
105 changed files with 7553 additions and 947 deletions
@@ -1,9 +1,9 @@
mod types;
pub use types::{
MinimalCandidateSelectionReadRepository, MinimalCandidateSelectionRepository,
StoredApiFormatCandidateRowsQuery, StoredMinimalCandidateSelectionRow,
StoredPoolKeyCandidateOrder, StoredPoolKeyCandidateRowsByKeyIdsQuery,
StoredPoolKeyCandidateRowsQuery, StoredProviderModelMapping,
StoredRequestedModelCandidateRowsQuery,
provider_model_mapping_api_format_covers, MinimalCandidateSelectionReadRepository,
MinimalCandidateSelectionRepository, StoredApiFormatCandidateRowsQuery,
StoredMinimalCandidateSelectionRow, StoredPoolKeyCandidateOrder,
StoredPoolKeyCandidateRowsByKeyIdsQuery, StoredPoolKeyCandidateRowsQuery,
StoredProviderModelMapping, StoredRequestedModelCandidateRowsQuery,
};
@@ -113,6 +113,27 @@ impl StoredMinimalCandidateSelectionRow {
}
}
/// Evaluates the API-format scope on a provider-model mapping.
///
/// Codex Live was introduced after existing Codex model associations had
/// already stored their source-model scope as `openai:responses`. Preserve
/// those associations for the same Codex provider without treating the two
/// formats as globally interchangeable. Endpoint and key permissions remain
/// independently scoped to `codex:live`.
pub fn provider_model_mapping_api_format_covers(
provider_type: &str,
mapping_api_format: &str,
requested_api_format: &str,
) -> bool {
if aether_ai_formats::api_format_permission_covers(mapping_api_format, requested_api_format) {
return true;
}
provider_type.trim().eq_ignore_ascii_case("codex")
&& aether_ai_formats::normalize_api_format_alias(requested_api_format) == "codex:live"
&& aether_ai_formats::normalize_api_format_alias(mapping_api_format) == "openai:responses"
}
fn api_format_permission_covers(allowed: &str, requested: &str) -> bool {
aether_ai_formats::api_format_permission_covers(allowed, requested)
}
@@ -176,3 +197,37 @@ impl<T> MinimalCandidateSelectionRepository for T where
T: MinimalCandidateSelectionReadRepository + Send + Sync
{
}
#[cfg(test)]
mod tests {
use super::provider_model_mapping_api_format_covers;
#[test]
fn legacy_responses_mapping_is_only_compatible_with_codex_live() {
assert!(provider_model_mapping_api_format_covers(
"codex",
"openai:responses",
"codex:live"
));
assert!(provider_model_mapping_api_format_covers(
" CoDeX ",
"/v1/responses",
"codex:live"
));
for provider_type in ["openai", "custom", "chatgpt_web"] {
assert!(!provider_model_mapping_api_format_covers(
provider_type,
"openai:responses",
"codex:live"
));
}
for requested_api_format in ["openai:chat", "claude:messages", "openai:image"] {
assert!(!provider_model_mapping_api_format_covers(
"codex",
"openai:responses",
requested_api_format
));
}
}
}
@@ -33,9 +33,11 @@ pub use types::{
UsageLeaderboardGroupBy, UsageLeaderboardQuery, UsageMonitoringErrorCountQuery,
UsageMonitoringErrorListQuery, UsagePerformancePercentilesQuery, UsageProviderPerformanceQuery,
UsageReadRepository, UsageRepository, UsageSettledCostSummaryQuery, UsageTimeSeriesGranularity,
UsageTimeSeriesQuery, UsageWriteRepository, PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY,
PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY, PROVIDER_REASONING_EFFORT_METADATA_KEY,
PROVIDER_SERVICE_TIER_METADATA_KEY, REQUESTED_REASONING_EFFORT_METADATA_KEY,
UsageTimeSeriesQuery, UsageWriteRepository, LIVE_SESSION_METADATA_KEY,
PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY, PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY,
PROVIDER_REASONING_EFFORT_METADATA_KEY, PROVIDER_SERVICE_TIER_METADATA_KEY,
REALTIME_SESSION_METADATA_KEY, REQUESTED_REASONING_EFFORT_METADATA_KEY,
ROUTING_CANDIDATE_SKIP_REASON_METADATA_KEY, ROUTING_FAILURE_DIAGNOSTIC_METADATA_KEY,
USAGE_AVAILABLE_METADATA_KEY, USAGE_PRICING_AVAILABLE_METADATA_KEY,
WEBSOCKET_MODE_METADATA_KEY, WEBSOCKET_TRANSPORT_METADATA_KEY,
};
@@ -254,12 +254,15 @@ pub fn provider_api_key_usage_contribution(
request_count: 1,
success_count: i64::from(is_success),
error_count: i64::from(is_error),
total_tokens: if is_in_flight {
total_tokens: if is_in_flight || !usage.usage_available() {
0
} else {
i64::try_from(usage.total_tokens).unwrap_or(i64::MAX)
},
total_cost_usd: if is_in_flight {
total_cost_usd: if is_in_flight
|| !usage.usage_available()
|| !usage.usage_pricing_available()
{
0.0
} else if usage.total_cost_usd.is_finite() {
usage.total_cost_usd.max(0.0)
@@ -308,8 +311,14 @@ pub fn api_key_usage_contribution(
Some(ApiKeyUsageContribution {
api_key_id,
total_requests: 1,
total_tokens: i64::try_from(usage.total_tokens).unwrap_or(i64::MAX),
total_cost_usd: if usage.total_cost_usd.is_finite() {
total_tokens: if usage.usage_available() {
i64::try_from(usage.total_tokens).unwrap_or(i64::MAX)
} else {
0
},
total_cost_usd: if !usage.usage_available() || !usage.usage_pricing_available() {
0.0
} else if usage.total_cost_usd.is_finite() {
usage.total_cost_usd.max(0.0)
} else {
0.0
@@ -325,3 +334,80 @@ fn newer_last_used_at(before: Option<u64>, after: Option<u64>) -> Option<u64> {
_ => None,
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{api_key_usage_contribution, provider_api_key_usage_contribution};
use crate::repository::usage::StoredRequestUsageAudit;
fn authoritative_unpriced_usage() -> StoredRequestUsageAudit {
let mut usage = StoredRequestUsageAudit::new(
"usage-realtime".to_string(),
"request-realtime".to_string(),
None,
Some("downstream-key".to_string()),
None,
None,
"OpenAI".to_string(),
"gpt-realtime".to_string(),
None,
Some("provider-realtime".to_string()),
Some("endpoint-realtime".to_string()),
Some("provider-key-realtime".to_string()),
Some("realtime".to_string()),
Some("openai:realtime".to_string()),
Some("openai".to_string()),
Some("realtime".to_string()),
Some("openai:realtime".to_string()),
Some("openai".to_string()),
Some("realtime".to_string()),
false,
true,
120,
40,
160,
9.75,
11.25,
Some(200),
None,
None,
Some(250),
Some(30),
"completed".to_string(),
"void".to_string(),
100,
101,
Some(102),
)
.expect("usage should build");
usage.request_metadata = Some(json!({
"usage_available": true,
"usage_pricing_available": false,
"realtime_session": {
"input_audio_tokens": 20,
"output_audio_tokens": 10,
}
}));
usage
}
#[test]
fn authoritative_unpriced_usage_contributes_tokens_but_never_cost() {
let usage = authoritative_unpriced_usage();
let provider = provider_api_key_usage_contribution(&usage)
.expect("provider key contribution should exist");
assert_eq!(provider.request_count, 1);
assert_eq!(provider.success_count, 1);
assert_eq!(provider.total_tokens, 160);
assert_eq!(provider.total_cost_usd, 0.0);
let downstream =
api_key_usage_contribution(&usage).expect("API key contribution should exist");
assert_eq!(downstream.total_requests, 1);
assert_eq!(downstream.total_tokens, 160);
assert_eq!(downstream.total_cost_usd, 0.0);
}
}
@@ -11,6 +11,24 @@ pub const ROUTING_CANDIDATE_SKIP_REASON_METADATA_KEY: &str = "routing_candidate_
pub const ROUTING_FAILURE_DIAGNOSTIC_METADATA_KEY: &str = "routing_failure_diagnostic";
pub const WEBSOCKET_MODE_METADATA_KEY: &str = "websocket_mode";
pub const WEBSOCKET_TRANSPORT_METADATA_KEY: &str = "websocket_transport";
/// Whether token/cost usage is authoritative for this audit row.
///
/// The field is absent for legacy and normally-metered requests. An explicit
/// `false` marks a transport/session audit whose lifecycle is known while the
/// upstream protocol exposes no trustworthy token/cost usage. Such rows still
/// count as requests and retain status/latency; only token and cost accounting
/// is unavailable.
pub const USAGE_AVAILABLE_METADATA_KEY: &str = "usage_available";
/// Whether Aether has a compatible pricing model for the authoritative usage
/// dimensions on this row. An explicit `false` keeps token telemetry visible
/// while preventing those tokens from being priced with an incompatible rule.
pub const USAGE_PRICING_AVAILABLE_METADATA_KEY: &str = "usage_pricing_available";
/// Bounded session-level telemetry for transports that do not expose token
/// usage (for example Codex Live direct/sideband WebSockets).
pub const LIVE_SESSION_METADATA_KEY: &str = "live_session";
/// Bounded lifecycle and authoritative usage facts for an OpenAI Realtime
/// WebSocket connection. Audio payloads themselves are never stored here.
pub const REALTIME_SESSION_METADATA_KEY: &str = "realtime_session";
pub fn extract_provider_reasoning_effort_from_body(value: Option<&Value>) -> Option<String> {
let object = value.and_then(Value::as_object)?;
@@ -546,6 +564,40 @@ impl StoredRequestUsageAudit {
.unwrap_or(false)
}
pub fn websocket_transport(&self) -> Option<&str> {
self.request_metadata_string(WEBSOCKET_TRANSPORT_METADATA_KEY)
}
/// Returns whether this row may participate in token/cost accounting.
/// Missing metadata is treated as available for backward compatibility.
pub fn usage_available(&self) -> bool {
self.request_metadata_bool(USAGE_AVAILABLE_METADATA_KEY)
.unwrap_or(true)
}
/// Returns whether token usage can be safely converted into cost. Missing
/// metadata remains eligible for backward compatibility.
pub fn usage_pricing_available(&self) -> bool {
self.request_metadata_bool(USAGE_PRICING_AVAILABLE_METADATA_KEY)
.unwrap_or(true)
}
pub fn realtime_input_audio_tokens(&self) -> Option<u64> {
self.request_metadata_object()
.and_then(|metadata| metadata.get(REALTIME_SESSION_METADATA_KEY))
.and_then(Value::as_object)
.and_then(|session| session.get("input_audio_tokens"))
.and_then(Value::as_u64)
}
pub fn realtime_output_audio_tokens(&self) -> Option<u64> {
self.request_metadata_object()
.and_then(|metadata| metadata.get(REALTIME_SESSION_METADATA_KEY))
.and_then(Value::as_object)
.and_then(|session| session.get("output_audio_tokens"))
.and_then(Value::as_u64)
}
fn billing_snapshot_resolved_number(&self, key: &str) -> Option<f64> {
self.request_metadata_object()
.and_then(|metadata| metadata.get("billing_snapshot"))
@@ -953,6 +1005,7 @@ pub struct UsageAuditListQuery {
pub statuses: Option<Vec<String>>,
pub exclude_status_codes: Vec<u16>,
pub is_stream: Option<bool>,
pub is_websocket: Option<bool>,
pub error_only: bool,
pub limit: Option<usize>,
pub offset: Option<usize>,
@@ -972,6 +1025,7 @@ pub struct UsageAuditKeywordSearchQuery {
pub statuses: Option<Vec<String>>,
pub exclude_status_codes: Vec<u16>,
pub is_stream: Option<bool>,
pub is_websocket: Option<bool>,
pub error_only: bool,
pub keywords: Vec<String>,
pub matched_user_ids_by_keyword: Vec<Vec<String>>,
@@ -2401,8 +2455,9 @@ mod tests {
extract_provider_actual_service_tier_from_response,
extract_provider_service_tier_from_body, resolve_provider_cache_ttl_minutes,
StoredRequestUsageAudit, UpsertUsageRecord, UsageBodyCaptureState, UsageBodyCaptureStorage,
UsageBodyField, UsageProviderPerformanceQuery, WEBSOCKET_MODE_METADATA_KEY,
WEBSOCKET_TRANSPORT_METADATA_KEY,
UsageBodyField, UsageProviderPerformanceQuery, REALTIME_SESSION_METADATA_KEY,
USAGE_AVAILABLE_METADATA_KEY, USAGE_PRICING_AVAILABLE_METADATA_KEY,
WEBSOCKET_MODE_METADATA_KEY, WEBSOCKET_TRANSPORT_METADATA_KEY,
};
use serde_json::{json, Value};
@@ -2675,13 +2730,26 @@ mod tests {
fn websocket_transport_uses_typed_request_metadata() {
let mut usage = sample_usage();
assert!(!usage.is_websocket());
assert!(usage.usage_available());
assert!(usage.usage_pricing_available());
usage.request_metadata = Some(json!({
WEBSOCKET_MODE_METADATA_KEY: true,
WEBSOCKET_TRANSPORT_METADATA_KEY: "responses",
(WEBSOCKET_MODE_METADATA_KEY): true,
(WEBSOCKET_TRANSPORT_METADATA_KEY): "responses",
(USAGE_AVAILABLE_METADATA_KEY): false,
(USAGE_PRICING_AVAILABLE_METADATA_KEY): false,
(REALTIME_SESSION_METADATA_KEY): {
"input_audio_tokens": 7,
"output_audio_tokens": 3,
},
}));
assert!(usage.is_websocket());
assert_eq!(usage.websocket_transport(), Some("responses"));
assert!(!usage.usage_available());
assert!(!usage.usage_pricing_available());
assert_eq!(usage.realtime_input_audio_tokens(), Some(7));
assert_eq!(usage.realtime_output_audio_tokens(), Some(3));
}
#[test]