mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 10:27:46 +08:00
feat(openai): align GPT-5.6 and Codex request contracts
This commit is contained in:
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user