fix(gateway): decode stream-encoded provider response JSON

This commit is contained in:
ZheFox
2026-05-20 20:40:48 +08:00
parent ab2287202d
commit d3355a8a09
2 changed files with 53 additions and 11 deletions

View File

@@ -1486,6 +1486,17 @@ fn provider_query_decode_execution_body(
.and_then(|value| base64::engine::general_purpose::STANDARD.decode(value).ok()) .and_then(|value| base64::engine::general_purpose::STANDARD.decode(value).ok())
} }
fn provider_query_execution_json_body(result: &aether_contracts::ExecutionResult) -> Option<Value> {
result
.body
.as_ref()
.and_then(|body| body.json_body.clone())
.or_else(|| {
provider_query_decode_execution_body(result)
.and_then(|body| serde_json::from_slice::<Value>(&body).ok())
})
}
fn provider_query_aggregate_standard_stream_sync_response( fn provider_query_aggregate_standard_stream_sync_response(
provider_api_format: &str, provider_api_format: &str,
body: &[u8], body: &[u8],
@@ -1505,10 +1516,7 @@ fn provider_query_standard_execution_response_body(
provider_api_format: &str, provider_api_format: &str,
result: &aether_contracts::ExecutionResult, result: &aether_contracts::ExecutionResult,
) -> Option<Value> { ) -> Option<Value> {
let body = result let body = provider_query_execution_json_body(result)
.body
.as_ref()
.and_then(|body| body.json_body.clone())
.or_else(|| { .or_else(|| {
provider_query_decode_execution_body(result).and_then(|body| { provider_query_decode_execution_body(result).and_then(|body| {
provider_query_aggregate_standard_stream_sync_response(provider_api_format, &body) provider_query_aggregate_standard_stream_sync_response(provider_api_format, &body)
@@ -1527,10 +1535,8 @@ fn provider_query_standard_execution_response_body(
fn provider_query_extract_error_message( fn provider_query_extract_error_message(
result: &aether_contracts::ExecutionResult, result: &aether_contracts::ExecutionResult,
) -> Option<String> { ) -> Option<String> {
result provider_query_execution_json_body(result)
.body
.as_ref() .as_ref()
.and_then(|body| body.json_body.as_ref())
.and_then(Value::as_object) .and_then(Value::as_object)
.and_then(|value| { .and_then(|value| {
value value
@@ -1589,7 +1595,7 @@ async fn provider_query_finalize_kiro_result(
})), })),
status_code: result.status_code, status_code: result.status_code,
headers: result.headers.clone(), headers: result.headers.clone(),
body_json: result.body.as_ref().and_then(|body| body.json_body.clone()), body_json: provider_query_execution_json_body(result),
client_body_json: None, client_body_json: None,
body_base64: result body_base64: result
.body .body
@@ -1879,7 +1885,7 @@ async fn provider_query_finalize_openai_image_result(
})), })),
status_code: result.status_code, status_code: result.status_code,
headers: result.headers.clone(), headers: result.headers.clone(),
body_json: result.body.as_ref().and_then(|body| body.json_body.clone()), body_json: provider_query_execution_json_body(result),
client_body_json: None, client_body_json: None,
body_base64: result body_base64: result
.body .body
@@ -2177,9 +2183,9 @@ async fn provider_query_execute_openai_image_test_candidate(
&result, &result,
) )
.await? .await?
.or_else(|| result.body.as_ref().and_then(|body| body.json_body.clone())) .or_else(|| provider_query_execution_json_body(&result))
} else { } else {
result.body.as_ref().and_then(|body| body.json_body.clone()) provider_query_execution_json_body(&result)
}; };
let did_fail = result.status_code >= 400; let did_fail = result.status_code >= 400;
let error_message = if did_fail { let error_message = if did_fail {

View File

@@ -90,6 +90,42 @@ fn provider_query_test_request_body_defaults_missing_model() {
assert_eq!(body["model"], json!("fallback-model")); assert_eq!(body["model"], json!("fallback-model"));
} }
#[test]
fn provider_query_execution_json_body_decodes_stream_encoded_json_response() {
use base64::Engine as _;
let body = json!({
"created": 1,
"data": [{
"url": "https://example.test/image.png"
}]
});
let encoded_body = base64::engine::general_purpose::STANDARD.encode(
serde_json::to_vec(&body).expect("test body should serialize"),
);
let result = aether_contracts::ExecutionResult {
request_id: "request-1".to_string(),
candidate_id: None,
status_code: 200,
headers: std::collections::BTreeMap::from([(
"content-type".to_string(),
"application/json".to_string(),
)]),
body: Some(aether_contracts::ResponseBody {
json_body: None,
body_bytes_b64: Some(encoded_body),
}),
telemetry: None,
error: None,
};
assert_eq!(provider_query_execution_json_body(&result), Some(body.clone()));
assert_eq!(
provider_query_standard_execution_response_body("openai:image", &result),
Some(body)
);
}
#[test] #[test]
fn provider_query_test_request_body_fills_empty_conversation() { fn provider_query_test_request_body_fills_empty_conversation() {
let payload = json!({ let payload = json!({