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
@@ -310,6 +310,11 @@ fn usage_matches_list_query(item: &StoredRequestUsageAudit, query: &UsageAuditLi
return false;
}
}
if let Some(is_websocket) = query.is_websocket {
if item.is_websocket() != is_websocket {
return false;
}
}
if query.error_only
&& item.status != "failed"
&& item.status_code.unwrap_or_default() < 400
@@ -391,6 +396,11 @@ fn usage_matches_keyword_search_query(
return false;
}
}
if let Some(is_websocket) = query.is_websocket {
if item.is_websocket() != is_websocket {
return false;
}
}
if query.error_only
&& item.status != "failed"
&& item.status_code.unwrap_or_default() < 400
@@ -2396,7 +2406,9 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
continue;
}
let entry = totals.entry(api_key_id.to_string()).or_insert(0);
*entry = (*entry).saturating_add(item.total_tokens);
if item.usage_available() {
*entry = (*entry).saturating_add(item.total_tokens);
}
}
Ok(totals)
}
@@ -2436,7 +2448,9 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
..Default::default()
});
entry.request_count = entry.request_count.saturating_add(1);
entry.total_tokens = entry.total_tokens.saturating_add(item.total_tokens);
if item.usage_available() {
entry.total_tokens = entry.total_tokens.saturating_add(item.total_tokens);
}
}
Ok(totals.into_values().collect())
}
@@ -2469,8 +2483,10 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
..StoredProviderApiKeyUsageSummary::default()
});
entry.request_count = entry.request_count.saturating_add(1);
entry.total_tokens = entry.total_tokens.saturating_add(item.total_tokens);
entry.total_cost_usd += item.total_cost_usd;
if item.usage_available() {
entry.total_tokens = entry.total_tokens.saturating_add(item.total_tokens);
entry.total_cost_usd += item.total_cost_usd;
}
entry.last_used_at_unix_secs = Some(
entry
.last_used_at_unix_secs
@@ -2524,8 +2540,10 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
}
summary.request_count = summary.request_count.saturating_add(1);
summary.total_tokens = summary.total_tokens.saturating_add(item.total_tokens);
summary.total_cost_usd += item.total_cost_usd;
if item.usage_available() {
summary.total_tokens = summary.total_tokens.saturating_add(item.total_tokens);
summary.total_cost_usd += item.total_cost_usd;
}
}
summaries.push(summary);
@@ -2598,6 +2616,9 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
let date_key = dt.date_naive().to_string();
let entry = daily.entry(date_key).or_insert((0, 0, 0.0, 0.0));
entry.0 += 1;
if !item.usage_available() {
continue;
}
let cache_creation = if item.cache_creation_input_tokens == 0
&& (item.cache_creation_ephemeral_5m_input_tokens
+ item.cache_creation_ephemeral_1h_input_tokens)