Merge origin/main into main

Integrate upstream updates while preserving the local analytics dashboards and schema-only migration changes.

Combine user account analysis with upstream user/group usage statistics in separate tabs, retain all migration versions, and keep the deleted audit document removed.

Validation: gateway all-target cargo check, frontend type check and 57 focused tests, 48 migration tests, schema composition checks, and diff whitespace checks.
This commit is contained in:
elky
2026-10-02 11:57:18 +08:00
343 changed files with 27929 additions and 2549 deletions
@@ -3199,13 +3199,15 @@ fn openai_responses_body(
let response_id = format!("resp_{}", Uuid::new_v4());
let mut output = Vec::new();
if !collected.thinking.trim().is_empty() {
let thinking = collected.thinking.trim();
output.push(json!({
"id": openai_responses_synthetic_reasoning_item_id(&response_id, 0),
"type": "reasoning",
"status": "completed",
"summary": [{
"type": "summary_text",
"text": collected.thinking.trim(),
"summary": [],
"content": [{
"type": "reasoning_text",
"text": thinking,
}],
}));
}
@@ -4723,6 +4725,15 @@ mod tests {
serde_json::json!(usage.reasoning_tokens)
);
assert_eq!(body["output"][0]["type"], serde_json::json!("reasoning"));
assert_eq!(
body["output"][0]["content"][0]["type"],
serde_json::json!("reasoning_text")
);
assert_eq!(
body["output"][0]["content"][0]["text"],
serde_json::json!("short reasoning")
);
assert_eq!(body["output"][0]["summary"], serde_json::json!([]));
assert_eq!(body["output"][1]["type"], serde_json::json!("message"));
assert!(body["output"][1]["id"]
.as_str()
@@ -4906,7 +4917,12 @@ mod tests {
assert!(body.contains("event: response.created"));
assert!(body.contains("event: response.in_progress"));
assert!(body.contains("event: response.reasoning_summary_part.added"));
// Thinking must stay off the summary channel or clients that render
// both (Codex) print the raw chain-of-thought twice.
assert!(!body.contains("event: response.reasoning_summary_part.added"));
assert!(!body.contains("event: response.reasoning_summary_text.delta"));
assert!(!body.contains("event: response.reasoning_summary_text.done"));
assert!(body.contains("\"type\":\"reasoning_text\""));
assert!(body.contains("event: response.content_part.added"));
assert!(body.contains("event: response.output_text.done"));
assert!(body.contains("event: response.completed"));
@@ -6513,6 +6513,21 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
let normalized_stream_report_context =
normalize_provider_private_report_context(report_context.as_ref());
// Observers follow the live protocol stream across prefetch and transfer.
// Diagnostic capture limits must never determine parser state.
let stream_usage_report_context = normalized_stream_report_context.clone().or_else(|| {
Some(json!({
"provider_api_format": plan.provider_api_format.as_str(),
"client_api_format": plan.client_api_format.as_str(),
}))
});
let mut stream_usage_observer = stream_usage_report_context
.as_ref()
.map(|_| StreamingStandardTerminalObserver::default());
let mut stream_usage_observer_buffered =
StreamUsageObservationBuffer::new(max_stream_body_buffer_bytes);
let mut provider_error_inspection = ProviderStreamErrorInspection::default();
let mut prefetched_provider_error = None;
let upstream_headers = headers.clone();
let mut private_stream_normalizer =
maybe_build_provider_private_stream_normalizer(report_context.as_ref());
@@ -6613,7 +6628,8 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
stream_commit_gate.commit();
}
let mut prefetched_chunks: Vec<Bytes> = Vec::new();
let mut provider_prefetched_body = Vec::new();
let mut provider_prefetched_body = StreamBodyCapture::default();
let mut provider_prefetched_bytes = 0_u64;
let mut provider_prefetched_body_truncated = false;
let mut prefetched_body = Vec::new();
let mut prefetched_inspection_body = Vec::new();
@@ -6866,10 +6882,12 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
}
}
append_stream_capture_bytes(
provider_prefetched_bytes =
provider_prefetched_bytes.saturating_add(chunk.len() as u64);
append_budgeted_stream_capture_bytes(
&mut provider_prefetched_body,
&chunk,
MAX_STREAM_PREFETCH_BYTES,
max_stream_body_buffer_bytes,
&mut provider_prefetched_body_truncated,
);
append_stream_capture_bytes(
@@ -7114,6 +7132,22 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
} else {
chunk
};
if let Some(error) = provider_error_inspection
.observe(stream_usage_report_context.as_ref(), &normalized_chunk)
{
prefetched_provider_error.get_or_insert(error);
}
if let (Some(observer), Some(context)) = (
stream_usage_observer.as_mut(),
stream_usage_report_context.as_ref(),
) {
observe_stream_usage_bytes(
observer,
context,
&mut stream_usage_observer_buffered,
&normalized_chunk,
);
}
let rewritten_chunk = if let Some(rewriter) = local_stream_rewriter.as_mut() {
match rewriter.push_chunk(&normalized_chunk) {
Ok(rewritten_chunk) => rewritten_chunk,
@@ -7244,17 +7278,21 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
if stream_commit_gate.is_uncommitted() {
stream_commit_gate.commit();
}
let prefetched_response_history_persisted = if let Some(record) = local_stream_rewriter
if let Some(record) = local_stream_rewriter
.as_mut()
.and_then(|rewriter| rewriter.take_response_history_record())
{
crate::ai_serving::persist_response_history_record(state, record).await;
true
} else {
false
};
drop(private_stream_normalizer);
drop(local_stream_rewriter);
}
// Keep partial records and conversion state; replaying the bounded
// inspection/capture prefix loses any bytes consumed beyond that prefix.
let mut private_stream_normalizer = private_stream_normalizer.map(|parser| parser.into_owned());
let mut local_stream_rewriter = local_stream_rewriter.map(|parser| parser.into_owned());
if sync_json_stream_bridge_active {
private_stream_normalizer = None;
local_stream_rewriter = None;
stream_usage_observer = None;
}
let initial_usage_telemetry = prefetched_usage_telemetry.clone().or_else(|| {
prefetched_telemetry
@@ -7297,7 +7335,6 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
let headers_for_report = headers.clone();
let report_kind_owned = report_kind;
let report_context_owned = report_context;
let normalized_stream_report_context_owned = normalized_stream_report_context;
let lifecycle_seed_for_report = lifecycle_seed;
let provider_prefetched_body_for_report = provider_prefetched_body;
let prefetched_body_for_report = prefetched_body;
@@ -7339,40 +7376,10 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
let _stream_total_guard =
StageElapsedGuard::from_started_at("stream_total", stream_started_at_for_report);
let _provider_pool_in_flight_guard = provider_pool_in_flight_guard_for_report;
let mut provider_buffered_body = StreamBodyCapture::default();
let mut provider_buffered_body = provider_prefetched_body_for_report;
let mut buffered_body = StreamBodyCapture::default();
let mut provider_body_truncated = false;
let mut provider_body_truncated = provider_prefetched_body_truncated;
let mut client_body_truncated = false;
let mut private_stream_normalizer = if sync_json_stream_bridge_active_for_report {
None
} else {
maybe_build_provider_private_stream_normalizer(report_context_owned.as_ref())
};
let mut local_stream_rewriter = if sync_json_stream_bridge_active_for_report {
None
} else {
maybe_build_stream_response_rewriter(normalized_stream_report_context_owned.as_ref())
};
let stream_usage_report_context =
normalized_stream_report_context_owned.clone().or_else(|| {
Some(serde_json::json!({
"provider_api_format": plan_for_report.provider_api_format.as_str(),
"client_api_format": plan_for_report.client_api_format.as_str(),
}))
});
let mut stream_usage_observer = stream_usage_report_context
.as_ref()
.filter(|_| !sync_json_stream_bridge_active_for_report)
.map(|_| StreamingStandardTerminalObserver::default());
let mut stream_usage_observer_buffered =
StreamUsageObservationBuffer::new(max_stream_body_buffer_bytes);
let mut provider_error_inspection = ProviderStreamErrorInspection::default();
append_budgeted_stream_capture_bytes(
&mut provider_buffered_body,
&provider_prefetched_body_for_report,
max_stream_body_buffer_bytes,
&mut provider_body_truncated,
);
append_budgeted_stream_capture_bytes(
&mut buffered_body,
&prefetched_body_for_report,
@@ -7416,9 +7423,7 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
} else {
initial_elapsed_ms
}));
let provider_stream_bytes = Arc::new(AtomicU64::new(
u64::try_from(provider_prefetched_body_for_report.len()).unwrap_or(u64::MAX),
));
let provider_stream_bytes = Arc::new(AtomicU64::new(provider_prefetched_bytes));
let client_stream_bytes = Arc::new(AtomicU64::new(
u64::try_from(prefetched_body_for_report.len()).unwrap_or(u64::MAX),
));
@@ -7514,96 +7519,20 @@ async fn execute_stream_from_frame_stream_with_retry_scope(
}
})
};
if !provider_prefetched_body_for_report.is_empty() {
let normalized_prefetched_chunk = if let Some(normalizer) =
private_stream_normalizer.as_mut()
{
match normalizer.push_chunk(&provider_prefetched_body_for_report) {
Ok(normalized_chunk) => Some(normalized_chunk),
Err(err) => {
warn!(
event_name = "stream_execution_prefetch_normalize_restore_failed",
log_type = "ops",
trace_id = %trace_id_owned,
request_id = %request_id_for_report_log,
candidate_id = ?candidate_id_for_report.as_deref(),
error_category = "stream_normalization_restore_failed",
"gateway failed to restore private stream normalization state after prefetch"
);
terminal_failure = Some(build_stream_failure_report(
"execution_runtime_stream_rewrite_error",
format!(
"failed to restore private stream normalization state after prefetch: {err:?}"
),
502,
));
None
}
}
} else {
None
};
let replay_chunk = normalized_prefetched_chunk
.as_deref()
.unwrap_or(provider_prefetched_body_for_report.as_slice());
if let Some(error_body_json) = provider_error_inspection
.observe(stream_usage_report_context.as_ref(), replay_chunk)
{
provider_error_forwarded_to_client = !prefetched_body_for_report.is_empty();
let error_status_code = resolve_provider_stream_error_status_code(
plan_for_report.provider_api_format.as_str(),
status_code,
&error_body_json,
);
terminal_failure = Some(build_stream_failure_from_provider_error_body(
error_status_code,
&error_body_json,
));
}
if let (Some(observer), Some(report_context)) = (
stream_usage_observer.as_mut(),
stream_usage_report_context.as_ref(),
) {
observe_stream_usage_bytes(
observer,
report_context,
&mut stream_usage_observer_buffered,
replay_chunk,
);
}
if terminal_failure.is_none() {
if let Some(rewriter) = local_stream_rewriter.as_mut() {
if let Err(err) = rewriter.push_chunk(replay_chunk) {
warn!(
event_name = "stream_execution_prefetch_rewrite_restore_failed",
log_type = "ops",
trace_id = %trace_id_owned,
request_id = %request_id_for_report_log,
candidate_id = ?candidate_id_for_report.as_deref(),
error_category = "stream_rewrite_restore_failed",
"gateway failed to restore local stream rewrite state after prefetch"
);
terminal_failure = Some(build_stream_failure_report(
"execution_runtime_stream_rewrite_error",
format!(
"failed to restore local stream rewrite state after prefetch: {err:?}"
),
502,
));
}
}
}
if prefetched_response_history_persisted {
if let Some(rewriter) = local_stream_rewriter.as_mut() {
let _ = rewriter.take_response_history_record();
}
}
if let Some(error_body_json) = prefetched_provider_error {
provider_error_forwarded_to_client = !prefetched_body_for_report.is_empty();
let error_status_code = resolve_provider_stream_error_status_code(
plan_for_report.provider_api_format.as_str(),
status_code,
&error_body_json,
);
terminal_failure = Some(build_stream_failure_from_provider_error_body(
error_status_code,
&error_body_json,
));
}
// These buffers restore parser/rewriter state above. Audit capture owns
// its budgeted copies; retaining semantic prefetch duplicates for the
// rest of the stream would bypass the capture memory limit.
drop(provider_prefetched_body_for_report);
// Parser state is already current and capture owns its budgeted bytes.
// This output prefix is needed only to initialize client-side trackers.
drop(prefetched_body_for_report);
if terminal_failure.is_none() && !reached_eof {
@@ -9363,6 +9292,188 @@ mod tests {
.unwrap()
}
#[tokio::test]
async fn prefetch_handoff_preserves_large_responses_setup_event() {
let event = format!(
"event: response.created\ndata: {}\n\n",
json!({"type":"response.created", "response": {
"id":"resp-large-setup", "status":"in_progress", "output":[],
"tools":[{"name":"write", "description":"x".repeat(64 * 1024)}]
}})
);
let done = "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp-large-setup\",\"status\":\"completed\",\"output\":[],\"usage\":{\"input_tokens\":7,\"output_tokens\":2}}}\n\n";
// Include the two observed transport boundaries, exact/near budget
// boundaries, and multiple prefetch chunks crossing the budget.
for cuts in [
vec![16_383],
vec![16_384],
vec![17_735],
vec![17_741],
vec![8_192, 17_735],
] {
let mut chunks = Vec::new();
let mut start = 0;
for end in cuts {
chunks.push(&event[start..end]);
start = end;
}
chunks.push(&event[start..]);
chunks.push(done);
let response = execute_generic_sse_precommit(chunks, json!({}), None, false)
.await
.expect("large setup should commit at the bounded prefetch limit");
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let body = String::from_utf8(body.to_vec()).unwrap();
assert!(
body.starts_with(&event),
"setup bytes lost or duplicated at split {start}"
);
let events: Vec<Value> = body
.lines()
.filter_map(|line| line.strip_prefix("data: "))
.filter(|payload| *payload != "[DONE]")
.map(|payload| {
serde_json::from_str(payload).expect("every SSE payload must be valid JSON")
})
.collect();
assert_eq!(events.len(), 2, "events must be forwarded exactly once");
assert_eq!(events[1]["type"], "response.completed");
}
}
#[tokio::test]
async fn prefetch_handoff_keeps_audit_usage_and_private_conversion() {
for private in [false, true] {
let request_id = format!("handoff-audit-{}", uuid::Uuid::new_v4());
let mut plan = if private {
antigravity_gemini_stream_plan(&request_id)
} else {
native_anthropic_stream_plan(&request_id)
};
if !private {
plan.provider_api_format = "openai:responses".into();
plan.client_api_format = "openai:responses".into();
}
let context = json!({
"request_id": request_id, "candidate_id": plan.candidate_id,
"candidate_index":0, "retry_index":0,
"provider_api_format": plan.provider_api_format,
"client_api_format": plan.client_api_format,
"needs_conversion": private, "has_envelope": private,
"envelope_name": if private { "antigravity:v1internal" } else { "" },
});
let repository = Arc::new(InMemoryUsageReadRepository::default());
let catalog = provider_catalog_for_plan(&plan, None);
let state = AppState::new()
.unwrap()
.with_data_state_for_tests(
crate::data::GatewayDataState::with_usage_repository_for_tests(Arc::clone(
&repository,
))
.with_provider_catalog_reader(Arc::new(catalog))
.with_encryption_key_for_tests(DEVELOPMENT_ENCRYPTION_KEY)
.with_system_config_values_for_tests([(
"request_record_level".into(),
json!("full"),
)]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
..Default::default()
});
let text = "hello".repeat(12_000);
let payload = if private {
json!({"response":{"candidates":[{"content":{"role":"model","parts":[{"text":text}]},
"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":1234,"candidatesTokenCount":567},
"modelVersion":"gemini-3.7-flash-tiered"}})
} else {
json!({"type":"response.completed","response":{"id":"resp-handoff-usage","status":"completed",
"output":[{"type":"message","id":"msg-handoff","role":"assistant","status":"completed",
"content":[{"type":"output_text","text":text,"annotations":[]}]}],
"usage":{"input_tokens":1234,"output_tokens":567,"total_tokens":1801}}})
};
let input = format!("data: {payload}\n\n");
// One complete large chunk exercises an already-emitted prefetch
// result; the private path exercises incomplete normalization too.
let chunks = if private {
vec![input[..17_735].to_string(), input[17_735..].to_string()]
} else {
vec![input.clone()]
};
let frames = stream! {
yield Ok::<Bytes, std::io::Error>(ndjson_frame(StreamFrame {
frame_type:StreamFrameType::Headers,
payload:StreamFramePayload::Headers { status_code:200,
headers:BTreeMap::from([("content-type".into(),"text/event-stream".into())]),
response_observation:None },
}));
for chunk in chunks {
yield Ok(ndjson_frame(StreamFrame { frame_type:StreamFrameType::Data,
payload:StreamFramePayload::Data { text:Some(chunk),chunk_b64:None } }));
}
yield Ok(ndjson_frame(StreamFrame::eof()));
}.boxed();
let response = execute_stream_from_frame_stream(
&state,
plan,
"trace-handoff-audit",
&test_decision(),
OPENAI_RESPONSES_STREAM_PLAN_KIND,
Some("openai_responses_stream_success".into()),
Some(context),
crate::clock::current_unix_ms(),
Instant::now(),
RequestStageTrace::from_env(),
false,
frames,
None,
)
.await
.unwrap()
.unwrap();
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let body = String::from_utf8(body.to_vec()).unwrap();
let events: Vec<Value> = body
.lines()
.filter_map(|l| l.strip_prefix("data: "))
.filter(|p| *p != "[DONE]")
.map(|p| serde_json::from_str(p).unwrap())
.collect();
assert_eq!(
events
.iter()
.filter(|e| e["type"] == "response.completed")
.count(),
1
);
assert!(body.contains(&text));
let usage = tokio::time::timeout(Duration::from_secs(3), async {
loop {
if let Some(u) = repository
.find_by_request_id(&request_id)
.await
.unwrap()
.filter(|u| u.status == "completed" || u.status == "failed")
{
break u;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("usage should finalize");
assert_eq!(usage.status, "completed", "{:?}", usage.error_message);
assert_eq!(usage.input_tokens, 1234);
assert_eq!(usage.output_tokens, 567);
let captured = usage.response_body.as_ref().expect("provider capture");
assert!(
captured["metadata"].get("dropped_chunks").is_none(),
"{captured}"
);
assert_eq!(captured["chunks"].as_array().unwrap(), &vec![payload]);
}
}
#[tokio::test]
async fn generic_stream_success_regex_matches_fragmented_plain_body() {
for chunks in [
@@ -9901,7 +10012,7 @@ mod tests {
let mut buffer = super::StreamUsageObservationBuffer::new(32 * 1024);
let mut rewriter = super::maybe_build_stream_response_rewriter(Some(&context)).unwrap();
let mut delivered = Vec::new();
for chunk in chunks {
for (index, chunk) in chunks.into_iter().enumerate() {
provider.append(chunk, 32 * 1024, &mut provider_truncated);
super::observe_stream_usage_bytes(
observer.as_mut().unwrap(),
@@ -9912,6 +10023,10 @@ mod tests {
let output = rewriter.push_chunk(chunk).unwrap();
client.append(&output, 32 * 1024, &mut client_truncated);
delivered.extend(output);
if index == 0 {
// Task handoff must also work when audit admits no bytes.
rewriter = rewriter.into_owned();
}
}
let tail = rewriter.finish().unwrap();
client.append(&tail, 32 * 1024, &mut client_truncated);
@@ -11931,7 +12046,11 @@ mod tests {
.expect("response body should read");
let body = String::from_utf8(body.to_vec()).expect("response body should be utf8");
assert!(
body.contains("event: response.reasoning_summary_text.delta\n"),
body.contains("event: response.reasoning_text.delta\n"),
"{body}"
);
assert!(
!body.contains("event: response.reasoning_summary_text.delta\n"),
"{body}"
);
assert!(
@@ -196,6 +196,78 @@ fn maybe_build_invalid_provider_success_finalize_response(
)?))
}
fn local_sync_needs_conversion(payload: &GatewaySyncReportRequest) -> bool {
payload
.report_context
.as_ref()
.and_then(|value| value.get("needs_conversion"))
.and_then(|value| value.as_bool())
.unwrap_or(false)
}
/// A successful upstream response that needed conversion but could not be
/// converted must not reach the client in the provider's own format.
fn maybe_build_unconverted_cross_format_success_response(
trace_id: &str,
decision: &GatewayControlDecision,
payload: &GatewaySyncReportRequest,
) -> Result<Option<Response<Body>>, GatewayError> {
if payload.status_code >= 400
|| !local_sync_needs_conversion(payload)
|| !is_core_error_finalize_kind(payload.report_kind.as_str())
{
return Ok(None);
}
let client_api_format = resolve_local_sync_client_api_format(payload);
let provider_api_format = resolve_local_sync_provider_api_format(payload);
warn!(
event_name = "local_core_finalize_cross_format_success_unconverted",
log_type = "event",
trace_id = %trace_id,
report_kind = %payload.report_kind,
status_code = payload.status_code,
client_api_format = %client_api_format,
provider_api_format = %provider_api_format,
"gateway could not convert a successful provider response to the client format"
);
let message = format!(
"Provider returned HTTP {} but its {provider_api_format} response could not be converted to {client_api_format}.",
payload.status_code
);
let body_json = build_core_error_body_for_client_format(
&client_api_format,
&message,
Some("response_conversion_failed"),
LocalCoreSyncErrorKind::ServerError,
)
.unwrap_or_else(|| {
serde_json::json!({
"error": {
"message": message,
"type": "server_error",
"code": "response_conversion_failed"
}
})
});
let mut response_headers = payload.headers.clone();
response_headers.remove("content-encoding");
response_headers.remove("content-length");
response_headers.insert("content-type".to_string(), "application/json".to_string());
let body_bytes =
serde_json::to_vec(&body_json).map_err(|err| GatewayError::Internal(err.to_string()))?;
response_headers.insert("content-length".to_string(), body_bytes.len().to_string());
Ok(Some(build_client_response_from_parts(
StatusCode::BAD_GATEWAY.as_u16(),
&response_headers,
Body::from(body_bytes),
trace_id,
Some(decision),
)?))
}
fn local_core_sync_finalize_has_invalid_provider_success(
payload: &GatewaySyncReportRequest,
) -> Result<bool, GatewayError> {
@@ -274,6 +346,12 @@ pub(crate) fn resolve_local_core_error_response_body_json(
return Ok(Some(body_json));
}
// A 2xx cross-format body that is not JSON (e.g. an aggregated SSE capture)
// carries no upstream error; wrapping it as one would ship raw provider
// bytes to the client under the success status.
if payload.status_code < 400 && local_sync_needs_conversion(payload) {
return Ok(None);
}
let Some(body_text) = decode_local_sync_body_text(payload)? else {
return Ok(None);
};
@@ -626,6 +704,10 @@ pub(crate) async fn submit_local_core_error_or_sync_finalize(
maybe_build_local_core_error_response(trace_id, decision, &payload)?
{
response
} else if let Some(response) =
maybe_build_unconverted_cross_format_success_response(trace_id, decision, &payload)?
{
response
} else {
warn!(
event_name = "local_core_finalize_fallback_raw_response_body",
@@ -937,6 +1019,128 @@ mod tests {
);
}
#[tokio::test]
async fn local_core_sync_finalize_converts_forced_responses_stream_for_gemini_client() {
use base64::Engine as _;
// Forced-stream xAI shape: the terminal response echoes request
// metadata and encrypted reasoning next to the real answer.
let raw_sse = concat!(
"event: response.created\n",
"data: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_xai_123\",\"object\":\"response\",\"status\":\"in_progress\",\"model\":\"grok-4.7-build\",\"output\":[],\"parallel_tool_calls\":true,\"tools\":[]}}\n\n",
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"sequence_number\":1,\"output_index\":0,\"item\":{\"id\":\"rs_xai_123\",\"type\":\"reasoning\",\"status\":\"completed\",\"summary\":[],\"encrypted_content\":\"opaque-xai-reasoning\"}}\n\n",
"event: response.output_text.delta\n",
"data: {\"type\":\"response.output_text.delta\",\"sequence_number\":2,\"item_id\":\"msg_xai_123\",\"output_index\":1,\"content_index\":0,\"delta\":\"Hi there, friend\"}\n\n",
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"sequence_number\":3,\"output_index\":1,\"item\":{\"id\":\"msg_xai_123\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hi there, friend\",\"annotations\":[]}]}}\n\n",
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"sequence_number\":4,\"response\":{\"id\":\"resp_xai_123\",\"object\":\"response\",\"status\":\"completed\",\"model\":\"grok-4.7-build\",\"output\":[],\"parallel_tool_calls\":true,\"tool_choice\":\"auto\",\"tools\":[],\"text\":{\"format\":{\"type\":\"text\"}},\"temperature\":0.7,\"store\":false,\"usage\":{\"input_tokens\":1249,\"output_tokens\":12,\"total_tokens\":1261}}}\n\n",
);
let mut payload = core_finalize_payload(
"gemini_chat_sync_finalize",
"gemini:generate_content",
"openai:responses",
200,
json!(null),
);
payload.body_json = None;
payload.body_base64 = Some(base64::engine::general_purpose::STANDARD.encode(raw_sse));
payload.report_context = Some(json!({
"client_api_format": "gemini:generate_content",
"provider_api_format": "openai:responses",
"provider_stream_event_api_format": "openai:responses",
"model": "grok-4.7",
"mapped_model": "grok-4.7",
"needs_conversion": true,
}));
let state = AppState::new().expect("state should build");
let response = submit_local_core_error_or_sync_finalize(
&state,
"trace-forced-responses-gemini",
&test_decision(),
payload,
)
.await
.expect("finalize should build a response");
assert_eq!(response.status(), http::StatusCode::OK);
let body_bytes = to_bytes(response.into_body(), usize::MAX)
.await
.expect("body should read");
let body =
serde_json::from_slice::<serde_json::Value>(&body_bytes).expect("body should decode");
assert!(body.get("error").is_none(), "unexpected error body: {body}");
let parts = body["candidates"][0]["content"]["parts"]
.as_array()
.expect("gemini parts");
assert!(parts.iter().any(|part| part["text"] == "Hi there, friend"));
let text = String::from_utf8_lossy(&body_bytes);
assert!(!text.contains("opaque-xai-reasoning") && !text.contains("response.created"));
}
#[tokio::test]
async fn local_core_sync_finalize_never_wraps_unconvertible_success_sse_as_client_error() {
use base64::Engine as _;
// A complete stream whose output the Gemini client cannot represent.
let raw_sse = concat!(
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"future_item_123\",\"type\":\"future_output\",\"payload\":\"must-not-drop\"}}\n\n",
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_raw_123\",\"object\":\"response\",\"status\":\"completed\",\"model\":\"grok-4.7\",\"output\":[]}}\n\n",
);
let mut payload = core_finalize_payload(
"gemini_chat_sync_finalize",
"gemini:generate_content",
"openai:responses",
200,
json!(null),
);
payload.body_json = None;
payload.body_base64 = Some(base64::engine::general_purpose::STANDARD.encode(raw_sse));
payload.report_context = Some(json!({
"client_api_format": "gemini:generate_content",
"provider_api_format": "openai:responses",
"provider_stream_event_api_format": "openai:responses",
"needs_conversion": true,
}));
assert!(maybe_build_local_core_error_response(
"trace-raw-success-sse",
&test_decision(),
&payload,
)
.expect("response build should not error")
.is_none());
let state = AppState::new().expect("state should build");
let response = submit_local_core_error_or_sync_finalize(
&state,
"trace-raw-success-sse",
&test_decision(),
payload,
)
.await
.expect("finalize should build a response");
assert_eq!(response.status(), http::StatusCode::BAD_GATEWAY);
let body = serde_json::from_slice::<serde_json::Value>(
&to_bytes(response.into_body(), usize::MAX)
.await
.expect("body should read"),
)
.expect("body should decode");
let message = body["error"]["message"]
.as_str()
.expect("error message should exist");
assert!(
message.contains("could not be converted") && !message.contains("must-not-drop"),
"unexpected message: {message}"
);
}
#[tokio::test]
async fn submit_local_core_finalize_keeps_http_200_for_success_image_body() {
let payload = core_finalize_payload(
@@ -9737,7 +9737,10 @@ mod tests {
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"})),
body: RequestBody::from_json(json!({
"model": "gpt-4.1",
"service_tier": "ultrafast"
})),
stream: false,
client_api_format: "openai:chat".into(),
provider_api_format: "openai:chat".into(),
@@ -9758,7 +9761,7 @@ mod tests {
result.body.and_then(|body| body.json_body),
Some(json!({
"content_encoding": encoding,
"body": {"model": "gpt-4.1"},
"body": {"model": "gpt-4.1", "service_tier": "ultrafast"},
}))
);
}