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
+8 -8
View File
@@ -9,12 +9,12 @@ pub use crate::contracts::{
CLAUDE_CLI_STREAM_SUCCESS_REPORT_KIND, CLAUDE_CLI_SYNC_ERROR_REPORT_KIND,
CLAUDE_CLI_SYNC_FINALIZE_REPORT_KIND, CLAUDE_CLI_SYNC_PLAN_KIND,
CLAUDE_CLI_SYNC_SUCCESS_REPORT_KIND, CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND,
CLAUDE_COUNT_TOKENS_SYNC_SUCCESS_REPORT_KIND, EXECUTION_RUNTIME_STREAM_ACTION,
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_ACTION,
EXECUTION_RUNTIME_SYNC_DECISION_ACTION, GEMINI_CHAT_STREAM_PLAN_KIND,
GEMINI_CHAT_STREAM_SUCCESS_REPORT_KIND, GEMINI_CHAT_SYNC_ERROR_REPORT_KIND,
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND, GEMINI_CHAT_SYNC_PLAN_KIND,
GEMINI_CHAT_SYNC_SUCCESS_REPORT_KIND, GEMINI_CLI_STREAM_PLAN_KIND,
CLAUDE_COUNT_TOKENS_SYNC_SUCCESS_REPORT_KIND, CODEX_LIVE_STREAM_PLAN_KIND,
EXECUTION_RUNTIME_STREAM_ACTION, EXECUTION_RUNTIME_STREAM_DECISION_ACTION,
EXECUTION_RUNTIME_SYNC_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_STREAM_SUCCESS_REPORT_KIND,
GEMINI_CHAT_SYNC_ERROR_REPORT_KIND, GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND,
GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CHAT_SYNC_SUCCESS_REPORT_KIND, GEMINI_CLI_STREAM_PLAN_KIND,
GEMINI_CLI_STREAM_SUCCESS_REPORT_KIND, GEMINI_CLI_SYNC_ERROR_REPORT_KIND,
GEMINI_CLI_SYNC_FINALIZE_REPORT_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
GEMINI_CLI_SYNC_SUCCESS_REPORT_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND,
@@ -33,8 +33,8 @@ pub use crate::contracts::{
OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_STREAM_SUCCESS_REPORT_KIND,
OPENAI_IMAGE_SYNC_ERROR_REPORT_KIND, OPENAI_IMAGE_SYNC_FINALIZE_REPORT_KIND,
OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_IMAGE_SYNC_SUCCESS_REPORT_KIND,
OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_SUCCESS_REPORT_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_SUCCESS_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_ERROR_REPORT_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_FINALIZE_REPORT_KIND, OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_SUCCESS_REPORT_KIND, OPENAI_RESPONSES_STREAM_PLAN_KIND,
@@ -14,20 +14,20 @@ pub use control_payloads::{build_ai_control_plan_request, AiControlPlanRequest};
pub use plan_kinds::{
is_openai_responses_stream_plan_kind, is_openai_responses_sync_plan_kind,
CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND, CLAUDE_CLI_STREAM_PLAN_KIND,
CLAUDE_CLI_SYNC_PLAN_KIND, CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND, GEMINI_CHAT_STREAM_PLAN_KIND,
GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
GEMINI_EMBEDDING_SYNC_PLAN_KIND, GEMINI_FILES_DELETE_PLAN_KIND,
CLAUDE_CLI_SYNC_PLAN_KIND, CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND, CODEX_LIVE_STREAM_PLAN_KIND,
GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND,
GEMINI_CLI_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND, GEMINI_FILES_DELETE_PLAN_KIND,
GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND, GEMINI_FILES_LIST_PLAN_KIND,
GEMINI_FILES_UPLOAD_PLAN_KIND, GEMINI_INTERACTIONS_STREAM_PLAN_KIND,
GEMINI_INTERACTIONS_SYNC_PLAN_KIND, GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND,
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
OPENAI_EMBEDDING_SYNC_PLAN_KIND, OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND,
OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND, OPENAI_RESPONSES_STREAM_PLAN_KIND,
OPENAI_RESPONSES_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND, OPENAI_VIDEO_CONTENT_PLAN_KIND,
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND,
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND, OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND,
OPENAI_RESPONSES_STREAM_PLAN_KIND, OPENAI_RESPONSES_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_PLAN_KIND, OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND,
OPENAI_VIDEO_CONTENT_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
};
pub use report_kinds::{
core_error_background_report_kind, core_error_default_client_api_format,
@@ -17,6 +17,8 @@ pub const CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND: &str = "claude_count_tokens_sync";
pub const GEMINI_CHAT_STREAM_PLAN_KIND: &str = "gemini_chat_stream";
pub const GEMINI_INTERACTIONS_STREAM_PLAN_KIND: &str = "gemini_interactions_stream";
pub const OPENAI_RESPONSES_STREAM_PLAN_KIND: &str = "openai_responses_stream";
pub const OPENAI_REALTIME_STREAM_PLAN_KIND: &str = "openai_realtime_stream";
pub const CODEX_LIVE_STREAM_PLAN_KIND: &str = "codex_live_stream";
pub const OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND: &str = "openai_responses_compact_stream";
pub const CLAUDE_CLI_STREAM_PLAN_KIND: &str = "claude_cli_stream";
pub const GEMINI_CLI_STREAM_PLAN_KIND: &str = "gemini_cli_stream";
@@ -5,6 +5,7 @@ use std::{fmt, str::FromStr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FormatFamily {
OpenAi,
Codex,
Claude,
Gemini,
Jina,
@@ -23,9 +24,11 @@ pub enum FormatId {
OpenAiChat,
OpenAiResponses,
OpenAiResponsesCompact,
OpenAiRealtime,
OpenAiSearch,
OpenAiEmbedding,
OpenAiRerank,
CodexLive,
ClaudeMessages,
GeminiGenerateContent,
GeminiInteractions,
@@ -50,9 +53,11 @@ impl FormatId {
Self::OpenAiChat
| Self::OpenAiResponses
| Self::OpenAiResponsesCompact
| Self::OpenAiRealtime
| Self::OpenAiSearch
| Self::OpenAiEmbedding
| Self::OpenAiRerank => FormatFamily::OpenAi,
Self::CodexLive => FormatFamily::Codex,
Self::ClaudeMessages => FormatFamily::Claude,
Self::GeminiGenerateContent | Self::GeminiInteractions | Self::GeminiEmbedding => {
FormatFamily::Gemini
@@ -75,9 +80,11 @@ impl FormatId {
Self::OpenAiChat => "openai:chat",
Self::OpenAiResponses => "openai:responses",
Self::OpenAiResponsesCompact => "openai:responses:compact",
Self::OpenAiRealtime => "openai:realtime",
Self::OpenAiSearch => "openai:search",
Self::OpenAiEmbedding => "openai:embedding",
Self::OpenAiRerank => "openai:rerank",
Self::CodexLive => "codex:live",
Self::ClaudeMessages => "claude:messages",
Self::GeminiGenerateContent => "gemini:generate_content",
Self::GeminiInteractions => "gemini:interactions",
@@ -106,11 +113,15 @@ impl FromStr for FormatId {
"openai:responses:compact" | "/v1/responses/compact" => {
Ok(Self::OpenAiResponsesCompact)
}
"openai:realtime" | "openai_realtime" | "realtime" | "/v1/realtime" => {
Ok(Self::OpenAiRealtime)
}
"openai:search" | "openai_search" | "search" | "/v1/alpha/search" => {
Ok(Self::OpenAiSearch)
}
"openai:embedding" | "/v1/embeddings" => Ok(Self::OpenAiEmbedding),
"openai:rerank" | "/v1/rerank" => Ok(Self::OpenAiRerank),
"codex:live" | "codex_live" | "live" | "/v1/live" => Ok(Self::CodexLive),
"claude:messages" | "/v1/messages" => Ok(Self::ClaudeMessages),
"gemini:generate_content" => Ok(Self::GeminiGenerateContent),
"gemini:interactions"
@@ -272,6 +283,59 @@ mod tests {
assert_eq!(FormatId::parse("gemini:cli"), None);
}
#[test]
fn realtime_and_codex_live_are_distinct_first_class_formats() {
use super::{FormatFamily, FormatProfile};
for alias in [
"openai:realtime",
"OPENAI_REALTIME",
"realtime",
"/v1/realtime",
] {
assert_eq!(FormatId::parse(alias), Some(FormatId::OpenAiRealtime));
assert_eq!(normalize_api_format_alias(alias), "openai:realtime");
}
for alias in ["codex:live", "CODEX_LIVE", "live", "/v1/live"] {
assert_eq!(FormatId::parse(alias), Some(FormatId::CodexLive));
assert_eq!(normalize_api_format_alias(alias), "codex:live");
}
assert_eq!(FormatId::OpenAiRealtime.family(), FormatFamily::OpenAi);
assert_eq!(FormatId::CodexLive.family(), FormatFamily::Codex);
assert_eq!(FormatId::OpenAiRealtime.profile(), FormatProfile::Default);
assert_eq!(FormatId::CodexLive.profile(), FormatProfile::Default);
assert!(!api_format_uses_body_stream_field("openai:realtime"));
assert!(!api_format_uses_body_stream_field("codex:live"));
}
#[test]
fn realtime_live_and_responses_permissions_do_not_cover_each_other() {
for (allowed, requested) in [
("openai:responses", "openai:realtime"),
("openai:responses", "codex:live"),
("openai:realtime", "openai:responses"),
("openai:realtime", "codex:live"),
("codex:live", "openai:responses"),
("codex:live", "openai:realtime"),
] {
assert!(!api_format_permission_covers(allowed, requested));
}
assert!(api_format_permission_covers(
"OPENAI_REALTIME",
"/v1/realtime"
));
assert!(api_format_permission_covers("CODEX_LIVE", "/v1/live"));
assert_eq!(
api_format_permission_storage_aliases("openai:realtime"),
vec!["openai:realtime".to_string()]
);
assert_eq!(
api_format_permission_storage_aliases("codex:live"),
vec!["codex:live".to_string()]
);
}
#[test]
fn responses_permission_covers_its_companion_endpoints() {
assert!(api_format_permission_covers(
@@ -57,6 +57,9 @@ pub fn request_candidate_api_format_preference(
if client_api_format == "openai:responses:compact" {
return (provider_api_format == "openai:responses:compact").then_some((0, 0));
}
if matches!(client_api_format.as_str(), "openai:realtime" | "codex:live") {
return (provider_api_format == client_api_format).then_some((0, 0));
}
if client_api_format == "openai:search" {
return (provider_api_format == "openai:search").then_some((0, 0));
}
@@ -112,6 +115,12 @@ pub fn request_candidate_api_formats(
if client_api_format == "openai:responses:compact" {
return vec!["openai:responses:compact"];
}
if client_api_format == "openai:realtime" {
return vec!["openai:realtime"];
}
if client_api_format == "codex:live" {
return vec!["codex:live"];
}
if client_api_format == "openai:search" {
return vec!["openai:search"];
}
@@ -746,6 +755,34 @@ mod tests {
);
}
#[test]
fn websocket_transports_only_select_their_exact_wire_format() {
assert_eq!(
request_candidate_api_formats("/v1/realtime", true),
vec!["openai:realtime"]
);
assert_eq!(
request_candidate_api_formats("/v1/live", true),
vec!["codex:live"]
);
assert_eq!(
request_candidate_api_format_preference("openai:realtime", "openai:realtime"),
Some((0, 0))
);
assert_eq!(
request_candidate_api_format_preference("codex:live", "codex:live"),
Some((0, 0))
);
assert_eq!(
request_candidate_api_format_preference("openai:realtime", "openai:responses"),
None
);
assert_eq!(
request_candidate_api_format_preference("codex:live", "openai:responses"),
None
);
}
#[test]
fn request_conversion_enable_flag_only_applies_to_real_data_format_conversions() {
assert!(!request_conversion_requires_enable_flag(
@@ -69,6 +69,7 @@ enum CodexOpenAiEndpointKind {
Compact,
Search,
Images,
Live,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -652,6 +653,8 @@ fn codex_openai_endpoint_kind(
Some(CodexOpenAiEndpointKind::Search)
} else if is_openai_image_request(provider_api_format) {
Some(CodexOpenAiEndpointKind::Images)
} else if aether_ai_formats::api_format_alias_matches(provider_api_format, "codex:live") {
Some(CodexOpenAiEndpointKind::Live)
} else {
None
}
@@ -2079,7 +2082,13 @@ pub fn apply_codex_openai_special_headers(
}
return;
}
if endpoint_kind == CodexOpenAiEndpointKind::Images {
if matches!(
endpoint_kind,
CodexOpenAiEndpointKind::Images | CodexOpenAiEndpointKind::Live
) {
if endpoint_kind == CodexOpenAiEndpointKind::Live {
remove_btree_header(provider_request_headers, CODEX_RESPONSES_LITE_HEADER);
}
return;
}
@@ -2873,6 +2882,42 @@ mod tests {
assert!(!headers.contains_key("accept"));
}
#[test]
fn codex_live_uses_account_identity_without_responses_lite_headers() {
let mut headers = std::collections::BTreeMap::from([(
CODEX_RESPONSES_LITE_HEADER.to_string(),
"true".to_string(),
)]);
apply_codex_openai_special_headers(
&mut headers,
&json!({"model": "gpt-live"}),
&http::HeaderMap::new(),
"codex",
"codex:live",
Some("request-live"),
Some(r#"{"account_id":"account-live","is_fedramp":true}"#),
);
assert_eq!(
headers.get("chatgpt-account-id").map(String::as_str),
Some("account-live")
);
assert_eq!(
headers.get("x-openai-fedramp").map(String::as_str),
Some("true")
);
assert_eq!(
headers.get("user-agent").map(String::as_str),
Some(CODEX_CLIENT_USER_AGENT)
);
assert_eq!(
headers.get("originator").map(String::as_str),
Some(CODEX_CLIENT_ORIGINATOR)
);
assert!(!headers.contains_key(CODEX_RESPONSES_LITE_HEADER));
}
#[test]
fn standard_codex_models_do_not_send_the_responses_lite_header() {
let mut headers = std::collections::BTreeMap::from([(
@@ -38,7 +38,10 @@ pub fn parse_request(
FormatId::GeminiEmbedding => gemini::embedding::request::from(body, ctx),
FormatId::DoubaoEmbedding => doubao::embedding::request::from(body, ctx),
FormatId::AliyunMultimodalEmbedding => aliyun::embedding::request::from(body, ctx),
FormatId::OpenAiSearch | FormatId::GeminiInteractions => None,
FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::CodexLive
| FormatId::GeminiInteractions => None,
}
.ok_or_else(|| FormatError::RequestParseFailed {
format: source.as_str().to_string(),
@@ -72,7 +75,10 @@ fn emit_request_inner(
FormatId::GeminiEmbedding => gemini::embedding::request::to(request, ctx),
FormatId::DoubaoEmbedding => doubao::embedding::request::to(request, ctx),
FormatId::AliyunMultimodalEmbedding => aliyun::embedding::request::to(request, ctx),
FormatId::OpenAiSearch | FormatId::GeminiInteractions => None,
FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::CodexLive
| FormatId::GeminiInteractions => None,
}
.ok_or_else(|| FormatError::RequestEmitFailed {
format: target.as_str().to_string(),
@@ -296,6 +302,7 @@ pub fn parse_response(
FormatId::ClaudeMessages => claude_messages::response::from(body, ctx),
FormatId::GeminiGenerateContent => gemini_generate_content::response::from(body, ctx),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::JinaEmbedding
| FormatId::OpenAiRerank
@@ -303,6 +310,7 @@ pub fn parse_response(
| FormatId::GeminiEmbedding
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive
| FormatId::GeminiInteractions => None,
}
.ok_or_else(|| FormatError::ResponseParseFailed {
@@ -335,6 +343,7 @@ fn emit_response_inner(
FormatId::ClaudeMessages => claude_messages::response::to(response, ctx),
FormatId::GeminiGenerateContent => gemini_generate_content::response::to(response, ctx),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::JinaEmbedding
| FormatId::OpenAiRerank
@@ -342,6 +351,7 @@ fn emit_response_inner(
| FormatId::GeminiEmbedding
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive
| FormatId::GeminiInteractions => None,
}
.ok_or_else(|| FormatError::ResponseEmitFailed {
@@ -1086,13 +1096,15 @@ fn standard_request_root_field_is_audited(source: FormatId, key: &str) -> bool {
| "tools"
),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::OpenAiRerank
| FormatId::GeminiEmbedding
| FormatId::JinaEmbedding
| FormatId::JinaRerank
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding => true,
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive => true,
}
}
@@ -1575,13 +1587,15 @@ fn validate_source_response_stop_enums(
FormatId::GeminiGenerateContent => validate_gemini_response_finish_reasons(body, target),
FormatId::GeminiInteractions => Ok(()),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::OpenAiRerank
| FormatId::GeminiEmbedding
| FormatId::JinaEmbedding
| FormatId::JinaRerank
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding => Ok(()),
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive => Ok(()),
}
}
@@ -1,12 +1,13 @@
use crate::contracts::{
ApiOperation, CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND,
CLAUDE_CLI_STREAM_PLAN_KIND, CLAUDE_CLI_SYNC_PLAN_KIND, CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND,
CLAUDE_COUNT_TOKENS_SYNC_SUCCESS_REPORT_KIND, GEMINI_CHAT_STREAM_PLAN_KIND,
GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
GEMINI_EMBEDDING_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND,
GEMINI_INTERACTIONS_STREAM_PLAN_KIND, GEMINI_INTERACTIONS_STREAM_SUCCESS_REPORT_KIND,
GEMINI_INTERACTIONS_SYNC_PLAN_KIND, GEMINI_INTERACTIONS_SYNC_SUCCESS_REPORT_KIND,
OPENAI_EMBEDDING_SYNC_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
CLAUDE_COUNT_TOKENS_SYNC_SUCCESS_REPORT_KIND, CODEX_LIVE_STREAM_PLAN_KIND,
GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND,
GEMINI_CLI_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND,
GEMINI_EMBEDDING_SYNC_SUCCESS_REPORT_KIND, GEMINI_INTERACTIONS_STREAM_PLAN_KIND,
GEMINI_INTERACTIONS_STREAM_SUCCESS_REPORT_KIND, GEMINI_INTERACTIONS_SYNC_PLAN_KIND,
GEMINI_INTERACTIONS_SYNC_SUCCESS_REPORT_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND,
OPENAI_REALTIME_STREAM_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_SUCCESS_REPORT_KIND,
};
@@ -114,6 +115,22 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec>
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
match plan_kind {
CODEX_LIVE_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "codex:live",
decision_kind: CODEX_LIVE_STREAM_PLAN_KIND,
report_kind: "codex_live_websocket_success",
family: LocalSameFormatProviderFamily::Standard,
require_streaming: true,
operation: None,
}),
OPENAI_REALTIME_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "openai:realtime",
decision_kind: OPENAI_REALTIME_STREAM_PLAN_KIND,
report_kind: "openai_realtime_websocket_success",
family: LocalSameFormatProviderFamily::Standard,
require_streaming: true,
operation: None,
}),
CLAUDE_CHAT_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "claude:messages",
decision_kind: CLAUDE_CHAT_STREAM_PLAN_KIND,
@@ -178,6 +195,24 @@ mod tests {
assert!(spec.require_streaming);
}
#[test]
fn resolves_openai_realtime_websocket_same_format_spec() {
let spec = resolve_stream_spec("openai_realtime_stream").expect("spec");
assert_eq!(spec.api_format, "openai:realtime");
assert_eq!(spec.report_kind, "openai_realtime_websocket_success");
assert!(spec.require_streaming);
assert_eq!(spec.family, super::LocalSameFormatProviderFamily::Standard);
}
#[test]
fn resolves_codex_live_websocket_same_format_spec() {
let spec = resolve_stream_spec("codex_live_stream").expect("spec");
assert_eq!(spec.api_format, "codex:live");
assert_eq!(spec.report_kind, "codex_live_websocket_success");
assert!(spec.require_streaming);
assert_eq!(spec.family, super::LocalSameFormatProviderFamily::Standard);
}
#[test]
fn resolves_openai_embedding_sync_same_format_spec() {
let spec = resolve_sync_spec("openai_embedding_sync").expect("spec");
@@ -4,13 +4,14 @@ use url::form_urlencoded;
use crate::contracts::{
ClientSurface, CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND,
CLAUDE_CLI_STREAM_PLAN_KIND, CLAUDE_CLI_SYNC_PLAN_KIND, CLAUDE_COUNT_TOKENS_SYNC_PLAN_KIND,
GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND,
GEMINI_CLI_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND, GEMINI_FILES_DELETE_PLAN_KIND,
GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND, GEMINI_FILES_LIST_PLAN_KIND,
GEMINI_FILES_UPLOAD_PLAN_KIND, GEMINI_INTERACTIONS_STREAM_PLAN_KIND,
GEMINI_INTERACTIONS_SYNC_PLAN_KIND, GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND,
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
OPENAI_EMBEDDING_SYNC_PLAN_KIND, OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND,
CODEX_LIVE_STREAM_PLAN_KIND, GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND,
GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND,
GEMINI_FILES_DELETE_PLAN_KIND, GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND,
GEMINI_FILES_LIST_PLAN_KIND, GEMINI_FILES_UPLOAD_PLAN_KIND,
GEMINI_INTERACTIONS_STREAM_PLAN_KIND, GEMINI_INTERACTIONS_SYNC_PLAN_KIND,
GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND, GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND,
OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_REALTIME_STREAM_PLAN_KIND,
OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND, OPENAI_RESPONSES_STREAM_PLAN_KIND,
OPENAI_RESPONSES_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
@@ -116,6 +117,22 @@ pub fn resolve_execution_runtime_stream_plan_kind_with_client_surface(
return Some(OPENAI_RESPONSES_STREAM_PLAN_KIND);
}
if route_family == Some("openai")
&& route_kind == Some("realtime")
&& *method == Method::GET
&& path == "/v1/realtime"
{
return Some(OPENAI_REALTIME_STREAM_PLAN_KIND);
}
if route_family == Some("codex")
&& route_kind == Some("live")
&& ((*method == Method::GET && (path == "/v1/live" || path.starts_with("/v1/live/")))
|| (*method == Method::POST && path == "/v1/live"))
{
return Some(CODEX_LIVE_STREAM_PLAN_KIND);
}
if route_family == Some("openai")
&& route_kind == Some("image")
&& *method == Method::POST
@@ -465,7 +482,7 @@ pub fn sanitize_request_path_and_query(path: &str, query: Option<&str>) -> Optio
fn request_query_key_is_safe_to_trace(key: &str) -> bool {
matches!(
key.to_ascii_lowercase().as_str(),
"alt" | "view" | "pagesize" | "page_size" | "limit" | "offset"
"alt" | "view" | "pagesize" | "page_size" | "limit" | "offset" | "model"
)
}
@@ -541,6 +558,8 @@ pub fn supports_stream_execution_decision_kind(plan_kind: &str) -> bool {
matches!(
plan_kind,
OPENAI_CHAT_STREAM_PLAN_KIND
| CODEX_LIVE_STREAM_PLAN_KIND
| OPENAI_REALTIME_STREAM_PLAN_KIND
| CLAUDE_CHAT_STREAM_PLAN_KIND
| GEMINI_CHAT_STREAM_PLAN_KIND
| OPENAI_RESPONSES_STREAM_PLAN_KIND
@@ -567,14 +586,15 @@ mod tests {
};
use crate::contracts::{
CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND, CLAUDE_CLI_STREAM_PLAN_KIND,
CLAUDE_CLI_SYNC_PLAN_KIND, GEMINI_CHAT_STREAM_PLAN_KIND, GEMINI_CHAT_SYNC_PLAN_KIND,
GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND, GEMINI_EMBEDDING_SYNC_PLAN_KIND,
GEMINI_INTERACTIONS_STREAM_PLAN_KIND, GEMINI_INTERACTIONS_SYNC_PLAN_KIND,
OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND,
OPENAI_IMAGE_STREAM_PLAN_KIND, OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_RERANK_SYNC_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND, OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND,
OPENAI_RESPONSES_STREAM_PLAN_KIND, OPENAI_RESPONSES_SYNC_PLAN_KIND,
OPENAI_SEARCH_SYNC_PLAN_KIND,
CLAUDE_CLI_SYNC_PLAN_KIND, CODEX_LIVE_STREAM_PLAN_KIND, GEMINI_CHAT_STREAM_PLAN_KIND,
GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
GEMINI_EMBEDDING_SYNC_PLAN_KIND, GEMINI_INTERACTIONS_STREAM_PLAN_KIND,
GEMINI_INTERACTIONS_SYNC_PLAN_KIND, OPENAI_CHAT_STREAM_PLAN_KIND,
OPENAI_CHAT_SYNC_PLAN_KIND, OPENAI_EMBEDDING_SYNC_PLAN_KIND, OPENAI_IMAGE_STREAM_PLAN_KIND,
OPENAI_IMAGE_SYNC_PLAN_KIND, OPENAI_REALTIME_STREAM_PLAN_KIND,
OPENAI_RERANK_SYNC_PLAN_KIND, OPENAI_RESPONSES_COMPACT_STREAM_PLAN_KIND,
OPENAI_RESPONSES_COMPACT_SYNC_PLAN_KIND, OPENAI_RESPONSES_STREAM_PLAN_KIND,
OPENAI_RESPONSES_SYNC_PLAN_KIND, OPENAI_SEARCH_SYNC_PLAN_KIND,
};
#[test]
@@ -646,6 +666,59 @@ mod tests {
));
}
#[test]
fn resolves_openai_realtime_as_websocket_stream_only() {
assert_eq!(
resolve_execution_runtime_stream_plan_kind(
Some("ai_public"),
Some("openai"),
Some("realtime"),
None,
&Method::GET,
"/v1/realtime",
),
Some(OPENAI_REALTIME_STREAM_PLAN_KIND)
);
assert_eq!(
resolve_execution_runtime_sync_plan_kind(
Some("ai_public"),
Some("openai"),
Some("realtime"),
None,
&Method::GET,
"/v1/realtime",
),
None
);
assert!(supports_stream_execution_decision_kind(
OPENAI_REALTIME_STREAM_PLAN_KIND
));
}
#[test]
fn resolves_codex_live_call_create_and_websocket_routes() {
for (method, path) in [
(Method::POST, "/v1/live"),
(Method::GET, "/v1/live"),
(Method::GET, "/v1/live/rtc_opaque"),
] {
assert_eq!(
resolve_execution_runtime_stream_plan_kind(
Some("ai_public"),
Some("codex"),
Some("live"),
None,
&method,
path,
),
Some(CODEX_LIVE_STREAM_PLAN_KIND)
);
}
assert!(supports_stream_execution_decision_kind(
CODEX_LIVE_STREAM_PLAN_KIND
));
}
#[test]
fn resolves_openai_responses_compact_plan_kinds() {
assert_eq!(
@@ -892,6 +965,14 @@ mod tests {
.as_deref(),
Some("/v1/live/{call_id}?alt=sse")
);
assert_eq!(
sanitize_request_path_and_query(
"/v1/realtime?model=gpt-realtime-2.1&api_key=secret&token=hidden",
None
)
.as_deref(),
Some("/v1/realtime?model=gpt-realtime-2.1")
);
}
#[test]
@@ -418,6 +418,7 @@ impl ProviderStreamParser {
FormatId::ClaudeMessages => Self::Claude(ClaudeProviderState::default()),
FormatId::GeminiGenerateContent => Self::Gemini(GeminiProviderState::default()),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::OpenAiRerank
| FormatId::GeminiEmbedding
@@ -425,7 +426,8 @@ impl ProviderStreamParser {
| FormatId::JinaEmbedding
| FormatId::JinaRerank
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding => return None,
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive => return None,
})
}
@@ -537,6 +539,7 @@ impl ClientStreamEmitter {
FormatId::ClaudeMessages => Self::Claude(ClaudeClientEmitter::default()),
FormatId::GeminiGenerateContent => Self::Gemini(GeminiClientEmitter::default()),
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::OpenAiRerank
| FormatId::GeminiEmbedding
@@ -544,7 +547,8 @@ impl ClientStreamEmitter {
| FormatId::JinaEmbedding
| FormatId::JinaRerank
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding => return None,
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive => return None,
})
}
@@ -660,13 +664,15 @@ fn parse_provider_error(
parse_gemini_error(payload)
}
FormatId::OpenAiEmbedding
| FormatId::OpenAiRealtime
| FormatId::OpenAiSearch
| FormatId::OpenAiRerank
| FormatId::GeminiEmbedding
| FormatId::JinaEmbedding
| FormatId::JinaRerank
| FormatId::DoubaoEmbedding
| FormatId::AliyunMultimodalEmbedding => None,
| FormatId::AliyunMultimodalEmbedding
| FormatId::CodexLive => None,
}
}