From aa3e2ddc8f5c575125eb5ef0fe8c5a2ec86e7be1 Mon Sep 17 00:00:00 2001 From: zyycn Date: Sat, 25 Jul 2026 03:26:09 +0800 Subject: [PATCH] fix(openai): align context and catalog tolerance Co-authored-by: Codex --- .../providers/openai/src/transport/catalog.rs | 3 + .../providers/openai/src/transport/request.rs | 86 +++++++------------ .../openai/tests/transport/catalog.rs | 20 ++++- .../openai/tests/transport/request.rs | 81 +++++++++++++++++ 4 files changed, 132 insertions(+), 58 deletions(-) diff --git a/backend/crates/providers/openai/src/transport/catalog.rs b/backend/crates/providers/openai/src/transport/catalog.rs index b8d5b0086..300f33b45 100644 --- a/backend/crates/providers/openai/src/transport/catalog.rs +++ b/backend/crates/providers/openai/src/transport/catalog.rs @@ -165,6 +165,9 @@ pub enum CodexCatalogVisibility { Hide, /// 不进入 picker。 None, + /// 上游新增的可见性值;保留模型条目但不作本地可见性判断。 + #[serde(other)] + Unknown, } /// 一个已完整校验的 Codex 真实模型。 diff --git a/backend/crates/providers/openai/src/transport/request.rs b/backend/crates/providers/openai/src/transport/request.rs index c90a2f3a3..89090a208 100644 --- a/backend/crates/providers/openai/src/transport/request.rs +++ b/backend/crates/providers/openai/src/transport/request.rs @@ -1,6 +1,7 @@ //! 核心 Generate operation 到 Codex Responses wire request 的严格编码。 use gateway_core::operation::GenerateRequest; +use gateway_protocol::openai::WS_REQUEST_HEADER_RESPONSES_LITE_CLIENT_METADATA_KEY; use serde_json::{Map, Value}; use sha2::{Digest, Sha256}; @@ -127,64 +128,37 @@ struct ExtractedRequestContext { impl ExtractedRequestContext { fn from_body(body: &Map) -> Self { Self { - turn_state: body_or_metadata_string( - body, - &["turnState", "turn_state", "x-codex-turn-state"], - ), - turn_metadata: body_or_metadata_string( - body, - &["turnMetadata", "turn_metadata", "x-codex-turn-metadata"], - ), - beta_features: body_or_metadata_string(body, &["betaFeatures", "beta_features"]), - version: body_or_metadata_string(body, &["version"]), - include_timing_metrics: body_or_metadata_string( - body, - &["includeTimingMetrics", "include_timing_metrics"], - ), - codex_window_id: body_or_metadata_string( - body, - &["codexWindowId", "codex_window_id", "x-codex-window-id"], - ), - parent_thread_id: body_or_metadata_string( - body, - &[ - "parentThreadId", - "parent_thread_id", - "x-codex-parent-thread-id", - ], - ), - conversation_id: body_or_metadata_string(body, &["conversation_id", "conversationId"]), - session_id: body_or_metadata_string(body, &["session_id", "sessionId"]), - thread_id: body_or_metadata_string(body, &["thread_id", "threadId"]), - client_request_id: body_or_metadata_string( - body, - &[ - "x-client-request-id", - "client_request_id", - "clientRequestId", - ], - ), - turn_id: body_or_metadata_string(body, &["turn_id", "turnId", "x-codex-turn-id"]), - responses_lite: body_or_metadata_string( - body, - &["ws_request_header_x_openai_internal_codex_responses_lite"], - ), - memgen_request: body_or_metadata_string( - body, - &["x-openai-memgen-request", "memgen_request"], - ), + turn_state: body_string(body, "turnState"), + turn_metadata: body_string(body, "turnMetadata"), + beta_features: body_string(body, "betaFeatures"), + version: body_string(body, "version"), + include_timing_metrics: body_string(body, "includeTimingMetrics"), + codex_window_id: body_string(body, "codexWindowId"), + parent_thread_id: body_string(body, "parentThreadId"), + conversation_id: body_string(body, "conversation_id"), + session_id: body_string(body, "session_id"), + thread_id: body_string(body, "thread_id"), + client_request_id: body_string(body, "x-client-request-id"), + turn_id: body_string(body, "turn_id"), + // Responses Lite 在 WebSocket body 中的这个键是官方 header 投影, + // 不是普通上下文字段的 metadata 回退。 + responses_lite: body + .get("client_metadata") + .and_then(Value::as_object) + .and_then(|metadata| { + non_empty_string( + metadata.get(WS_REQUEST_HEADER_RESPONSES_LITE_CLIENT_METADATA_KEY), + ) + }), + // Memory consolidation 只由官方请求头提供;body 中同名字段不参与 + // transport 事实提取。 + memgen_request: None, } } } -fn body_or_metadata_string(body: &Map, keys: &[&str]) -> Option { - keys.iter() - .find_map(|key| non_empty_string(body.get(*key))) - .or_else(|| { - let metadata = body.get("client_metadata")?.as_object()?; - keys.iter() - .find_map(|key| non_empty_string(metadata.get(*key))) - }) +fn body_string(body: &Map, key: &str) -> Option { + non_empty_string(body.get(key)) } fn non_empty_string(value: Option<&Value>) -> Option { @@ -528,10 +502,12 @@ fn apply_protocol_context(request: &mut CodexResponsesRequest, context: &Map