Expose response usage metadata in completion events (#41087)

## What changed

- Parse optional `usage_metadata.amount` values from Responses API completion events without converting their string representation.
- Propagate the metadata through SSE and WebSocket streams, regular turns, and remote compaction.
- Add `usageMetadata` to the app-server `rawResponse/completed` notification and its generated schemas.

## Testing

- Cover missing, null, zero, and high-precision amounts, including completion events emitted during compaction.

GitOrigin-RevId: b16d81e8350eab0dc7ff613d0dc9dbed73041713
This commit is contained in:
Dylan Hurd
2026-08-27 07:50:05 +00:00
committed by copyberry
parent b592a0bfed
commit 2c4a95736b
25 changed files with 250 additions and 18 deletions
+2
View File
@@ -1,4 +1,5 @@
use crate::error::ApiError;
use codex_protocol::ResponseUsageMetadata;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::config_types::Verbosity as VerbosityConfig;
use codex_protocol::models::ResponseItem;
@@ -113,6 +114,7 @@ pub enum ResponseEvent {
Completed {
response_id: String,
token_usage: Option<TokenUsage>,
usage_metadata: Option<ResponseUsageMetadata>,
/// Did the model affirmatively end its turn? Some providers do not set this,
/// so we rely on fallback logic when this is `None`.
end_turn: Option<bool>,
+11
View File
@@ -8,6 +8,7 @@ use crate::safety_buffering::treatment_from_headers;
use crate::telemetry::SseTelemetry;
use codex_client::ByteStream;
use codex_client::StreamResponse;
use codex_protocol::ResponseUsageMetadata;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::MisalignmentErrorDetails;
use codex_protocol::protocol::ModelVerification;
@@ -118,6 +119,7 @@ struct ResponseCompleted {
id: String,
#[serde(default)]
usage: Option<ResponseCompletedUsage>,
usage_metadata: Option<ResponseUsageMetadata>,
#[serde(default)]
end_turn: Option<bool>,
}
@@ -481,6 +483,7 @@ pub fn process_responses_event(
return Ok(Some(ResponseEvent::Completed {
response_id: resp.id,
token_usage: resp.usage.map(Into::into),
usage_metadata: resp.usage_metadata,
end_turn: resp.end_turn,
}));
}
@@ -864,10 +867,12 @@ mod tests {
Ok(ResponseEvent::Completed {
response_id,
token_usage,
usage_metadata,
end_turn,
}) => {
assert_eq!(response_id, "resp1");
assert!(token_usage.is_none());
assert!(usage_metadata.is_none());
assert!(end_turn.is_none());
}
other => panic!("unexpected third event: {other:?}"),
@@ -1060,10 +1065,12 @@ mod tests {
Ok(ResponseEvent::Completed {
response_id,
token_usage,
usage_metadata,
end_turn,
}) => {
assert_eq!(response_id, "resp1");
assert!(token_usage.is_none());
assert!(usage_metadata.is_none());
assert!(end_turn.is_none());
}
other => panic!("unexpected event: {other:?}"),
@@ -1570,6 +1577,7 @@ mod tests {
ResponseEvent::Completed {
response_id,
token_usage: None,
usage_metadata: None,
end_turn: None,
} if response_id == "resp-1"
);
@@ -1607,6 +1615,7 @@ mod tests {
ResponseEvent::Completed {
response_id,
token_usage: None,
usage_metadata: None,
end_turn: None,
} if response_id == "resp-1"
);
@@ -1642,6 +1651,7 @@ mod tests {
ResponseEvent::Completed {
response_id,
token_usage: None,
usage_metadata: None,
end_turn: None,
} if response_id == "resp-1"
);
@@ -1677,6 +1687,7 @@ mod tests {
ResponseEvent::Completed {
response_id,
token_usage: None,
usage_metadata: None,
end_turn: None,
} if response_id == "resp-1"
);