use crate::catalog::Cost;
use serde_json::Value;
const PER_MILLION: f64 = 1_000_000.0;
fn count(usage: &Value, key: &str) -> u64 {
usage.get(key).and_then(Value::as_u64).unwrap_or(0)
}
fn charge(tokens: u64, rate: Option<f64>) -> Option<f64> {
if tokens == 0 {
return Some(0.0);
}
Some(tokens as f64 * rate? / PER_MILLION)
}
fn highest_published_rate(cost: &Cost) -> Option<f64> {
[cost.input, cost.output, cost.cache_read, cost.cache_write]
.into_iter()
.flatten()
.fold(None, |best: Option<f64>, rate| {
Some(best.map_or(rate, |b| b.max(rate)))
})
}
pub fn price(usage: &Value, cost: &Cost) -> Option<f64> {
let cache_read = count(usage, "cache_read_tokens");
let cache_write = count(usage, "cache_write_tokens");
let input = usage
.get("uncached_input_tokens")
.and_then(Value::as_u64)
.unwrap_or_else(|| count(usage, "prompt_tokens"));
if input == 0 && cache_read == 0 && cache_write == 0 && count(usage, "completion_tokens") == 0 {
return None;
}
let ceiling = highest_published_rate(cost);
let rate_for = |own: Option<f64>| own.or(ceiling);
Some(
charge(input, rate_for(cost.input))?
+ charge(count(usage, "completion_tokens"), rate_for(cost.output))?
+ charge(cache_read, rate_for(cost.cache_read))?
+ charge(cache_write, rate_for(cost.cache_write))?,
)
}
pub fn for_model(model: &str) -> Option<Cost> {
crate::catalog::resolve(model)?.cost
}
pub fn for_target(provider: &str, model: &str) -> Option<Cost> {
for_model(&format!("{provider}/{model}")).or_else(|| for_model(model))
}
pub fn cost_usd(provider: &str, model: &str, usage: &Value) -> Option<f64> {
let info = crate::catalog::resolve(&format!("{provider}/{model}"))
.filter(|m| m.cost.is_some())
.or_else(|| crate::catalog::resolve(model))?;
let input_tokens = usage
.get("uncached_input_tokens")
.and_then(Value::as_u64)
.map(|n| {
n.saturating_add(count(usage, "cache_read_tokens"))
.saturating_add(count(usage, "cache_write_tokens"))
})
.unwrap_or_else(|| count(usage, "prompt_tokens"));
price(usage, &info.cost_for_input_tokens(input_tokens)?)
}
pub fn is_priceable(provider: &str, model: &str) -> bool {
for_target(provider, model).is_some()
}
pub fn stamped(usage: &Value) -> Option<f64> {
usage.get("cost_usd").and_then(Value::as_f64)
}
pub fn stamp(provider: &str, model: &str, response: &mut Value) {
if !response["usage"].is_object() {
return;
}
let cost = cost_usd(provider, model, &response["usage"]);
response["usage"]["cost_usd"] = match cost {
Some(usd) => serde_json::json!(usd),
None => Value::Null,
};
}
pub fn stamp_chunk(provider: &str, model: &str, chunk: String) -> String {
if !chunk.contains("\"usage\":{") {
return chunk;
}
let Ok(mut value) = serde_json::from_str::<Value>(&chunk) else {
return chunk;
};
if !value["usage"].is_object() {
return chunk;
}
stamp(provider, model, &mut value);
value.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grok_context_threshold_counts_cached_input_and_reprices_the_whole_response() {
for model in ["grok-4.6", "grok-4.7"] {
let mut usage = serde_json::json!({"uncached_input_tokens":50_000,"cache_read_tokens":150_000,"completion_tokens":1000});
close(cost_usd("xai", model, &usage), 0.1 + 0.075 + 0.006);
usage["cache_read_tokens"] = serde_json::json!(150_001);
close(cost_usd("xai", model, &usage), 0.2 + 0.150001 + 0.012);
}
}
use serde_json::json;
const TOLERANCE: f64 = 1e-12;
fn priced() -> Cost {
Cost {
input: Some(3.0),
output: Some(15.0),
cache_read: Some(0.3),
cache_write: Some(3.75),
}
}
fn close(actual: Option<f64>, expected: f64) {
let actual = actual.expect("a priced model must produce a cost");
assert!(
(actual - expected).abs() < TOLERANCE,
"expected {expected}, got {actual}"
);
}
#[test]
fn exact_arithmetic_on_a_known_price() {
let usage = json!({"uncached_input_tokens": 1_000_000, "completion_tokens": 1_000_000});
close(price(&usage, &priced()), 18.0);
}
#[test]
fn cache_reads_and_writes_are_priced_separately() {
let usage = json!({
"uncached_input_tokens": 1_000_000,
"completion_tokens": 0,
"cache_read_tokens": 1_000_000,
"cache_write_tokens": 1_000_000,
});
close(price(&usage, &priced()), 7.05);
}
#[test]
fn a_cached_prompt_is_not_billed_twice() {
let mut usage = json!({"prompt_tokens": 1_000_000, "completion_tokens": 0});
crate::usage::normalize_cache(
&json!({"prompt_tokens": 1_000_000, "prompt_tokens_details": {"cached_tokens": 900_000}}),
&mut usage,
);
close(price(&usage, &priced()), 0.57);
}
#[test]
fn an_exclusive_prompt_provider_keeps_its_whole_prompt() {
let mut usage = json!({"prompt_tokens": 100_000, "completion_tokens": 0});
crate::usage::normalize_cache(
&json!({"input_tokens": 100_000, "cache_read_input_tokens": 900_000}),
&mut usage,
);
assert_eq!(usage["uncached_input_tokens"], 100_000);
close(price(&usage, &priced()), 0.57);
}
#[test]
fn an_unreported_usage_object_is_unknown_not_free() {
assert_eq!(
price(
&json!({"prompt_tokens":0,"completion_tokens":0,"cache_read_tokens":0,"cache_write_tokens":0,"uncached_input_tokens":0}),
&priced()
),
None
);
assert_eq!(price(&json!({}), &priced()), None);
close(
price(&json!({"uncached_input_tokens": 1_000_000}), &priced()),
3.0,
);
}
#[test]
fn a_model_with_no_price_is_unknown_not_free() {
let usage = json!({"uncached_input_tokens": 1_000, "completion_tokens": 1_000});
assert_eq!(price(&usage, &Cost::default()), None);
}
#[test]
fn a_missing_cache_rate_is_charged_at_the_models_highest_rate() {
let partial = Cost {
input: Some(3.0),
output: Some(15.0),
cache_read: None,
cache_write: None,
};
close(
price(
&json!({"uncached_input_tokens": 1_000_000, "completion_tokens": 0, "cache_read_tokens": 0}),
&partial,
),
3.0,
);
close(
price(
&json!({"uncached_input_tokens": 1_000_000, "cache_read_tokens": 1_000_000}),
&partial,
),
18.0,
);
let complete = Cost {
cache_read: Some(0.3),
..partial
};
let usage = json!({"uncached_input_tokens": 1_000_000, "cache_read_tokens": 1_000_000});
assert!(
price(&usage, &partial).unwrap() >= price(&usage, &complete).unwrap(),
"a fallback rate must bound the real one from above, or a cap under-charges"
);
}
#[test]
fn stamp_always_writes_the_key() {
let mut response = json!({"usage": {"uncached_input_tokens": 1, "completion_tokens": 1}});
stamp("nowhere", "definitely-not-a-model", &mut response);
assert!(
response["usage"]["cost_usd"].is_null(),
"an unpriceable model must stamp null, never 0"
);
assert!(response["usage"].get("cost_usd").is_some());
}
#[test]
fn a_response_without_usage_is_left_alone() {
let mut response = json!({"id": "r1"});
stamp("nowhere", "anything", &mut response);
assert!(response.get("usage").is_none());
}
#[test]
fn stamp_chunk_leaves_usageless_chunks_untouched() {
assert_eq!(stamp_chunk("nowhere", "m", "[DONE]".into()), "[DONE]");
let no_usage = json!({"choices": []}).to_string();
assert_eq!(stamp_chunk("nowhere", "m", no_usage.clone()), no_usage);
let stamped = stamp_chunk(
"nowhere",
"m",
json!({"usage": {"prompt_tokens": 1}}).to_string(),
);
assert!(stamped.contains("cost_usd"));
}
}