feat(openai): align GPT-5.6 and Codex request contracts

This commit is contained in:
MMEXA
2026-07-11 07:40:12 +08:00
parent bc1da3bf3f
commit dfa121dd5b
178 changed files with 17947 additions and 3541 deletions
@@ -3223,6 +3223,9 @@ async fn gateway_executes_codex_cli_sync_via_local_decision_gate_after_oauth_ref
model: String,
authorization: String,
x_client_request_id: String,
session_id: String,
thread_id: String,
prompt_cache_key: String,
stream_present: bool,
plan_stream: bool,
}
@@ -3509,6 +3512,25 @@ async fn gateway_executes_codex_cli_sync_via_local_decision_gate_after_oauth_ref
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
session_id: payload
.get("headers")
.and_then(|value| value.get("session-id"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
thread_id: payload
.get("headers")
.and_then(|value| value.get("thread-id"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
prompt_cache_key: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("prompt_cache_key"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
stream_present: payload
.get("body")
.and_then(|value| value.get("json_body"))
@@ -3646,7 +3668,16 @@ async fn gateway_executes_codex_cli_sync_via_local_decision_gate_after_oauth_ref
);
assert_eq!(
seen_execution_runtime_request.x_client_request_id,
"trace-codex-cli-local-123"
seen_execution_runtime_request.thread_id
);
assert_eq!(
seen_execution_runtime_request.session_id,
seen_execution_runtime_request.thread_id
);
assert!(seen_execution_runtime_request.prompt_cache_key.is_empty());
assert_ne!(
seen_execution_runtime_request.thread_id,
seen_execution_runtime_request.trace_id
);
assert!(seen_execution_runtime_request.stream_present);
assert!(seen_execution_runtime_request.plan_stream);
@@ -2,7 +2,6 @@ use super::{
any, build_router_with_state, build_state_with_execution_runtime_override, json, start_server,
to_bytes, Arc, Body, Json, Mutex, Request, Router, StatusCode, TRACE_ID_HEADER,
};
use crate::ai_serving::CODEX_OPENAI_IMAGE_INTERNAL_MODEL;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::auth::{
InMemoryAuthApiKeySnapshotRepository, StoredAuthApiKeySnapshot,
@@ -411,10 +410,10 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
url: String,
authorization: String,
model: String,
action: String,
prompt: String,
image_url: String,
request_stream: bool,
body_stream: Option<bool>,
}
fn hash_api_key(value: &str) -> String {
@@ -574,12 +573,6 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
.and_then(|value| value.get("json_body"))
.cloned()
.unwrap_or_else(|| json!({}));
let content = body_json
.get("input")
.and_then(|value| value.get(0))
.and_then(|value| value.get("content"))
.cloned()
.unwrap_or_else(|| json!([]));
*seen_execution_runtime_inner
.lock()
.expect("mutex should lock") = Some(SeenExecutionRuntimeSyncRequest {
@@ -605,39 +598,22 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
action: body_json
.get("tools")
.and_then(|value| value.get(0))
.and_then(|value| value.get("action"))
prompt: body_json
.get("prompt")
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
prompt: content
.as_array()
.into_iter()
.flatten()
.find(|item| {
item.get("type").and_then(|value| value.as_str()) == Some("input_text")
})
.and_then(|item| item.get("text"))
image_url: body_json
.get("image")
.and_then(|value| value.get("image_url"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
image_url: content
.as_array()
.into_iter()
.flatten()
.find(|item| {
item.get("type").and_then(|value| value.as_str()) == Some("input_image")
})
.and_then(|item| item.get("image_url"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
request_stream: body_json
request_stream: payload
.get("stream")
.and_then(|value| value.as_bool())
.unwrap_or(true),
body_stream: body_json.get("stream").and_then(serde_json::Value::as_bool),
});
Json(json!({
"request_id": "trace-gemini-image-to-openai-123",
@@ -647,21 +623,16 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
},
"body": {
"json_body": {
"id": "resp_img_bridge_123",
"object": "response",
"created": 1776839946,
"model": "gpt-image-2-upstream",
"status": "completed",
"usage": {
"input_tokens": 3,
"output_tokens": 4,
"total_tokens": 7
},
"output": [{
"type": "image_generation_call",
"status": "completed",
"output_format": "png",
"data": [{
"revised_prompt": "converted gemini prompt",
"result": "aGVsbG8="
"b64_json": "aGVsbG8="
}]
}
},
@@ -750,14 +721,13 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
);
assert_eq!(
seen_execution_runtime_request.url,
"https://api.openai.com/v1/images/generations"
"https://api.openai.com/v1/images/edits"
);
assert_eq!(
seen_execution_runtime_request.authorization,
"Bearer sk-upstream-openai-image"
);
assert_eq!(seen_execution_runtime_request.model, "gpt-image-2-upstream");
assert_eq!(seen_execution_runtime_request.action, "edit");
assert_eq!(
seen_execution_runtime_request.prompt,
"Change the background"
@@ -767,6 +737,7 @@ async fn gateway_converts_gemini_image_sync_to_openai_image_provider_impl() {
"data:image/png;base64,aGVsbG8="
);
assert!(!seen_execution_runtime_request.request_stream);
assert_eq!(seen_execution_runtime_request.body_stream, None);
gateway_handle.abort();
execution_runtime_handle.abort();
@@ -785,18 +756,9 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
struct SeenExecutionRuntimeSyncRequest {
trace_id: String,
url: String,
model: String,
authorization: String,
x_client_request_id: String,
prompt: String,
content_is_string: bool,
tool_type: String,
tool_size: String,
tool_quality: String,
tool_background: String,
tool_choice_type: String,
tool_has_n: bool,
request_stream: bool,
headers: serde_json::Value,
body: serde_json::Value,
plan_stream: bool,
}
@@ -1011,98 +973,18 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
model: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("model"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
authorization: payload
.get("headers")
.and_then(|value| value.get("authorization"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
x_client_request_id: payload
.get("headers")
.and_then(|value| value.get("x-client-request-id"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
prompt: payload
headers: payload.get("headers").cloned().unwrap_or_default(),
body: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("input"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("content"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
content_is_string: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("input"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("content"))
.is_some_and(|value| value.is_string()),
tool_type: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("type"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_size: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("size"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_quality: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("quality"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_background: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("background"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_choice_type: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tool_choice"))
.and_then(|value| value.get("type"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_has_n: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.as_object())
.is_some_and(|object| object.contains_key("n")),
request_stream: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("stream"))
.and_then(|value| value.as_bool())
.unwrap_or(false),
.cloned()
.unwrap_or_default(),
plan_stream: payload
.get("stream")
.and_then(|value| value.as_bool())
@@ -1112,20 +994,11 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
"request_id": "trace-codex-image-local-123",
"status_code": 200,
"headers": {
"content-type": "text/event-stream"
"content-type": "application/json"
},
"body": {
"body_bytes_b64": base64::engine::general_purpose::STANDARD.encode(
concat!(
"data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_img_123\",\"created_at\":1776839946}}\n\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"ig_123\",\"type\":\"image_generation_call\",\"status\":\"generating\",\"output_format\":\"png\",\"quality\":\"medium\",\"size\":\"1024x1024\",\"revised_prompt\":\"中国历史视觉海报\",\"result\":\"aGVsbG8=\"}}\n\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_img_123\",\"object\":\"response\",\"model\":\"__CODEX_IMAGE_MODEL__\",\"status\":\"completed\",\"output\":[],\"usage\":{\"input_tokens\":2440,\"output_tokens\":184,\"total_tokens\":2624},\"tool_usage\":{\"image_gen\":{\"input_tokens\":171,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":171},\"output_tokens\":1372,\"output_tokens_details\":{\"image_tokens\":1372,\"text_tokens\":0},\"total_tokens\":1543}}}}\n\n",
"data: [DONE]\n\n"
)
.replace(
"__CODEX_IMAGE_MODEL__",
CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
)
r#"{"created":1776839946,"data":[{"b64_json":"aGVsbG8=","revised_prompt":"水墨视觉海报"}],"usage":{"input_tokens":171,"output_tokens":1372,"total_tokens":1543}}"#
)
},
"telemetry": {
@@ -1182,7 +1055,7 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
format!("Bearer {client_api_key}"),
)
.header(TRACE_ID_HEADER, "trace-codex-image-local-123")
.body("{\"model\":\"gpt-image-2\",\"prompt\":\"生成一张中国历史视觉海报\",\"size\":\"1024x1024\",\"n\":1,\"response_format\":\"b64_json\"}")
.body("{\"model\":\"gpt-image-2\",\"prompt\":\"生成一张水墨视觉海报\",\"background\":\"auto\",\"quality\":\"auto\",\"size\":\"auto\",\"n\":1,\"response_format\":\"b64_json\"}")
.send()
.await
.expect("request should succeed");
@@ -1191,10 +1064,7 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
let response_json: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!(response_json["created"], 1776839946);
assert_eq!(response_json["data"][0]["b64_json"], "aGVsbG8=");
assert_eq!(
response_json["data"][0]["revised_prompt"],
"中国历史视觉海报"
);
assert_eq!(response_json["data"][0]["revised_prompt"], "水墨视觉海报");
assert_eq!(response_json["usage"]["input_tokens"], 171);
assert_eq!(response_json["usage"]["output_tokens"], 1372);
@@ -1229,35 +1099,34 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
);
assert_eq!(
seen_execution_runtime_request.url,
"https://chatgpt.com/backend-api/codex/responses"
);
assert_eq!(
seen_execution_runtime_request.model,
CODEX_OPENAI_IMAGE_INTERNAL_MODEL
"https://chatgpt.com/backend-api/codex/images/generations"
);
assert_eq!(
seen_execution_runtime_request.authorization,
"Bearer refreshed-codex-image-access-token"
);
assert_eq!(
seen_execution_runtime_request.x_client_request_id,
"trace-codex-image-local-123"
seen_execution_runtime_request.body,
json!({
"prompt": "生成一张水墨视觉海报",
"background": "auto",
"model": "gpt-image-2",
"n": 1,
"quality": "auto",
"size": "auto"
})
);
assert_eq!(
seen_execution_runtime_request.prompt,
"生成一张中国历史视觉海报"
seen_execution_runtime_request.headers["user-agent"],
"codex_cli_rs/0.144.1"
);
assert!(seen_execution_runtime_request.content_is_string);
assert_eq!(seen_execution_runtime_request.tool_type, "image_generation");
assert_eq!(seen_execution_runtime_request.tool_size, "1024x1024");
assert_eq!(seen_execution_runtime_request.tool_quality, "high");
assert_eq!(seen_execution_runtime_request.tool_background, "auto");
assert_eq!(
seen_execution_runtime_request.tool_choice_type,
"image_generation"
seen_execution_runtime_request.headers["originator"],
"codex_cli_rs"
);
assert!(!seen_execution_runtime_request.tool_has_n);
assert!(seen_execution_runtime_request.request_stream);
for header in ["x-client-request-id", "session-id", "thread-id"] {
assert!(seen_execution_runtime_request.headers.get(header).is_none());
}
assert!(!seen_execution_runtime_request.plan_stream);
let persisted_transport_state =