feat(gateway): add Codex Live transport

This commit is contained in:
ZheFox
2026-08-20 21:59:05 +08:00
parent 6916e9da76
commit 4185ad1b1e
35 changed files with 6683 additions and 85 deletions
@@ -22,6 +22,7 @@ use crate::ai_serving::AiExecutionDecision;
use crate::execution_runtime::transport::{
build_browser_wreq_client, build_request_headers, ExecutionTransportControls,
};
use crate::frontdoor_loop_guard::gateway_frontdoor_self_loop_guard_error;
use crate::handlers::proxy::websocket::session::{
WebSocketSessionLimits, RELAY_WRITE_TIMEOUT, TEARDOWN_WRITE_TIMEOUT,
};
@@ -30,6 +31,7 @@ use crate::handlers::proxy::websocket::session::{
pub(crate) struct UpstreamWebSocketErrorCodes {
pub(crate) upstream_url_missing: &'static str,
pub(crate) upstream_url_invalid: &'static str,
pub(crate) frontdoor_self_loop: &'static str,
pub(crate) headers_invalid: &'static str,
pub(crate) client_build_failed: &'static str,
pub(crate) proxy_invalid: &'static str,
@@ -53,7 +55,11 @@ pub(crate) async fn connect_upstream_websocket(
.upstream_url
.as_deref()
.ok_or(errors.upstream_url_missing)?;
let upstream_url = websocket_upstream_url(upstream_url, errors.upstream_url_invalid)?;
let upstream_url = guarded_websocket_upstream_url(
upstream_url,
errors.upstream_url_invalid,
errors.frontdoor_self_loop,
)?;
let headers =
websocket_handshake_headers(&decision.provider_request_headers, errors.headers_invalid)?;
let client = build_websocket_client(decision, errors)?;
@@ -79,6 +85,18 @@ pub(crate) async fn connect_upstream_websocket(
})
}
fn guarded_websocket_upstream_url(
raw: &str,
invalid_code: &'static str,
frontdoor_self_loop_code: &'static str,
) -> Result<Url, &'static str> {
let upstream_url = websocket_upstream_url(raw, invalid_code)?;
if gateway_frontdoor_self_loop_guard_error(upstream_url.as_str()).is_some() {
return Err(frontdoor_self_loop_code);
}
Ok(upstream_url)
}
fn websocket_response_headers(headers: &HeaderMap) -> BTreeMap<String, String> {
headers
.iter()
@@ -310,6 +328,19 @@ pub(crate) fn upstream_message_to_client(message: WreqWsMessage) -> AxumWsMessag
}
}
pub(crate) fn client_message_to_upstream(message: AxumWsMessage) -> WreqWsMessage {
match message {
AxumWsMessage::Text(text) => WreqWsMessage::Text(text.to_string().into()),
AxumWsMessage::Binary(data) => WreqWsMessage::Binary(data),
AxumWsMessage::Ping(data) => WreqWsMessage::Ping(data),
AxumWsMessage::Pong(data) => WreqWsMessage::Pong(data),
AxumWsMessage::Close(frame) => WreqWsMessage::Close(frame.map(|frame| WreqCloseFrame {
code: frame.code.into(),
reason: frame.reason.to_string().into(),
})),
}
}
/// Builds a Responses WebSocket error event in the shape understood by the
/// official client implementations. The status is part of the event body,
/// not the WebSocket handshake, because the connection is already upgraded.
@@ -467,11 +498,12 @@ pub(crate) async fn close_client_socket(client_socket: &mut WebSocket, code: u16
#[cfg(test)]
mod tests {
use super::{
bounded_send, responses_websocket_error_event,
bounded_send, guarded_websocket_upstream_url, responses_websocket_error_event,
responses_websocket_error_event_with_stream_id, websocket_handshake_headers,
websocket_response_headers, websocket_upstream_url, WebSocketWriteError,
RELAY_WRITE_TIMEOUT, TEARDOWN_WRITE_TIMEOUT,
};
use crate::frontdoor_loop_guard::configured_gateway_frontdoor_base_url;
use axum::http::HeaderMap;
use std::collections::BTreeMap;
use std::time::Duration;
@@ -587,6 +619,39 @@ mod tests {
assert!(websocket_upstream_url("https://[email protected]/responses", "invalid").is_err());
}
#[test]
fn rejects_responses_websocket_frontdoor_self_loop_before_connecting() {
let base_url = configured_gateway_frontdoor_base_url();
let raw_url = format!("{base_url}/v1/responses");
assert_eq!(
guarded_websocket_upstream_url(
raw_url.as_str(),
"responses_upstream_url_invalid",
"responses_websocket_frontdoor_self_loop",
),
Err("responses_websocket_frontdoor_self_loop")
);
}
#[test]
fn rejects_live_direct_and_sideband_frontdoor_self_loops_before_connecting() {
let base_url = configured_gateway_frontdoor_base_url();
for path in ["/v1/live", "/v1/live/rtc_test"] {
let raw_url = format!("{base_url}{path}");
assert_eq!(
guarded_websocket_upstream_url(
raw_url.as_str(),
"codex_live_upstream_url_invalid",
"codex_live_websocket_frontdoor_self_loop",
),
Err("codex_live_websocket_frontdoor_self_loop"),
"{path} must be rejected before an upstream handshake"
);
}
}
#[test]
fn upstream_handshake_keeps_provider_auth_but_drops_transport_managed_headers() {
let provider_headers = BTreeMap::from([