use crate::utils::{
TokenCounts, accumulate_i64_fields, accumulate_nested_object, extract_token_counts,
};
use serde_json::{Value, json};
pub(crate) fn merge_usage_values(existing: &mut Value, new: &Value) {
let (Some(existing_ro), Some(new_ro)) = (existing.as_object(), new.as_object()) else {
return;
};
let existing_flat = existing_ro.contains_key("input_tokens");
let existing_codex = existing_ro.contains_key("total_token_usage");
let new_flat = new_ro.contains_key("input_tokens");
let new_codex = new_ro.contains_key("total_token_usage");
if (existing_flat && new_codex) || (existing_codex && new_flat) {
let merged = add_token_counts(&extract_token_counts(existing), &extract_token_counts(new));
*existing = token_counts_to_flat_value(&merged);
return;
}
if let (Some(existing_obj), Some(new_obj)) = (existing.as_object_mut(), new.as_object()) {
if existing_obj.contains_key("input_tokens") {
accumulate_i64_fields(
existing_obj,
new_obj,
&[
"input_tokens",
"cache_creation_input_tokens",
"cache_read_input_tokens",
"output_tokens",
"thoughts_tokens",
"reasoning_output_tokens",
"tool_tokens",
"total_tokens",
],
);
if let Some(new_cache) = new_obj.get("cache_creation").and_then(|v| v.as_object()) {
accumulate_nested_object(existing_obj, "cache_creation", new_cache);
}
if let Some(new_stu) = new_obj.get("server_tool_use").and_then(|v| v.as_object()) {
accumulate_nested_object(existing_obj, "server_tool_use", new_stu);
}
if let Some(new_above) = new_obj.get("above_tier").and_then(|v| v.as_object()) {
accumulate_nested_object(existing_obj, "above_tier", new_above);
}
}
else if existing_obj.contains_key("total_token_usage") {
if let Some(new_total) = new_obj.get("total_token_usage").and_then(|v| v.as_object()) {
accumulate_nested_object(existing_obj, "total_token_usage", new_total);
}
if let Some(new_above) = new_obj.get("above_tier").and_then(|v| v.as_object()) {
accumulate_nested_object(existing_obj, "above_tier", new_above);
}
}
}
}
fn add_token_counts(a: &TokenCounts, b: &TokenCounts) -> TokenCounts {
TokenCounts {
input_tokens: a.input_tokens + b.input_tokens,
output_tokens: a.output_tokens + b.output_tokens,
reasoning_tokens: a.reasoning_tokens + b.reasoning_tokens,
cache_read: a.cache_read + b.cache_read,
cache_creation: a.cache_creation + b.cache_creation,
cache_creation_5m: a.cache_creation_5m + b.cache_creation_5m,
cache_creation_1h: a.cache_creation_1h + b.cache_creation_1h,
web_search_requests: a.web_search_requests + b.web_search_requests,
total: a.total + b.total,
above_input: a.above_input + b.above_input,
above_output: a.above_output + b.above_output,
above_reasoning: a.above_reasoning + b.above_reasoning,
above_cache_read: a.above_cache_read + b.above_cache_read,
above_cache_creation_5m: a.above_cache_creation_5m + b.above_cache_creation_5m,
above_cache_creation_1h: a.above_cache_creation_1h + b.above_cache_creation_1h,
}
}
pub fn normalize_usage_value(usage: &Value) -> Value {
let counts = extract_token_counts(usage);
let mut value = token_counts_to_flat_value(&counts);
if let Some(obj) = value.as_object_mut() {
obj.insert("total_tokens".into(), json!(counts.total));
}
value
}
fn token_counts_to_flat_value(c: &TokenCounts) -> Value {
let mut obj = serde_json::Map::new();
obj.insert("input_tokens".into(), json!(c.input_tokens));
obj.insert("output_tokens".into(), json!(c.output_tokens));
obj.insert("reasoning_output_tokens".into(), json!(c.reasoning_tokens));
obj.insert("cache_read_input_tokens".into(), json!(c.cache_read));
obj.insert(
"cache_creation_input_tokens".into(),
json!(c.cache_creation),
);
if c.cache_creation_5m != 0 || c.cache_creation_1h != 0 {
obj.insert(
"cache_creation".into(),
json!({
"ephemeral_5m_input_tokens": c.cache_creation_5m,
"ephemeral_1h_input_tokens": c.cache_creation_1h,
}),
);
}
if c.web_search_requests != 0 {
obj.insert(
"server_tool_use".into(),
json!({ "web_search_requests": c.web_search_requests }),
);
}
if c.above_input != 0
|| c.above_output != 0
|| c.above_reasoning != 0
|| c.above_cache_read != 0
|| c.above_cache_creation_5m != 0
|| c.above_cache_creation_1h != 0
{
obj.insert(
"above_tier".into(),
json!({
"input_tokens": c.above_input,
"output_tokens": c.above_output,
"reasoning_tokens": c.above_reasoning,
"cache_read_tokens": c.above_cache_read,
"cache_creation_5m_tokens": c.above_cache_creation_5m,
"cache_creation_1h_tokens": c.above_cache_creation_1h,
}),
);
}
Value::Object(obj)
}