Merge PR #669: align GPT-5.6 and Codex request protocols

This commit is contained in:
elky
2026-07-12 21:50:20 +08:00
313 changed files with 29823 additions and 5178 deletions
@@ -941,6 +941,7 @@ mod tests {
success_failover_patterns: Vec::new(),
error_stop_patterns: Vec::new(),
stop_cyber_policy_errors: false,
retry_client_errors_by_default: true,
}
);
}
@@ -1042,6 +1042,7 @@ fn grok_stream_terminal_summary(
finish_reason: Some("stop".to_string()),
response_id: None,
model: plan.model_name.clone(),
provider_actual_service_tier: None,
observed_finish: true,
unknown_event_count: 0,
parser_error: None,
@@ -359,8 +359,7 @@ impl IntoResponse for ExecutionRuntimeAppError {
return build_overloaded_response(&self.0.to_string());
}
ExecutionRuntimeServerError::Transport(
ExecutionRuntimeTransportError::StreamUnsupported
| ExecutionRuntimeTransportError::RequestBodyRequired
ExecutionRuntimeTransportError::RequestBodyRequired
| ExecutionRuntimeTransportError::BodyDecode(_)
| ExecutionRuntimeTransportError::UnsupportedContentEncoding(_)
| ExecutionRuntimeTransportError::ProxyUnsupported
@@ -397,7 +396,9 @@ mod tests {
build_execution_runtime_router_with_request_concurrency_limit,
build_execution_runtime_router_with_request_gates, DISTRIBUTED_REQUEST_GATE_NAME,
};
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, RequestBody};
use aether_contracts::{
ExecutionPlan, ExecutionTimeouts, RequestBody, StreamFrame, StreamFrameType,
};
use aether_runtime_state::{
MemoryRuntimeStateConfig, RuntimeSemaphore, RuntimeSemaphoreConfig, RuntimeState,
};
@@ -459,6 +460,43 @@ mod tests {
}
}
#[tokio::test]
async fn execution_runtime_stream_endpoint_carries_non_stream_upstream_plan() {
let upstream = Router::new().route(
"/sync-json",
any(|| async { axum::Json(serde_json::json!({"ok": true})) }),
);
let (upstream_url, upstream_handle) = start_server(upstream).await;
let runtime = build_execution_runtime_router_with_request_concurrency_limit(None);
let (runtime_url, runtime_handle) = start_server(runtime).await;
let mut plan = stream_plan(format!("{upstream_url}/sync-json"));
plan.stream = false;
let response = reqwest::Client::new()
.post(format!("{runtime_url}/v1/execute/stream"))
.json(&plan)
.send()
.await
.expect("execution request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let body = response.text().await.expect("frame body should read");
let frame_types = body
.lines()
.map(|line| {
serde_json::from_str::<StreamFrame>(line)
.expect("execution runtime frame should decode")
.frame_type
})
.collect::<Vec<_>>();
assert!(frame_types.contains(&StreamFrameType::Headers));
assert!(frame_types.contains(&StreamFrameType::Data));
assert!(frame_types.contains(&StreamFrameType::Eof));
runtime_handle.abort();
upstream_handle.abort();
}
#[tokio::test]
async fn execution_runtime_rejects_second_in_flight_stream_request_with_overload() {
let upstream_hits = Arc::new(AtomicUsize::new(0));
@@ -851,6 +851,9 @@ fn merge_stream_terminal_summary(
if current_summary.model.is_none() {
current_summary.model = observed.model;
}
if observed.provider_actual_service_tier.is_some() {
current_summary.provider_actual_service_tier = observed.provider_actual_service_tier;
}
current_summary.observed_finish |= observed.observed_finish;
current_summary.unknown_event_count = current_summary
.unknown_event_count
@@ -5484,9 +5487,8 @@ async fn execute_stream_from_frame_stream(
telemetry = Some(frame_telemetry);
}
StreamFramePayload::Eof { summary } => {
if summary.is_some() {
stream_terminal_summary = summary;
}
stream_terminal_summary =
merge_stream_terminal_summary(stream_terminal_summary.take(), summary);
break;
}
StreamFramePayload::Error { error } => {
@@ -6293,12 +6295,14 @@ mod tests {
Some(ExecutionStreamTerminalSummary {
standardized_usage: Some(runtime_usage),
model: Some("gpt-5.5".to_string()),
provider_actual_service_tier: Some("priority".to_string()),
unknown_event_count: 1,
..ExecutionStreamTerminalSummary::default()
}),
Some(ExecutionStreamTerminalSummary {
standardized_usage: Some(observed_usage),
response_id: Some("resp_123".to_string()),
provider_actual_service_tier: Some("default".to_string()),
observed_finish: true,
unknown_event_count: 2,
..ExecutionStreamTerminalSummary::default()
@@ -6313,6 +6317,10 @@ mod tests {
assert_eq!(usage.output_tokens, 137);
assert_eq!(merged.model.as_deref(), Some("gpt-5.5"));
assert_eq!(merged.response_id.as_deref(), Some("resp_123"));
assert_eq!(
merged.provider_actual_service_tier.as_deref(),
Some("default")
);
assert!(merged.observed_finish);
assert_eq!(merged.unknown_event_count, 3);
}
@@ -676,6 +676,14 @@ fn should_buffer_non_stream_response(
return false;
}
if report_context
.get("upstream_is_stream")
.and_then(Value::as_bool)
== Some(false)
{
return true;
}
headers
.get("content-length")
.and_then(|value| value.trim().parse::<u64>().ok())
@@ -1134,22 +1142,36 @@ mod tests {
}
#[test]
fn buffers_non_sse_response_only_when_content_length_is_known() {
let report_context = serde_json::json!({
fn buffers_declared_non_stream_responses_without_relying_on_content_length() {
let streaming_context = serde_json::json!({
"provider_api_format": "openai:chat",
"client_api_format": "openai:chat",
"upstream_is_stream": true,
});
let non_stream_context = serde_json::json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:responses",
"upstream_is_stream": false,
});
assert!(!should_buffer_non_stream_response(
&BTreeMap::from([("content-type".into(), "application/json".into())]),
&report_context
&streaming_context
));
assert!(should_buffer_non_stream_response(
&BTreeMap::from([("content-type".into(), "application/json".into())]),
&non_stream_context
));
assert!(should_buffer_non_stream_response(
&BTreeMap::from([
("content-type".into(), "application/json".into()),
("content-length".into(), "128".into()),
]),
&report_context
&streaming_context
));
assert!(!should_buffer_non_stream_response(
&BTreeMap::from([("content-type".into(), "text/event-stream".into())]),
&non_stream_context
));
}
@@ -1567,33 +1589,48 @@ mod tests {
.await
.expect("listener should bind");
let addr = listener.local_addr().expect("local addr should resolve");
let generated_image = "a".repeat(32 * 1024);
let expected_image = generated_image.clone();
let server = tokio::spawn(async move {
let app = Router::new().route(
"/responses",
post(|| async {
let body = serde_json::json!({
"created": 1776971267_u64,
"data": [{
"b64_json": "aGVsbG8="
}],
"usage": {
"total_tokens": 100,
"input_tokens": 50,
"output_tokens": 50,
"input_tokens_details": {
"text_tokens": 10,
"image_tokens": 40
"/images/generations",
post(move || {
let generated_image = generated_image.clone();
async move {
let body = serde_json::json!({
"created": 1776971267_u64,
"data": [{
"b64_json": generated_image
}],
"usage": {
"total_tokens": 100,
"input_tokens": 50,
"output_tokens": 50,
"input_tokens_details": {
"text_tokens": 10,
"image_tokens": 40
}
}
}
});
let mut response = axum::http::Response::new(Body::from(
serde_json::to_vec(&body).expect("json should encode"),
));
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
response
});
let encoded = serde_json::to_vec(&body).expect("json should encode");
let chunks = encoded
.chunks(4096)
.map(Bytes::copy_from_slice)
.collect::<Vec<_>>();
let chunked_body = stream! {
for chunk in chunks {
yield Ok::<Bytes, Infallible>(chunk);
tokio::task::yield_now().await;
}
};
let mut response =
axum::http::Response::new(Body::from_stream(chunked_body));
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
response
}
}),
);
axum::serve(listener, app)
@@ -1611,16 +1648,15 @@ mod tests {
endpoint_id: "endpoint-1".to_string(),
key_id: "key-1".to_string(),
method: "POST".to_string(),
url: format!("http://{addr}/responses"),
url: format!("http://{addr}/images/generations"),
headers: BTreeMap::new(),
content_type: None,
content_encoding: None,
body: RequestBody::from_json(serde_json::json!({
"model": "gpt-image-1",
"prompt": "poster",
"stream": true
"prompt": "poster"
})),
stream: true,
stream: false,
client_api_format: "openai:image".to_string(),
provider_api_format: "openai:image".to_string(),
model_name: Some("gpt-image-1".into()),
@@ -1673,7 +1709,8 @@ mod tests {
let bridged_text = String::from_utf8(bridged_body).expect("bridged body should be utf8");
assert!(bridged_text.contains("event: image_generation.completed"));
assert!(bridged_text.contains("\"type\":\"image_generation.completed\""));
assert!(bridged_text.contains("\"b64_json\":\"aGVsbG8=\""));
assert!(bridged_text.contains(&format!("\"b64_json\":\"{expected_image}\"")));
assert!(bridged_text.len() > 32 * 1024);
assert!(bridged_text.contains("\"total_tokens\":100"));
let eof_frame = frames
@@ -43,6 +43,7 @@ fn missing_exact_provider_request_payload(decision_kind: &str) -> AiExecutionDec
request_id: Some("req_123".to_string()),
candidate_id: Some("cand_123".to_string()),
provider_name: Some("provider".to_string()),
provider_type: None,
provider_id: Some("provider_id".to_string()),
endpoint_id: Some("endpoint_id".to_string()),
key_id: Some("key_id".to_string()),
@@ -57,8 +57,8 @@ const TUNNEL_RELAY_PATH_PREFIX: &str = "/api/internal/tunnel/relay";
const DEFAULT_TUNNEL_TIMEOUT_MS: u64 = 60_000;
const DEFAULT_STREAM_FIRST_BYTE_TIMEOUT_MS: u64 = 30_000;
const DEFAULT_NON_STREAM_TOTAL_TIMEOUT_MS: u64 = 300_000;
const DEFAULT_CODEX_COMPACT_TOTAL_TIMEOUT_MS: u64 = 1_200_000;
const MIN_TUNNEL_TIMEOUT_SECS: u64 = 1;
const MAX_TUNNEL_TIMEOUT_SECS: u64 = 300;
const DIRECT_REQWEST_H2_CLIENT_SHARDS_ENV: &str = "AETHER_GATEWAY_DIRECT_REQWEST_H2_CLIENT_SHARDS";
const DIRECT_REQWEST_CLIENT_SHARDS_ENV: &str = "AETHER_GATEWAY_DIRECT_REQWEST_CLIENT_SHARDS";
const DIRECT_REQWEST_H2_TARGET_STREAMS_PER_CLIENT_ENV: &str =
@@ -512,8 +512,6 @@ pub(crate) fn format_hyper_error_chain(err: &dyn std::error::Error) -> String {
#[derive(Debug, Error)]
pub(crate) enum ExecutionRuntimeTransportError {
#[error("stream execution is not supported for this plan")]
StreamUnsupported,
#[error("request body must contain json_body or body_bytes_b64")]
RequestBodyRequired,
#[error("request body base64 is invalid: {0}")]
@@ -681,10 +679,6 @@ impl DirectSyncExecutionRuntime {
&self,
plan: &ExecutionPlan,
) -> Result<DirectUpstreamStreamExecution, ExecutionRuntimeTransportError> {
if !plan.stream {
return Err(ExecutionRuntimeTransportError::StreamUnsupported);
}
let build_body_started_at = Instant::now();
let body_bytes = build_request_body(plan)?;
observe_gateway_stage_ms(
@@ -835,6 +829,7 @@ fn build_stream_summary_report_context(plan: &ExecutionPlan) -> Value {
"provider_api_format": plan.provider_api_format,
"client_api_format": plan.client_api_format,
"model": plan.model_name,
"upstream_is_stream": plan.stream,
})
}
@@ -2125,20 +2120,17 @@ pub(crate) fn build_request_body(
Vec::new()
};
if should_gzip_request_body(plan) && plan.body.json_body.is_some() {
body_bytes = gzip_bytes(&body_bytes)?;
if plan.body.json_body.is_some() {
body_bytes = match normalize_content_encoding(plan.content_encoding.as_deref()).as_deref() {
Some("gzip") => gzip_bytes(&body_bytes)?,
Some("zstd") => zstd_bytes(&body_bytes)?,
_ => body_bytes,
};
}
Ok(body_bytes)
}
fn should_gzip_request_body(plan: &ExecutionPlan) -> bool {
matches!(
normalize_content_encoding(plan.content_encoding.as_deref()).as_deref(),
Some("gzip")
)
}
fn normalize_content_encoding(value: Option<&str>) -> Option<String> {
value
.map(str::trim)
@@ -2156,6 +2148,11 @@ fn gzip_bytes(body_bytes: &[u8]) -> Result<Vec<u8>, ExecutionRuntimeTransportErr
.map_err(|err| ExecutionRuntimeTransportError::RelayError(err.to_string()))
}
fn zstd_bytes(body_bytes: &[u8]) -> Result<Vec<u8>, ExecutionRuntimeTransportError> {
zstd::stream::encode_all(std::io::Cursor::new(body_bytes), 3)
.map_err(|err| ExecutionRuntimeTransportError::RelayError(err.to_string()))
}
fn build_relay_client(
timeouts: Option<&aether_contracts::ExecutionTimeouts>,
) -> Result<reqwest::Client, ExecutionRuntimeTransportError> {
@@ -2218,28 +2215,49 @@ fn resolve_tunnel_first_byte_timeout(plan: &ExecutionPlan) -> Option<Duration> {
})
}
fn resolve_non_stream_total_timeout(plan: &ExecutionPlan) -> Option<Duration> {
if plan.stream {
pub(crate) fn resolve_non_stream_total_timeout_for_request(
is_stream: bool,
provider_api_format: &str,
timeouts: Option<&aether_contracts::ExecutionTimeouts>,
) -> Option<Duration> {
if is_stream {
return None;
}
let timeout_ms = plan
.timeouts
.as_ref()
let default_timeout_ms =
if crate::ai_serving::is_openai_responses_compact_format(provider_api_format) {
DEFAULT_CODEX_COMPACT_TOTAL_TIMEOUT_MS
} else {
DEFAULT_NON_STREAM_TOTAL_TIMEOUT_MS
};
let timeout_ms = timeouts
.and_then(|timeouts| timeouts.total_ms)
.unwrap_or(DEFAULT_NON_STREAM_TOTAL_TIMEOUT_MS);
.unwrap_or(default_timeout_ms);
Some(Duration::from_millis(timeout_ms.max(1)))
}
fn resolve_non_stream_total_timeout(plan: &ExecutionPlan) -> Option<Duration> {
resolve_non_stream_total_timeout_for_request(
plan.stream,
&plan.provider_api_format,
plan.timeouts.as_ref(),
)
}
pub(crate) fn resolve_stream_first_byte_timeout_for_request(
is_stream: bool,
timeouts: Option<&aether_contracts::ExecutionTimeouts>,
) -> Option<Duration> {
if !is_stream {
return None;
}
let timeout_ms = timeouts
.and_then(|timeouts| timeouts.first_byte_ms)
.unwrap_or(DEFAULT_STREAM_FIRST_BYTE_TIMEOUT_MS);
Some(Duration::from_millis(timeout_ms.max(1)))
}
pub(crate) fn resolve_stream_first_byte_timeout(plan: &ExecutionPlan) -> Option<Duration> {
if !plan.stream {
return None;
}
let timeout_ms = plan
.timeouts
.as_ref()
.and_then(|timeouts| timeouts.first_byte_ms)
.unwrap_or(DEFAULT_STREAM_FIRST_BYTE_TIMEOUT_MS);
Some(Duration::from_millis(timeout_ms.max(1)))
resolve_stream_first_byte_timeout_for_request(plan.stream, plan.timeouts.as_ref())
}
pub(crate) async fn with_non_stream_total_timeout<T, F>(
@@ -2359,7 +2377,10 @@ fn resolve_tunnel_timeout_metadata(plan: &ExecutionPlan) -> TunnelTimeoutMetadat
fn timeout_ms_to_secs(ms: u64) -> u64 {
let secs = ms.div_ceil(1_000);
secs.clamp(MIN_TUNNEL_TIMEOUT_SECS, MAX_TUNNEL_TIMEOUT_SECS)
secs.clamp(
MIN_TUNNEL_TIMEOUT_SECS,
aether_contracts::MAX_EXECUTION_REQUEST_TIMEOUT_SECS,
)
}
fn resolve_tunnel_node_id(proxy: Option<&ProxySnapshot>) -> Option<String> {
@@ -3552,7 +3573,7 @@ pub(crate) fn build_request_headers(
let mut out = HeaderMap::new();
let normalized_content_encoding = normalize_content_encoding(content_encoding);
if let Some(encoding) = normalized_content_encoding.as_deref() {
if encoding != "gzip" && !allow_passthrough_content_encoding {
if !matches!(encoding, "gzip" | "zstd") && !allow_passthrough_content_encoding {
return Err(ExecutionRuntimeTransportError::UnsupportedContentEncoding(
encoding.to_string(),
));
@@ -4637,6 +4658,25 @@ mod tests {
assert_eq!(timeout, std::time::Duration::from_secs(300));
}
#[test]
fn codex_compact_uses_the_full_unary_timeout_by_default() {
let mut plan = tunnel_timeout_plan(false);
plan.provider_api_format = "openai:responses:compact".to_string();
plan.timeouts = None;
let timeout = resolve_non_stream_total_timeout(&plan)
.expect("Codex Compact should have a total timeout");
let meta = build_direct_tunnel_request_meta(
&plan,
&reqwest::header::HeaderMap::new(),
ExecutionTransportControls::default(),
);
assert_eq!(timeout, std::time::Duration::from_secs(1_200));
assert_eq!(meta.request_timeout_ms, Some(1_200_000));
assert_eq!(meta.timeout, 1_200);
}
#[test]
fn tunnel_request_meta_uses_non_stream_default_instead_of_first_byte_default() {
let mut plan = tunnel_timeout_plan(false);
@@ -6323,13 +6363,21 @@ mod tests {
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_string();
let mut decoder = flate2::read::GzDecoder::new(body.as_ref());
let mut decoded = String::new();
decoder
.read_to_string(&mut decoded)
.expect("gzip body should decode");
let decoded = match header_encoding.as_str() {
"gzip" => {
let mut decoder = flate2::read::GzDecoder::new(body.as_ref());
let mut decoded = Vec::new();
decoder
.read_to_end(&mut decoded)
.expect("gzip body should decode");
decoded
}
"zstd" => zstd::stream::decode_all(std::io::Cursor::new(body.as_ref()))
.expect("zstd body should decode"),
encoding => panic!("unexpected content encoding: {encoding}"),
};
let decoded_json: serde_json::Value =
serde_json::from_str(&decoded).expect("decoded json should parse");
serde_json::from_slice(&decoded).expect("decoded json should parse");
(
axum::http::StatusCode::OK,
Json(json!({
@@ -6346,45 +6394,47 @@ mod tests {
});
let execution_runtime = DirectSyncExecutionRuntime::new();
let result = execution_runtime
.execute_sync(&ExecutionPlan {
request_id: "req-gzip-1".into(),
candidate_id: Some("cand-1".into()),
provider_name: Some("openai".into()),
provider_id: "prov-1".into(),
endpoint_id: "ep-1".into(),
key_id: "key-1".into(),
method: "POST".into(),
url: format!("http://{addr}/chat"),
headers: BTreeMap::from([("content-type".into(), "application/json".into())]),
content_type: Some("application/json".into()),
content_encoding: Some("gzip".into()),
body: RequestBody::from_json(json!({"model": "gpt-4.1"})),
stream: false,
client_api_format: "openai:chat".into(),
provider_api_format: "openai:chat".into(),
model_name: Some("gpt-4.1".into()),
proxy: None,
transport_profile: None,
timeouts: Some(ExecutionTimeouts {
connect_ms: Some(5_000),
total_ms: Some(LOCAL_HTTP_SUCCESS_TIMEOUT_MS),
..ExecutionTimeouts::default()
}),
})
.await
.expect("gzip sync execution should succeed");
for encoding in ["gzip", "zstd"] {
let result = execution_runtime
.execute_sync(&ExecutionPlan {
request_id: format!("req-{encoding}-1"),
candidate_id: Some("cand-1".into()),
provider_name: Some("openai".into()),
provider_id: "prov-1".into(),
endpoint_id: "ep-1".into(),
key_id: "key-1".into(),
method: "POST".into(),
url: format!("http://{addr}/chat"),
headers: BTreeMap::from([("content-type".into(), "application/json".into())]),
content_type: Some("application/json".into()),
content_encoding: Some(encoding.into()),
body: RequestBody::from_json(json!({"model": "gpt-4.1"})),
stream: false,
client_api_format: "openai:chat".into(),
provider_api_format: "openai:chat".into(),
model_name: Some("gpt-4.1".into()),
proxy: None,
transport_profile: None,
timeouts: Some(ExecutionTimeouts {
connect_ms: Some(5_000),
total_ms: Some(LOCAL_HTTP_SUCCESS_TIMEOUT_MS),
..ExecutionTimeouts::default()
}),
})
.await
.expect("compressed sync execution should succeed");
assert_eq!(result.status_code, 200);
assert_eq!(
result.body.and_then(|body| body.json_body),
Some(json!({
"content_encoding": encoding,
"body": {"model": "gpt-4.1"},
}))
);
}
server.abort();
assert_eq!(result.status_code, 200);
assert_eq!(
result.body.and_then(|body| body.json_body),
Some(json!({
"content_encoding": "gzip",
"body": {"model": "gpt-4.1"},
}))
);
}
#[tokio::test]