Preserve raw response usage metadata (#41980)

## What changed

- Include the complete upstream `response.usage` object in
  `ResponseUsageMetadata.metadata` while retaining the existing `amount` field.
- Expose the metadata through `rawResponse/completed` notifications and update
  the generated JSON and TypeScript schemas.

## Testing

- Cover SSE, WebSocket, turn, and compaction completion paths.

GitOrigin-RevId: 6d79b1b4c92ac11abf43ec98f380b4868288a701
This commit is contained in:
sayan-oai authored and copyberry committed 2026-09-01 05:17:11 +00:00
1 parent d038f3448f
commit e017e93ace
14 files changed
+62 -13

No files matched your search

@@ -254,6 +254,8 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -
]);
let mut completed = responses::ev_completed_with_tokens("r1", /*total_tokens*/ 200);
completed["response"]["usage_metadata"] = serde_json::json!({ "amount": "0.125" });
completed["response"]["usage"]["extra"] = serde_json::json!({ "label": "example" });
let expected_metadata = completed["response"]["usage"].clone();
let sse = responses::sse(vec![
responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"),
completed,
@@ -338,6 +340,7 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -
response_id: "r1".to_string(),
usage_metadata: Some(ResponseUsageMetadata {
amount: Some("0.125".to_string()),
metadata: Some(expected_metadata),
}),
usage: Some(TokenUsageBreakdown {
total_tokens: 200,
@@ -904,19 +904,24 @@ async fn turn_start_sends_service_tier_id_to_model_request() -> Result<()> {
Ok(())
}
#[test_case(None, json!(null); "without_usage_metadata")]
#[test_case(Some(json!({})), json!({ "amount": null }); "without_amount")]
#[test_case(Some(json!({ "amount": null })), json!({ "amount": null }); "null_amount")]
#[test_case(Some(json!({ "amount": "0" })), json!({ "amount": "0" }); "zero_amount")]
#[test_case(None, None, None; "without_usage_metadata")]
#[test_case(Some(json!({})), None, None; "without_amount")]
#[test_case(Some(json!({ "amount": null })), None, None; "null_amount")]
#[test_case(Some(json!({ "amount": "0" })), Some("0"), None; "zero_amount")]
#[test_case(
Some(json!({ "amount": "0.12345678901234567890" })),
json!({ "amount": "0.12345678901234567890" });
Some("0.12345678901234567890"),
Some(json!({ "label": "example", "items": [0, null, true] }));
"exact_amount"
)]
#[test_case(None, None, Some(json!(null)); "null_extra")]
#[test_case(None, None, Some(json!(0)); "zero_extra")]
#[test_case(None, None, Some(json!({ "label": "example" })); "nested_extra")]
#[tokio::test]
async fn turn_start_emits_raw_response_completed_with_upstream_usage(
upstream_metadata: Option<Value>,
expected_metadata: Value,
expected_amount: Option<&str>,
extra: Option<Value>,
) -> Result<()> {
let server = responses::start_mock_server().await;
let mut completed = json!({
@@ -935,6 +940,13 @@ async fn turn_start_emits_raw_response_completed_with_upstream_usage(
if let Some(metadata) = upstream_metadata {
completed["response"]["usage_metadata"] = metadata;
}
if let Some(extra) = extra {
completed["response"]["usage"]["extra"] = extra;
}
let expected_metadata = json!({
"amount": expected_amount,
"metadata": completed["response"]["usage"],
});
let body = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_assistant_message("msg-1", "Done"),