mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +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:
@@ -73,7 +73,7 @@ INNER JOIN LATERAL (
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($3) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image')
|
||||
AND LOWER($3) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image', 'codex:live')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'chatgpt_web'
|
||||
@@ -158,7 +158,7 @@ WHERE p.is_active = TRUE
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($3) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image')
|
||||
AND LOWER($3) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image', 'codex:live')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'chatgpt_web'
|
||||
@@ -336,7 +336,7 @@ INNER JOIN LATERAL (
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($4) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image')
|
||||
AND LOWER($4) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image', 'codex:live')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'chatgpt_web'
|
||||
@@ -422,7 +422,7 @@ WHERE p.is_active = TRUE
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($4) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image')
|
||||
AND LOWER($4) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image', 'codex:live')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'chatgpt_web'
|
||||
@@ -608,7 +608,7 @@ WHERE p.is_active = TRUE
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($6) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image')
|
||||
AND LOWER($6) IN ('openai:responses', 'openai:responses:compact', 'openai:search', 'openai:image', 'codex:live')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'chatgpt_web'
|
||||
@@ -971,6 +971,17 @@ impl SqlxMinimalCandidateSelectionReadRepository {
|
||||
}
|
||||
}
|
||||
|
||||
const PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_MARKER: &str =
|
||||
"__AETHER_PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH__";
|
||||
const PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_SQL: &str = r#"(
|
||||
LOWER(BTRIM(fmt.value)) = ANY($3::text[])
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER($4) = 'codex:live'
|
||||
AND LOWER(BTRIM(fmt.value)) = 'openai:responses'
|
||||
)
|
||||
)"#;
|
||||
|
||||
fn requested_model_selection_sql() -> String {
|
||||
LIST_FOR_EXACT_API_FORMAT_AND_GLOBAL_MODEL_SQL
|
||||
.replace(
|
||||
@@ -996,7 +1007,7 @@ fn requested_model_selection_sql() -> String {
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements_text(mapping.value -> 'api_formats') AS fmt(value)
|
||||
WHERE LOWER(BTRIM(fmt.value)) = ANY($3::text[])
|
||||
WHERE __AETHER_PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH__
|
||||
)
|
||||
)
|
||||
AND (
|
||||
@@ -1054,7 +1065,7 @@ fn requested_model_selection_sql() -> String {
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements_text(mapping.value -> 'api_formats') AS fmt(value)
|
||||
WHERE LOWER(BTRIM(fmt.value)) = ANY($3::text[])
|
||||
WHERE __AETHER_PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH__
|
||||
)
|
||||
)
|
||||
AND (
|
||||
@@ -1087,7 +1098,7 @@ fn requested_model_selection_sql() -> String {
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements_text(mapping.value -> 'api_formats') AS fmt(value)
|
||||
WHERE LOWER(BTRIM(fmt.value)) = ANY($3::text[])
|
||||
WHERE __AETHER_PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH__
|
||||
)
|
||||
)
|
||||
AND (
|
||||
@@ -1103,6 +1114,10 @@ fn requested_model_selection_sql() -> String {
|
||||
)
|
||||
)"#,
|
||||
)
|
||||
.replace(
|
||||
PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_MARKER,
|
||||
PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_SQL,
|
||||
)
|
||||
.replace(
|
||||
"ORDER BY\n provider_priority ASC,",
|
||||
"ORDER BY\n global_model_name ASC,\n provider_priority ASC,",
|
||||
@@ -1494,7 +1509,8 @@ mod tests {
|
||||
requested_model_selection_page_sql, requested_model_selection_sql,
|
||||
SqlxMinimalCandidateSelectionReadRepository,
|
||||
LIST_FOR_EXACT_API_FORMAT_AND_GLOBAL_MODEL_SQL, LIST_FOR_EXACT_API_FORMAT_SQL,
|
||||
LIST_POOL_KEYS_FOR_GROUP_SQL,
|
||||
LIST_POOL_KEYS_FOR_GROUP_SQL, PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_MARKER,
|
||||
PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_SQL,
|
||||
};
|
||||
use crate::{PostgresPoolConfig, PostgresPoolFactory};
|
||||
use aether_data_contracts::repository::candidate_selection::{
|
||||
@@ -1566,6 +1582,36 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_selection_sql_allows_codex_live_oauth_auth() {
|
||||
let requested_model_sql = requested_model_selection_sql();
|
||||
for sql in [
|
||||
LIST_FOR_EXACT_API_FORMAT_SQL,
|
||||
LIST_FOR_EXACT_API_FORMAT_AND_GLOBAL_MODEL_SQL,
|
||||
LIST_POOL_KEYS_FOR_GROUP_SQL,
|
||||
requested_model_sql.as_str(),
|
||||
] {
|
||||
assert!(sql.contains("LOWER(BTRIM(p.provider_type)) = 'codex'"));
|
||||
assert!(sql.contains("LOWER(BTRIM(pak.auth_type)) = 'oauth'"));
|
||||
assert!(sql.contains("'codex:live'"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requested_model_sql_scopes_legacy_responses_mapping_to_codex_live() {
|
||||
let sql = requested_model_selection_sql();
|
||||
let compatibility = PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_SQL;
|
||||
|
||||
assert_eq!(sql.matches(compatibility).count(), 3);
|
||||
assert!(!sql.contains(PROVIDER_MODEL_MAPPING_API_FORMAT_MATCH_MARKER));
|
||||
assert!(compatibility.contains("LOWER(BTRIM(p.provider_type)) = 'codex'"));
|
||||
assert!(compatibility.contains("LOWER($4) = 'codex:live'"));
|
||||
assert!(compatibility.contains("LOWER(BTRIM(fmt.value)) = 'openai:responses'"));
|
||||
assert!(!LIST_FOR_EXACT_API_FORMAT_SQL.contains(compatibility));
|
||||
assert!(!LIST_FOR_EXACT_API_FORMAT_AND_GLOBAL_MODEL_SQL.contains(compatibility));
|
||||
assert!(!LIST_POOL_KEYS_FOR_GROUP_SQL.contains(compatibility));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_selection_sql_allows_grok_oauth_chat_auth() {
|
||||
let requested_model_sql = requested_model_selection_sql();
|
||||
|
||||
@@ -382,6 +382,30 @@ mod tests {
|
||||
assert_eq!(pending_migrations_from_applied(&[]), all_up_migrations());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embeds_scoped_codex_live_permission_migration() {
|
||||
let migration = POSTGRES_MIGRATOR
|
||||
.iter()
|
||||
.find(|migration| migration.version == 20260821000000)
|
||||
.expect("Codex Live permission migration should be embedded");
|
||||
let sql = migration.sql.as_ref();
|
||||
|
||||
for required_fragment in [
|
||||
"UPDATE public.users",
|
||||
"UPDATE public.user_groups",
|
||||
"UPDATE public.api_keys",
|
||||
"UPDATE public.provider_api_keys",
|
||||
"provider.provider_type",
|
||||
"openai:responses",
|
||||
"codex:live",
|
||||
] {
|
||||
assert!(
|
||||
sql.contains(required_fragment),
|
||||
"Codex Live permission migration is missing {required_fragment}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_index_migrations_opt_out_of_transactions() {
|
||||
for version in [20260715000000, 20260715130000, 20260720000000] {
|
||||
|
||||
@@ -1640,6 +1640,21 @@ fn push_postgres_usage_client_family_filter(
|
||||
.push_bind(client_family.to_ascii_lowercase());
|
||||
}
|
||||
|
||||
fn push_postgres_usage_websocket_filter(
|
||||
builder: &mut QueryBuilder<'_, Postgres>,
|
||||
has_where: &mut bool,
|
||||
is_websocket: Option<bool>,
|
||||
) {
|
||||
let Some(is_websocket) = is_websocket else {
|
||||
return;
|
||||
};
|
||||
|
||||
push_postgres_usage_where(builder, has_where);
|
||||
builder
|
||||
.push("LOWER(COALESCE(\"usage\".request_metadata->>'websocket_mode', 'false')) = ")
|
||||
.push_bind(if is_websocket { "true" } else { "false" });
|
||||
}
|
||||
|
||||
fn push_postgres_usage_exclude_unknown_filter(
|
||||
builder: &mut QueryBuilder<'_, Postgres>,
|
||||
has_where: &mut bool,
|
||||
@@ -3015,6 +3030,7 @@ ORDER BY request_count DESC, "usage".provider_name ASC
|
||||
has_where = true;
|
||||
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
|
||||
}
|
||||
push_postgres_usage_websocket_filter(&mut builder, &mut has_where, query.is_websocket);
|
||||
if query.error_only {
|
||||
builder.push(if has_where { " AND " } else { " WHERE " });
|
||||
builder.push(
|
||||
@@ -3127,6 +3143,7 @@ OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''
|
||||
has_where = true;
|
||||
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
|
||||
}
|
||||
push_postgres_usage_websocket_filter(&mut builder, &mut has_where, query.is_websocket);
|
||||
if query.error_only {
|
||||
builder.push(if has_where { " AND " } else { " WHERE " });
|
||||
has_where = true;
|
||||
@@ -3320,6 +3337,7 @@ OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''
|
||||
has_where = true;
|
||||
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
|
||||
}
|
||||
push_postgres_usage_websocket_filter(&mut builder, &mut has_where, query.is_websocket);
|
||||
if query.error_only {
|
||||
builder.push(if has_where { " AND " } else { " WHERE " });
|
||||
builder.push(
|
||||
@@ -3421,6 +3439,7 @@ OR (\"usage\".error_message IS NOT NULL AND BTRIM(\"usage\".error_message) <> ''
|
||||
has_where = true;
|
||||
builder.push("\"usage\".is_stream = ").push_bind(is_stream);
|
||||
}
|
||||
push_postgres_usage_websocket_filter(&mut builder, &mut has_where, query.is_websocket);
|
||||
if query.error_only {
|
||||
builder.push(if has_where { " AND " } else { " WHERE " });
|
||||
has_where = true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{TimeZone, Utc};
|
||||
use serde_json::json;
|
||||
use sqlx::Row;
|
||||
use sqlx::{Postgres, QueryBuilder, Row};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{
|
||||
@@ -8,14 +8,15 @@ use super::{
|
||||
attach_usage_routing_snapshot_metadata, attach_usage_settlement_pricing_snapshot_metadata,
|
||||
clear_previous_request_body_facts, inflate_usage_json_value,
|
||||
prepare_request_metadata_for_body_storage, prepare_usage_body_storage,
|
||||
prepare_usage_upsert_context, request_body_capture_replaces_derived_facts,
|
||||
resolved_read_usage_body_ref, resolved_write_usage_body_ref,
|
||||
split_dashboard_daily_aggregate_range, split_dashboard_hourly_aggregate_range,
|
||||
usage_body_capture_state_for_storage, usage_body_ref, usage_capture_update_allowed,
|
||||
usage_effective_input_tokens, usage_http_audit_body_refs, usage_http_audit_capture_mode,
|
||||
usage_routing_snapshot_from_usage, usage_settlement_pricing_snapshot_from_usage,
|
||||
usage_total_input_context, AggregateRangeSplit, SqlxUsageReadRepository, UsageHttpAuditRefs,
|
||||
UsageRoutingSnapshot, UsageSettlementPricingSnapshot, MAX_INLINE_USAGE_BODY_BYTES,
|
||||
prepare_usage_upsert_context, push_postgres_usage_websocket_filter,
|
||||
request_body_capture_replaces_derived_facts, resolved_read_usage_body_ref,
|
||||
resolved_write_usage_body_ref, split_dashboard_daily_aggregate_range,
|
||||
split_dashboard_hourly_aggregate_range, usage_body_capture_state_for_storage, usage_body_ref,
|
||||
usage_capture_update_allowed, usage_effective_input_tokens, usage_http_audit_body_refs,
|
||||
usage_http_audit_capture_mode, usage_routing_snapshot_from_usage,
|
||||
usage_settlement_pricing_snapshot_from_usage, usage_total_input_context, AggregateRangeSplit,
|
||||
SqlxUsageReadRepository, UsageHttpAuditRefs, UsageRoutingSnapshot,
|
||||
UsageSettlementPricingSnapshot, MAX_INLINE_USAGE_BODY_BYTES,
|
||||
SELECT_STALE_PENDING_USAGE_BATCH_SQL,
|
||||
};
|
||||
use crate::{PostgresPoolConfig, PostgresPoolFactory};
|
||||
@@ -3247,6 +3248,26 @@ fn usage_sql_admin_record_filters_are_pushed_into_postgres_queries() {
|
||||
assert!(source.contains("request_metadata->>'client_family'"));
|
||||
assert!(source.contains("exclude_unknown_model_or_provider"));
|
||||
assert!(source.contains("NOT IN ('unknown', 'unknow')"));
|
||||
assert_eq!(
|
||||
source
|
||||
.matches("push_postgres_usage_websocket_filter(")
|
||||
.count(),
|
||||
5,
|
||||
"the WebSocket filter must cover list/count and keyword list/count"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_sql_websocket_filter_compares_json_metadata_without_boolean_casts() {
|
||||
let mut builder = QueryBuilder::<Postgres>::new("SELECT * FROM usage");
|
||||
let mut has_where = false;
|
||||
push_postgres_usage_websocket_filter(&mut builder, &mut has_where, Some(true));
|
||||
|
||||
assert!(has_where);
|
||||
assert!(builder
|
||||
.sql()
|
||||
.contains("LOWER(COALESCE(\"usage\".request_metadata->>'websocket_mode', 'false'))"));
|
||||
assert!(!builder.sql().contains("::boolean"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user