mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 09:57:47 +08:00
feat(openai): align GPT-5.6 and Codex request contracts
This commit is contained in:
@@ -463,8 +463,8 @@ async fn gateway_executes_openai_chat_stream_via_local_openai_responses_cross_fo
|
||||
accept: String,
|
||||
authorization: String,
|
||||
x_client_request_id: String,
|
||||
session_id: String,
|
||||
conversation_id: String,
|
||||
codex_session_id: String,
|
||||
codex_thread_id: String,
|
||||
instructions: String,
|
||||
user_text: String,
|
||||
prompt_cache_key: String,
|
||||
@@ -742,15 +742,15 @@ async fn gateway_executes_openai_chat_stream_via_local_openai_responses_cross_fo
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
session_id: payload
|
||||
codex_session_id: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("session_id"))
|
||||
.and_then(|value| value.get("session-id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
conversation_id: payload
|
||||
codex_thread_id: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("conversation_id"))
|
||||
.and_then(|value| value.get("thread-id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
@@ -889,19 +889,16 @@ async fn gateway_executes_openai_chat_stream_via_local_openai_responses_cross_fo
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_client_request_id,
|
||||
"trace-openai-chat-cli-local-123"
|
||||
seen_execution_runtime_request.codex_thread_id
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.prompt_cache_key,
|
||||
"bc749eb7-a9e2-5793-8d14-abd659c700b0"
|
||||
seen_execution_runtime_request.codex_session_id,
|
||||
seen_execution_runtime_request.codex_thread_id
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.session_id,
|
||||
"d1e9b802644e1f52"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.conversation_id,
|
||||
"d1e9b802644e1f52"
|
||||
assert!(seen_execution_runtime_request.prompt_cache_key.is_empty());
|
||||
assert_ne!(
|
||||
seen_execution_runtime_request.codex_thread_id,
|
||||
seen_execution_runtime_request.trace_id
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.instructions,
|
||||
|
||||
@@ -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,
|
||||
@@ -55,13 +54,9 @@ async fn gateway_executes_codex_image_stream_via_local_decision_gate_after_oauth
|
||||
struct SeenExecutionRuntimeStreamRequest {
|
||||
trace_id: String,
|
||||
url: String,
|
||||
model: String,
|
||||
authorization: String,
|
||||
x_client_request_id: String,
|
||||
tool_type: String,
|
||||
tool_action: String,
|
||||
tool_partial_images: Option<u64>,
|
||||
request_stream: bool,
|
||||
headers: serde_json::Value,
|
||||
body: serde_json::Value,
|
||||
plan_stream: bool,
|
||||
}
|
||||
|
||||
@@ -180,7 +175,7 @@ async fn gateway_executes_codex_image_stream_via_local_decision_gate_after_oauth
|
||||
None,
|
||||
Some(2),
|
||||
None,
|
||||
Some(serde_json::json!({"upstream_stream_policy":"force_stream"})),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
@@ -272,65 +267,26 @@ async fn gateway_executes_codex_image_stream_via_local_decision_gate_after_oauth
|
||||
.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(),
|
||||
tool_type: 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("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_action: 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("action"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
tool_partial_images: 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("partial_images"))
|
||||
.and_then(|value| value.as_u64()),
|
||||
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())
|
||||
.unwrap_or(false),
|
||||
});
|
||||
let frames = concat!(
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.output_item.done\\ndata: {\\\"type\\\":\\\"response.output_item.done\\\",\\\"output_index\\\":0,\\\"item\\\":{\\\"id\\\":\\\"ig_123\\\",\\\"type\\\":\\\"image_generation_call\\\",\\\"result\\\":\\\"aGVsbG8=\\\"}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.completed\\ndata: {\\\"type\\\":\\\"response.completed\\\",\\\"response\\\":{\\\"tool_usage\\\":{\\\"image_gen\\\":{\\\"input_tokens\\\":11,\\\"output_tokens\\\":22,\\\"total_tokens\\\":33}}}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"application/json\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"{\\\"created\\\":1776991097,\\\"data\\\":[{\\\"b64_json\\\":\\\"aGVsbG8=\\\",\\\"revised_prompt\\\":\\\"水墨视觉海报\\\"}],\\\"usage\\\":{\\\"input_tokens\\\":11,\\\"output_tokens\\\":22,\\\"total_tokens\\\":33}}\"}}\n",
|
||||
"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":41}}}\n",
|
||||
"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n"
|
||||
);
|
||||
@@ -397,26 +353,28 @@ async fn gateway_executes_codex_image_stream_via_local_decision_gate_after_oauth
|
||||
)
|
||||
.header(TRACE_ID_HEADER, "trace-codex-image-stream-local-123")
|
||||
.body(
|
||||
"{\"model\":\"gpt-image-2\",\"prompt\":\"生成一张中国历史视觉海报\",\"stream\":true,\"partial_images\":1}",
|
||||
"{\"model\":\"gpt-image-2\",\"prompt\":\"生成一张水墨视觉海报\",\"background\":\"auto\",\"quality\":\"auto\",\"size\":\"auto\",\"stream\":true,\"response_format\":\"b64_json\"}",
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("text/event-stream")
|
||||
);
|
||||
let response_status = response.status();
|
||||
let response_content_type = response
|
||||
.headers()
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::to_string);
|
||||
let response_text = response.text().await.expect("body should read");
|
||||
assert!(response_text.contains("event: image_generation.partial_image"));
|
||||
assert!(response_text.contains("\"type\":\"image_generation.partial_image\""));
|
||||
assert!(response_text.contains("\"b64_json\":\"aGVsbG8=\""));
|
||||
assert_eq!(response_status, StatusCode::OK, "{response_text}");
|
||||
assert_eq!(
|
||||
response_content_type.as_deref(),
|
||||
Some("text/event-stream"),
|
||||
"{response_text}"
|
||||
);
|
||||
assert!(response_text.contains("event: image_generation.completed"));
|
||||
assert!(response_text.contains("\"type\":\"image_generation.completed\""));
|
||||
assert!(response_text.contains("\"b64_json\":\"aGVsbG8=\""));
|
||||
assert!(response_text.contains("\"total_tokens\":33"));
|
||||
assert!(!response_text.contains("response.completed"));
|
||||
|
||||
@@ -444,25 +402,34 @@ async fn gateway_executes_codex_image_stream_via_local_decision_gate_after_oauth
|
||||
);
|
||||
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-stream-access-token"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_client_request_id,
|
||||
"trace-codex-image-stream-local-123"
|
||||
seen_execution_runtime_request.body,
|
||||
json!({
|
||||
"prompt": "生成一张水墨视觉海报",
|
||||
"background": "auto",
|
||||
"model": "gpt-image-2",
|
||||
"quality": "auto",
|
||||
"size": "auto"
|
||||
})
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.tool_type, "image_generation");
|
||||
assert_eq!(seen_execution_runtime_request.tool_action, "generate");
|
||||
assert_eq!(seen_execution_runtime_request.tool_partial_images, Some(1));
|
||||
assert!(seen_execution_runtime_request.request_stream);
|
||||
assert!(seen_execution_runtime_request.plan_stream);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.headers["user-agent"],
|
||||
"codex_cli_rs/0.144.1"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.headers["originator"],
|
||||
"codex_cli_rs"
|
||||
);
|
||||
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);
|
||||
|
||||
gateway_handle.abort();
|
||||
execution_runtime_handle.abort();
|
||||
@@ -594,7 +561,7 @@ async fn gateway_bridges_codex_image_sync_json_to_streaming_image_sse_impl() {
|
||||
None,
|
||||
Some(2),
|
||||
None,
|
||||
Some(serde_json::json!({"upstream_stream_policy":"force_stream"})),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
@@ -772,8 +739,8 @@ async fn gateway_bridges_codex_image_sync_json_to_streaming_image_sse_impl() {
|
||||
seen_execution_runtime_request.trace_id,
|
||||
"trace-codex-image-stream-json-123"
|
||||
);
|
||||
assert!(seen_execution_runtime_request.request_stream);
|
||||
assert!(seen_execution_runtime_request.plan_stream);
|
||||
assert!(!seen_execution_runtime_request.request_stream);
|
||||
assert!(!seen_execution_runtime_request.plan_stream);
|
||||
|
||||
gateway_handle.abort();
|
||||
execution_runtime_handle.abort();
|
||||
@@ -1152,12 +1119,14 @@ async fn gateway_routes_openai_responses_stream_image_intent_to_openai_image_pla
|
||||
seen_plan.url,
|
||||
"https://images.example.com/v1/images/generations"
|
||||
);
|
||||
assert!(seen_plan.plan_stream);
|
||||
assert!(!seen_plan.plan_stream);
|
||||
assert_eq!(seen_plan.auth_header, "Bearer sk-upstream-image-bridge");
|
||||
assert_eq!(seen_plan.body_json["stream"], true);
|
||||
assert_eq!(seen_plan.body_json["input"], "Draw a mountain observatory");
|
||||
assert_eq!(seen_plan.body_json["tools"][0]["type"], "image_generation");
|
||||
assert_eq!(seen_plan.body_json["tools"][0]["size"], "1024x1024");
|
||||
assert_eq!(seen_plan.body_json["model"], "gpt-image-2");
|
||||
assert_eq!(seen_plan.body_json["prompt"], "Draw a mountain observatory");
|
||||
assert_eq!(seen_plan.body_json["size"], "1024x1024");
|
||||
assert!(seen_plan.body_json.get("stream").is_none());
|
||||
assert!(seen_plan.body_json.get("input").is_none());
|
||||
assert!(seen_plan.body_json.get("tools").is_none());
|
||||
|
||||
gateway_handle.abort();
|
||||
execution_runtime_handle.abort();
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
any, build_router_with_state, build_state_with_execution_runtime_override, json,
|
||||
run_stream_cli_test, start_server, to_bytes, Arc, Body, Bytes, HeaderName, HeaderValue,
|
||||
Infallible, Json, Mutex, Request, Response, Router, StatusCode,
|
||||
EXECUTION_PATH_EXECUTION_RUNTIME_STREAM, EXECUTION_PATH_HEADER, TRACE_ID_HEADER,
|
||||
EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER, TRACE_ID_HEADER,
|
||||
};
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use aether_data::repository::auth::{
|
||||
@@ -23,33 +23,37 @@ use aether_data_contracts::repository::provider_catalog::{
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[test]
|
||||
fn gateway_executes_openai_responses_compact_stream_via_local_decision_gate_with_local_stream_decision(
|
||||
) {
|
||||
fn gateway_executes_openai_responses_compact_as_unary_request() {
|
||||
run_stream_cli_test(
|
||||
"gateway_executes_openai_responses_compact_stream_via_local_decision_gate_with_local_stream_decision",
|
||||
gateway_executes_openai_responses_compact_stream_via_local_decision_gate_with_local_stream_decision_impl,
|
||||
"gateway_executes_openai_responses_compact_as_unary_request",
|
||||
gateway_executes_openai_responses_compact_as_unary_request_impl,
|
||||
);
|
||||
}
|
||||
|
||||
async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gate_with_local_stream_decision_impl(
|
||||
) {
|
||||
async fn gateway_executes_openai_responses_compact_as_unary_request_impl() {
|
||||
#[derive(Debug, Clone)]
|
||||
struct SeenExecutionRuntimeStreamRequest {
|
||||
trace_id: String,
|
||||
url: String,
|
||||
model: String,
|
||||
content_encoding: String,
|
||||
stream: bool,
|
||||
accept: String,
|
||||
turn_state: String,
|
||||
authorization: String,
|
||||
chatgpt_account_id: String,
|
||||
fedramp: String,
|
||||
responses_lite: String,
|
||||
session_id: String,
|
||||
thread_id: String,
|
||||
x_client_request_id_present: bool,
|
||||
endpoint_tag: String,
|
||||
conditional_header: String,
|
||||
renamed_header: String,
|
||||
dropped_header_present: bool,
|
||||
metadata_mode: String,
|
||||
metadata_source: String,
|
||||
metadata_origin: String,
|
||||
instructions: String,
|
||||
store_present: bool,
|
||||
body: serde_json::Value,
|
||||
proxy_node_id: String,
|
||||
transport_profile_id: String,
|
||||
}
|
||||
@@ -71,7 +75,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
false,
|
||||
Some(serde_json::json!(["openai"])),
|
||||
Some(serde_json::json!(["openai:responses:compact"])),
|
||||
Some(serde_json::json!(["gpt-5"])),
|
||||
Some(serde_json::json!(["gpt-5.6-sol"])),
|
||||
api_key_id.to_string(),
|
||||
Some("default".to_string()),
|
||||
true,
|
||||
@@ -82,7 +86,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
Some(4_102_444_800_i64),
|
||||
Some(serde_json::json!(["openai"])),
|
||||
Some(serde_json::json!(["openai:responses:compact"])),
|
||||
Some(serde_json::json!(["gpt-5"])),
|
||||
Some(serde_json::json!(["gpt-5.6-sol"])),
|
||||
)
|
||||
.expect("auth snapshot should build")
|
||||
}
|
||||
@@ -91,7 +95,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
StoredMinimalCandidateSelectionRow {
|
||||
provider_id: "provider-openai-compact-local-1".to_string(),
|
||||
provider_name: "openai".to_string(),
|
||||
provider_type: "custom".to_string(),
|
||||
provider_type: "codex".to_string(),
|
||||
provider_priority: 10,
|
||||
provider_is_active: true,
|
||||
endpoint_id: "endpoint-openai-compact-local-1".to_string(),
|
||||
@@ -101,7 +105,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
endpoint_is_active: true,
|
||||
key_id: "key-openai-compact-local-1".to_string(),
|
||||
key_name: "prod".to_string(),
|
||||
key_auth_type: "bearer".to_string(),
|
||||
key_auth_type: "oauth".to_string(),
|
||||
key_is_active: true,
|
||||
key_api_formats: Some(vec!["openai:responses:compact".to_string()]),
|
||||
key_allowed_models: None,
|
||||
@@ -110,12 +114,12 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
key_global_priority_by_format: Some(serde_json::json!({"openai:responses:compact": 1})),
|
||||
model_id: "model-openai-compact-local-1".to_string(),
|
||||
global_model_id: "global-model-openai-compact-local-1".to_string(),
|
||||
global_model_name: "gpt-5".to_string(),
|
||||
global_model_name: "gpt-5.6-sol".to_string(),
|
||||
global_model_mappings: None,
|
||||
global_model_supports_streaming: Some(true),
|
||||
model_provider_model_name: "gpt-5-upstream".to_string(),
|
||||
model_provider_model_name: "deployment-production".to_string(),
|
||||
model_provider_model_mappings: Some(vec![StoredProviderModelMapping {
|
||||
name: "gpt-5-upstream".to_string(),
|
||||
name: "deployment-production".to_string(),
|
||||
priority: 1,
|
||||
api_formats: Some(vec!["openai:responses:compact".to_string()]),
|
||||
endpoint_ids: None,
|
||||
@@ -131,7 +135,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
"provider-openai-compact-local-1".to_string(),
|
||||
"openai".to_string(),
|
||||
Some("https://example.com".to_string()),
|
||||
"custom".to_string(),
|
||||
"codex".to_string(),
|
||||
)
|
||||
.expect("provider should build")
|
||||
.with_transport_fields(
|
||||
@@ -161,15 +165,12 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
"https://api.openai.example".to_string(),
|
||||
Some(serde_json::json!([
|
||||
{"action":"set","key":"x-endpoint-tag","value":"openai-compact-local"},
|
||||
{"action":"set","key":"x-conditional-tag","value":"header-condition-hit","condition":{"path":"instructions","op":"exists","source":"current"}},
|
||||
{"action":"set","key":"x-conditional-tag","value":"header-condition-hit","condition":{"path":"reasoning","op":"exists","source":"current"}},
|
||||
{"action":"rename","from":"x-client-rename","to":"x-upstream-rename"},
|
||||
{"action":"drop","key":"x-drop-me"}
|
||||
])),
|
||||
Some(serde_json::json!([
|
||||
{"action":"set","path":"instructions","value":"You are GPT-5.","condition":{"path":"instructions","op":"not_exists","source":"current"}},
|
||||
{"action":"set","path":"metadata.mode","value":"safe","condition":{"path":"metadata.mode","op":"not_exists","source":"current"}},
|
||||
{"action":"rename","from":"metadata.client","to":"metadata.source"},
|
||||
{"action":"set","path":"metadata.origin","value":"from-original","condition":{"path":"metadata.client","op":"exists","source":"original"}},
|
||||
{"action":"set","path":"instructions","value":"Use the configured tools.","condition":{"path":"instructions","op":"not_exists","source":"current"}},
|
||||
{"action":"drop","path":"store"}
|
||||
])),
|
||||
Some(2),
|
||||
@@ -186,7 +187,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
"key-openai-compact-local-1".to_string(),
|
||||
"provider-openai-compact-local-1".to_string(),
|
||||
"prod".to_string(),
|
||||
"bearer".to_string(),
|
||||
"oauth".to_string(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
@@ -198,7 +199,13 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
"sk-upstream-openai-compact",
|
||||
)
|
||||
.expect("api key should encrypt"),
|
||||
None,
|
||||
Some(
|
||||
encrypt_python_fernet_plaintext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
r#"{"account_id":"acc-compact-local-123","is_fedramp":true}"#,
|
||||
)
|
||||
.expect("auth config should encrypt"),
|
||||
),
|
||||
None,
|
||||
Some(serde_json::json!({"openai:responses:compact": 1})),
|
||||
None,
|
||||
@@ -242,7 +249,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/gateway/decision-stream",
|
||||
"/api/internal/gateway/decision-sync",
|
||||
any(move |_request: Request| {
|
||||
let decision_hits_inner = Arc::clone(&decision_hits_clone);
|
||||
async move {
|
||||
@@ -252,7 +259,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/gateway/plan-stream",
|
||||
"/api/internal/gateway/plan-sync",
|
||||
any(move |_request: Request| {
|
||||
let plan_hits_inner = Arc::clone(&plan_hits_clone);
|
||||
async move {
|
||||
@@ -262,7 +269,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/gateway/report-stream",
|
||||
"/api/internal/gateway/report-sync",
|
||||
any(move |request: Request| {
|
||||
let seen_report_inner = Arc::clone(&seen_report_clone);
|
||||
async move {
|
||||
@@ -299,135 +306,166 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
);
|
||||
|
||||
let execution_runtime = Router::new().route(
|
||||
"/v1/execute/stream",
|
||||
"/v1/execute/sync",
|
||||
any(move |request: Request| {
|
||||
let seen_execution_runtime_inner = Arc::clone(&seen_execution_runtime_clone);
|
||||
async move {
|
||||
let (parts, body) = request.into_parts();
|
||||
let raw_body = to_bytes(body, usize::MAX).await.expect("body should read");
|
||||
let payload: serde_json::Value =
|
||||
serde_json::from_slice(&raw_body).expect("execution runtime payload should parse");
|
||||
*seen_execution_runtime_inner.lock().expect("mutex should lock") =
|
||||
Some(SeenExecutionRuntimeStreamRequest {
|
||||
trace_id: parts
|
||||
.headers
|
||||
.get(TRACE_ID_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
url: payload
|
||||
.get("url")
|
||||
.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(),
|
||||
stream: payload
|
||||
.get("stream")
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false),
|
||||
accept: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("accept"))
|
||||
.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(),
|
||||
endpoint_tag: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-endpoint-tag"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
conditional_header: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-conditional-tag"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
renamed_header: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-upstream-rename"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
dropped_header_present: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-drop-me"))
|
||||
.is_some(),
|
||||
metadata_mode: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("metadata"))
|
||||
.and_then(|value| value.get("mode"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
metadata_source: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("metadata"))
|
||||
.and_then(|value| value.get("source"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
metadata_origin: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("metadata"))
|
||||
.and_then(|value| value.get("origin"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
instructions: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("instructions"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
store_present: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("store"))
|
||||
.is_some(),
|
||||
proxy_node_id: payload
|
||||
.get("proxy")
|
||||
.and_then(|value| value.get("node_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
transport_profile_id: payload
|
||||
.get("transport_profile").and_then(|value| value.get("profile_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
});
|
||||
let stream = concat!(
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.completed\\ndata: {\\\"type\\\":\\\"response.completed\\\",\\\"response\\\":{\\\"id\\\":\\\"resp-compact-local-123\\\",\\\"object\\\":\\\"response\\\",\\\"model\\\":\\\"gpt-5-upstream\\\",\\\"output\\\":[]}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":41,\"ttfb_ms\":11}}}\n",
|
||||
"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n"
|
||||
);
|
||||
let mut response = Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.body(Body::from(stream))
|
||||
.expect("response should build");
|
||||
response.headers_mut().insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/x-ndjson"),
|
||||
);
|
||||
response
|
||||
let payload: serde_json::Value = serde_json::from_slice(&raw_body)
|
||||
.expect("execution runtime payload should parse");
|
||||
*seen_execution_runtime_inner
|
||||
.lock()
|
||||
.expect("mutex should lock") = Some(SeenExecutionRuntimeStreamRequest {
|
||||
trace_id: parts
|
||||
.headers
|
||||
.get(TRACE_ID_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
url: payload
|
||||
.get("url")
|
||||
.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(),
|
||||
content_encoding: payload
|
||||
.get("content_encoding")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
stream: payload
|
||||
.get("stream")
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false),
|
||||
accept: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("accept"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
turn_state: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-codex-turn-state"))
|
||||
.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(),
|
||||
chatgpt_account_id: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("chatgpt-account-id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
fedramp: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-openai-fedramp"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
responses_lite: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-openai-internal-codex-responses-lite"))
|
||||
.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(),
|
||||
x_client_request_id_present: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-client-request-id"))
|
||||
.is_some(),
|
||||
endpoint_tag: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-endpoint-tag"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
conditional_header: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-conditional-tag"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
renamed_header: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-upstream-rename"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
dropped_header_present: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-drop-me"))
|
||||
.is_some(),
|
||||
instructions: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("instructions"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
store_present: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("store"))
|
||||
.is_some(),
|
||||
body: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
proxy_node_id: payload
|
||||
.get("proxy")
|
||||
.and_then(|value| value.get("node_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
transport_profile_id: payload
|
||||
.get("transport_profile")
|
||||
.and_then(|value| value.get("profile_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
});
|
||||
Json(json!({
|
||||
"request_id": "trace-openai-compact-local-123",
|
||||
"status_code": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json",
|
||||
"x-codex-turn-state": "turn-state-compact-123"
|
||||
},
|
||||
"body": {
|
||||
"json_body": {
|
||||
"output": [{
|
||||
"type": "compaction",
|
||||
"id": "cmp-compact-local-123",
|
||||
"encrypted_content": "encrypted-compact-history"
|
||||
}]
|
||||
}
|
||||
},
|
||||
"telemetry": {"elapsed_ms": 41, "ttfb_ms": 11}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -474,23 +512,40 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
)
|
||||
.header("x-client-rename", "rename-openai-compact")
|
||||
.header("x-drop-me", "drop-openai-compact")
|
||||
.header("x-codex-turn-state", "turn-state-inbound-123")
|
||||
.header("session-id", "session-compact-local-123")
|
||||
.header("thread-id", "thread-compact-local-123")
|
||||
.header(TRACE_ID_HEADER, "trace-openai-compact-local-123")
|
||||
.body("{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true,\"metadata\":{\"client\":\"desktop-openai-compact\"},\"store\":false}")
|
||||
.body(r#"{"model":"gpt-5.6-sol","input":"hello","client_metadata":{"origin":"codex"},"include":["reasoning.encrypted_content"],"store":false,"stream":true,"stream_options":{"reasoning_summary_delivery":"sequential_cutoff"},"tool_choice":"auto","parallel_tool_calls":true,"reasoning":{"effort":"high"},"text":{"verbosity":"medium"},"tools":[{"type":"function","name":"lookup","parameters":{"type":"object"}}],"prompt_cache_key":"session:compact-e2e"}"#)
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
if response.status() != StatusCode::OK {
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body = response.text().await.expect("error body should read");
|
||||
panic!("Compact request failed: status={status}, headers={headers:?}, body={body}");
|
||||
}
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get("x-codex-turn-state")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("turn-state-compact-123")
|
||||
);
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(EXECUTION_PATH_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some(EXECUTION_PATH_EXECUTION_RUNTIME_STREAM)
|
||||
Some(EXECUTION_PATH_EXECUTION_RUNTIME_SYNC)
|
||||
);
|
||||
let body: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(
|
||||
body["output"][0]["encrypted_content"],
|
||||
"encrypted-compact-history"
|
||||
);
|
||||
let body = response.text().await.expect("body should read");
|
||||
assert!(body.contains("event: response.completed"));
|
||||
assert!(body.contains("\"model\":\"gpt-5-upstream\""));
|
||||
|
||||
let seen_execution_runtime_request = seen_execution_runtime
|
||||
.lock()
|
||||
@@ -505,13 +560,36 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
seen_execution_runtime_request.url,
|
||||
"https://api.openai.example/custom/v1/responses/compact"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.model, "gpt-5-upstream");
|
||||
assert!(seen_execution_runtime_request.stream);
|
||||
assert_eq!(seen_execution_runtime_request.accept, "text/event-stream");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.model,
|
||||
"deployment-production"
|
||||
);
|
||||
assert!(seen_execution_runtime_request.content_encoding.is_empty());
|
||||
assert!(!seen_execution_runtime_request.stream);
|
||||
assert_ne!(seen_execution_runtime_request.accept, "text/event-stream");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.turn_state,
|
||||
"turn-state-inbound-123"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.authorization,
|
||||
"Bearer sk-upstream-openai-compact"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.chatgpt_account_id,
|
||||
"acc-compact-local-123"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.fedramp, "true");
|
||||
assert_eq!(seen_execution_runtime_request.responses_lite, "true");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.session_id,
|
||||
"session-compact-local-123"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.thread_id,
|
||||
"thread-compact-local-123"
|
||||
);
|
||||
assert!(!seen_execution_runtime_request.x_client_request_id_present);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.endpoint_tag,
|
||||
"openai-compact-local"
|
||||
@@ -525,20 +603,61 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
"rename-openai-compact"
|
||||
);
|
||||
assert!(!seen_execution_runtime_request.dropped_header_present);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.instructions,
|
||||
"You are GPT-5."
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.metadata_mode, "safe");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.metadata_source,
|
||||
"desktop-openai-compact"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.metadata_origin,
|
||||
"from-original"
|
||||
);
|
||||
assert!(seen_execution_runtime_request.instructions.is_empty());
|
||||
assert!(!seen_execution_runtime_request.store_present);
|
||||
for field in [
|
||||
"client_metadata",
|
||||
"include",
|
||||
"store",
|
||||
"stream",
|
||||
"stream_options",
|
||||
"tool_choice",
|
||||
] {
|
||||
assert!(
|
||||
seen_execution_runtime_request.body.get(field).is_none(),
|
||||
"Compact request must omit {field}"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["parallel_tool_calls"],
|
||||
json!(false)
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["reasoning"]["effort"],
|
||||
json!("high")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["text"]["verbosity"],
|
||||
json!("medium")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["reasoning"]["context"],
|
||||
json!("all_turns")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["input"][0]["type"],
|
||||
json!("additional_tools")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["input"][0]["tools"][0]["name"],
|
||||
json!("lookup")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["input"][1]["role"],
|
||||
json!("developer")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["input"][1]["content"][0]["text"],
|
||||
json!("Use the configured tools.")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["input"][2]["content"][0]["text"],
|
||||
json!("hello")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.body["prompt_cache_key"],
|
||||
json!("session:compact-e2e")
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.proxy_node_id,
|
||||
"proxy-node-openai-compact-local"
|
||||
@@ -558,7 +677,7 @@ async fn gateway_executes_openai_responses_compact_stream_via_local_decision_gat
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
assert!(
|
||||
!*seen_report.lock().expect("mutex should lock"),
|
||||
"report-stream should stay local when request candidate persistence is available"
|
||||
"report-sync should stay local when request candidate persistence is available"
|
||||
);
|
||||
|
||||
assert_eq!(*decision_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
@@ -38,10 +38,24 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
trace_id: String,
|
||||
url: String,
|
||||
model: String,
|
||||
content_encoding: String,
|
||||
stream: bool,
|
||||
accept: String,
|
||||
authorization: String,
|
||||
chatgpt_account_id: String,
|
||||
fedramp: String,
|
||||
x_client_request_id: String,
|
||||
session_id: String,
|
||||
thread_id: String,
|
||||
prompt_cache_key: String,
|
||||
responses_lite: String,
|
||||
has_top_level_tools: bool,
|
||||
has_top_level_instructions: bool,
|
||||
has_additional_tools: bool,
|
||||
parallel_tool_calls: bool,
|
||||
reasoning_effort: String,
|
||||
reasoning_context: String,
|
||||
has_compaction_trigger: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -74,7 +88,7 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
false,
|
||||
Some(serde_json::json!(["openai", "codex"])),
|
||||
Some(serde_json::json!(["openai:responses"])),
|
||||
Some(serde_json::json!(["gpt-5.4"])),
|
||||
Some(serde_json::json!(["gpt-5.6-sol"])),
|
||||
api_key_id.to_string(),
|
||||
Some("default".to_string()),
|
||||
true,
|
||||
@@ -85,7 +99,7 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
Some(4_102_444_800_i64),
|
||||
Some(serde_json::json!(["openai", "codex"])),
|
||||
Some(serde_json::json!(["openai:responses"])),
|
||||
Some(serde_json::json!(["gpt-5.4"])),
|
||||
Some(serde_json::json!(["gpt-5.6-sol"])),
|
||||
)
|
||||
.expect("auth snapshot should build")
|
||||
}
|
||||
@@ -113,12 +127,12 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
key_global_priority_by_format: Some(serde_json::json!({"openai:responses": 1})),
|
||||
model_id: "model-codex-cli-stream-local-1".to_string(),
|
||||
global_model_id: "global-model-codex-cli-stream-local-1".to_string(),
|
||||
global_model_name: "gpt-5.4".to_string(),
|
||||
global_model_name: "gpt-5.6-sol".to_string(),
|
||||
global_model_mappings: None,
|
||||
global_model_supports_streaming: Some(true),
|
||||
model_provider_model_name: "gpt-5.4".to_string(),
|
||||
model_provider_model_name: "gpt-5.6-sol".to_string(),
|
||||
model_provider_model_mappings: Some(vec![StoredProviderModelMapping {
|
||||
name: "gpt-5.4".to_string(),
|
||||
name: "gpt-5.6-sol".to_string(),
|
||||
priority: 1,
|
||||
api_formats: Some(vec!["openai:responses".to_string()]),
|
||||
endpoint_ids: None,
|
||||
@@ -176,7 +190,7 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
fn sample_provider_catalog_key() -> StoredProviderCatalogKey {
|
||||
let encrypted_auth_config = encrypt_python_fernet_plaintext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
r#"{"provider_type":"codex","refresh_token":"rt-codex-stream-local-123"}"#,
|
||||
r#"{"provider_type":"codex","refresh_token":"rt-codex-stream-local-123","account_id":"acc-codex-stream-local-123","is_fedramp":true}"#,
|
||||
)
|
||||
.expect("auth config should encrypt");
|
||||
StoredProviderCatalogKey::new(
|
||||
@@ -367,6 +381,11 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
content_encoding: payload
|
||||
.get("content_encoding")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
stream: payload
|
||||
.get("stream")
|
||||
.and_then(|value| value.as_bool())
|
||||
@@ -383,16 +402,106 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
chatgpt_account_id: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("chatgpt-account-id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
fedramp: payload
|
||||
.get("headers")
|
||||
.and_then(|value| value.get("x-openai-fedramp"))
|
||||
.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(),
|
||||
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(),
|
||||
responses_lite: payload
|
||||
.get("headers")
|
||||
.and_then(|value| {
|
||||
value.get("x-openai-internal-codex-responses-lite")
|
||||
})
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
has_top_level_tools: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.is_some_and(|body| body.get("tools").is_some()),
|
||||
has_top_level_instructions: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.is_some_and(|body| body.get("instructions").is_some()),
|
||||
has_additional_tools: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("input"))
|
||||
.and_then(|value| value.as_array())
|
||||
.and_then(|input| input.first())
|
||||
.and_then(|item| item.get("type"))
|
||||
.and_then(|value| value.as_str())
|
||||
== Some("additional_tools"),
|
||||
parallel_tool_calls: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("parallel_tool_calls"))
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(true),
|
||||
reasoning_effort: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("reasoning"))
|
||||
.and_then(|value| value.get("effort"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
reasoning_context: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("reasoning"))
|
||||
.and_then(|value| value.get("context"))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
has_compaction_trigger: payload
|
||||
.get("body")
|
||||
.and_then(|value| value.get("json_body"))
|
||||
.and_then(|value| value.get("input"))
|
||||
.and_then(|value| value.as_array())
|
||||
.is_some_and(|input| {
|
||||
input.iter().any(|item| {
|
||||
item.get("type").and_then(|value| value.as_str())
|
||||
== Some("compaction_trigger")
|
||||
})
|
||||
}),
|
||||
});
|
||||
let frames = concat!(
|
||||
"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.completed\\ndata: {\\\"type\\\":\\\"response.completed\\\",\\\"response\\\":{\\\"id\\\":\\\"resp_codex_cli_stream_local_123\\\",\\\"object\\\":\\\"response\\\",\\\"model\\\":\\\"gpt-5.4\\\",\\\"status\\\":\\\"completed\\\",\\\"usage\\\":{\\\"input_tokens\\\":1,\\\"output_tokens\\\":2,\\\"total_tokens\\\":3}}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.output_item.done\\ndata: {\\\"type\\\":\\\"response.output_item.done\\\",\\\"item\\\":{\\\"type\\\":\\\"compaction\\\",\\\"encrypted_content\\\":\\\"ENCRYPTED_CONTEXT_COMPACTION_SUMMARY\\\"}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.completed\\ndata: {\\\"type\\\":\\\"response.completed\\\",\\\"response\\\":{\\\"id\\\":\\\"resp_codex_cli_stream_local_123\\\",\\\"object\\\":\\\"response\\\",\\\"model\\\":\\\"gpt-5.6-sol\\\",\\\"status\\\":\\\"completed\\\",\\\"usage\\\":{\\\"input_tokens\\\":1,\\\"output_tokens\\\":2,\\\"total_tokens\\\":3}}}\\n\\n\"}}\n",
|
||||
"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"elapsed_ms\":41}}}\n",
|
||||
"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n"
|
||||
);
|
||||
@@ -469,8 +578,16 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
http::header::AUTHORIZATION,
|
||||
format!("Bearer {client_api_key}"),
|
||||
)
|
||||
.header("session-id", "session-codex-stream-local-123")
|
||||
.header("thread-id", "thread-codex-stream-local-123")
|
||||
.header(
|
||||
"x-client-request-id",
|
||||
"thread-codex-stream-local-123",
|
||||
)
|
||||
.header(TRACE_ID_HEADER, "trace-codex-cli-stream-local-123")
|
||||
.body("{\"model\":\"gpt-5.4\",\"input\":\"hello\",\"stream\":true}")
|
||||
.body(
|
||||
r#"{"model":"gpt-5.6-sol","instructions":"Use the configured tools.","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"compact"}]},{"type":"compaction_trigger"}],"tools":[{"type":"function","name":"lookup","parameters":{"type":"object"}}],"parallel_tool_calls":true,"prompt_cache_key":"thread-codex-stream-local-123","client_metadata":{"session_id":"session-codex-stream-local-123","thread_id":"thread-codex-stream-local-123"},"stream":true}"#,
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
@@ -478,9 +595,20 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let response_body =
|
||||
strip_sse_keepalive_comments(&response.text().await.expect("body should read"));
|
||||
assert!(response_body.contains("event: response.output_item.done\n"));
|
||||
assert!(response_body.contains("\"type\":\"compaction\""));
|
||||
assert!(response_body.contains("ENCRYPTED_CONTEXT_COMPACTION_SUMMARY"));
|
||||
let data_line = response_body
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("data: "))
|
||||
.filter_map(|line| line.strip_prefix("data: "))
|
||||
.find(|line| {
|
||||
serde_json::from_str::<serde_json::Value>(line)
|
||||
.ok()
|
||||
.and_then(|event| event.get("type").cloned())
|
||||
.and_then(|value| value.as_str().map(ToOwned::to_owned))
|
||||
.as_deref()
|
||||
== Some("response.completed")
|
||||
})
|
||||
.expect("completed event data should exist");
|
||||
let completed_event: serde_json::Value =
|
||||
serde_json::from_str(data_line).expect("completed event should parse");
|
||||
@@ -495,7 +623,7 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
"response": {
|
||||
"id": "resp_codex_cli_stream_local_123",
|
||||
"object": "response",
|
||||
"model": "gpt-5.4",
|
||||
"model": "gpt-5.6-sol",
|
||||
"status": "completed",
|
||||
"usage": {
|
||||
"input_tokens": 1,
|
||||
@@ -542,7 +670,8 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
seen_execution_runtime_request.url,
|
||||
"https://chatgpt.com/backend-api/codex/responses"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.model, "gpt-5.4");
|
||||
assert_eq!(seen_execution_runtime_request.model, "gpt-5.6-sol");
|
||||
assert_eq!(seen_execution_runtime_request.content_encoding, "zstd");
|
||||
assert!(seen_execution_runtime_request.stream);
|
||||
assert_eq!(seen_execution_runtime_request.accept, "text/event-stream");
|
||||
assert_eq!(
|
||||
@@ -550,9 +679,37 @@ async fn gateway_executes_codex_cli_stream_via_local_decision_gate_after_oauth_r
|
||||
"Bearer refreshed-codex-stream-access-token"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_client_request_id,
|
||||
"trace-codex-cli-stream-local-123"
|
||||
seen_execution_runtime_request.chatgpt_account_id,
|
||||
"acc-codex-stream-local-123"
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.fedramp, "true");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.x_client_request_id,
|
||||
"thread-codex-stream-local-123"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.session_id,
|
||||
"session-codex-stream-local-123"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.thread_id,
|
||||
"thread-codex-stream-local-123"
|
||||
);
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.thread_id,
|
||||
seen_execution_runtime_request.prompt_cache_key
|
||||
);
|
||||
assert_eq!(seen_execution_runtime_request.responses_lite, "true");
|
||||
assert!(!seen_execution_runtime_request.has_top_level_tools);
|
||||
assert!(!seen_execution_runtime_request.has_top_level_instructions);
|
||||
assert!(seen_execution_runtime_request.has_additional_tools);
|
||||
assert!(!seen_execution_runtime_request.parallel_tool_calls);
|
||||
assert_eq!(seen_execution_runtime_request.reasoning_effort, "low");
|
||||
assert_eq!(
|
||||
seen_execution_runtime_request.reasoning_context,
|
||||
"all_turns"
|
||||
);
|
||||
assert!(seen_execution_runtime_request.has_compaction_trigger);
|
||||
|
||||
let stored_candidates = request_candidate_repository
|
||||
.list_by_request_id("trace-codex-cli-stream-local-123")
|
||||
|
||||
@@ -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