Track prompt cache write token usage (#33454)

## What changed

- Parse `cache_write_tokens` from response input token details and carry it through token usage aggregation.
- Expose `cache_write_input_tokens` in protocol, app-server, exec, and TypeScript SDK usage events, defaulting it to zero for compatibility with older payloads.
- Report cache-write usage in analytics, metrics, traces, and rollout data alongside existing cache-read usage.

## Testing

- Add coverage for response parsing, usage event output, aggregation, analytics, metrics, and telemetry fields.

GitOrigin-RevId: 634e4fbe3086a8923c0ee13b622573e7d9d221bd
This commit is contained in:
Eric Traut
2026-07-16 00:22:53 +00:00
committed by copyberry
parent 78d4a563f9
commit 2edad72de3
46 changed files with 207 additions and 31 deletions
+33 -5
View File
@@ -130,12 +130,11 @@ struct ResponseCompletedUsage {
impl From<ResponseCompletedUsage> for TokenUsage {
fn from(val: ResponseCompletedUsage) -> Self {
let input_tokens_details = val.input_tokens_details.unwrap_or_default();
TokenUsage {
input_tokens: val.input_tokens,
cached_input_tokens: val
.input_tokens_details
.map(|d| d.cached_tokens)
.unwrap_or(0),
cached_input_tokens: input_tokens_details.cached_tokens,
cache_write_input_tokens: input_tokens_details.cache_write_tokens,
output_tokens: val.output_tokens,
reasoning_output_tokens: val
.output_tokens_details
@@ -146,9 +145,11 @@ impl From<ResponseCompletedUsage> for TokenUsage {
}
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Default, Deserialize)]
struct ResponseCompletedInputTokensDetails {
cached_tokens: i64,
#[serde(default)]
cache_write_tokens: i64,
}
#[derive(Debug, Deserialize)]
@@ -805,6 +806,33 @@ mod tests {
}
}
#[test]
fn parses_cache_write_token_usage() {
let usage: ResponseCompletedUsage = serde_json::from_value(json!({
"input_tokens": 100,
"input_tokens_details": {
"cached_tokens": 40,
"cache_write_tokens": 60
},
"output_tokens": 10,
"output_tokens_details": { "reasoning_tokens": 5 },
"total_tokens": 110
}))
.expect("valid response usage");
assert_eq!(
TokenUsage::from(usage),
TokenUsage {
input_tokens: 100,
cached_input_tokens: 40,
cache_write_input_tokens: 60,
output_tokens: 10,
reasoning_output_tokens: 5,
total_tokens: 110,
}
);
}
#[tokio::test]
async fn parses_reasoning_summary_done() {
let events = run_sse(vec![