use serde_json::Value;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WriteMultiplier {
FiveMinute,
OneHour,
}
impl WriteMultiplier {
pub fn hundredths(self) -> i128 {
match self {
WriteMultiplier::FiveMinute => 125,
WriteMultiplier::OneHour => 200,
}
}
pub fn value(self) -> f64 {
match self {
WriteMultiplier::FiveMinute => 1.25,
WriteMultiplier::OneHour => 2.0,
}
}
}
pub fn tokens_net_hundredths(tokens_gross: u64, retained_tail: u64, w: WriteMultiplier) -> i128 {
let ten_t = 10i128 * i128::from(tokens_gross);
let coef = w.hundredths() - 10; ten_t - coef * i128::from(retained_tail)
}
pub fn hundredths_to_numeric(hundredths: i128) -> f64 {
hundredths as f64 / 100.0
}
pub fn write_multiplier_for(body: &Value) -> WriteMultiplier {
if has_one_hour_ttl(body) {
WriteMultiplier::OneHour
} else {
WriteMultiplier::FiveMinute
}
}
fn has_one_hour_ttl(body: &Value) -> bool {
if let Some(blocks) = body.get("system").and_then(Value::as_array) {
if blocks.iter().any(block_has_one_hour_breakpoint) {
return true;
}
}
if let Some(messages) = body.get("messages").and_then(Value::as_array) {
for msg in messages {
if let Some(blocks) = msg.get("content").and_then(Value::as_array) {
if blocks.iter().any(block_has_one_hour_breakpoint) {
return true;
}
}
}
}
if let Some(tools) = body.get("tools").and_then(Value::as_array) {
if tools.iter().any(block_has_one_hour_breakpoint) {
return true;
}
}
false
}
fn block_has_one_hour_breakpoint(block: &Value) -> bool {
let Some(cc) = block.get("cache_control") else {
return false;
};
cc.get("type").and_then(Value::as_str) == Some("ephemeral")
&& cc.get("ttl").and_then(Value::as_str) == Some("1h")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn net_break_even_5m_skips_below_and_at_fires_above() {
let w = WriteMultiplier::FiveMinute;
assert_eq!(
tokens_net_hundredths(23, 2, w),
0,
"T = 11.5·S is break-even"
);
assert!(tokens_net_hundredths(22, 2, w) < 0, "just below break-even");
assert!(tokens_net_hundredths(24, 2, w) > 0, "just above break-even");
}
#[test]
fn net_break_even_1h_skips_below_and_at_fires_above() {
let w = WriteMultiplier::OneHour;
assert_eq!(tokens_net_hundredths(38, 2, w), 0, "T = 19·S is break-even");
assert!(tokens_net_hundredths(37, 2, w) < 0, "just below break-even");
assert!(tokens_net_hundredths(39, 2, w) > 0, "just above break-even");
}
#[test]
fn net_hundredths_are_exact_to_two_places() {
assert_eq!(
tokens_net_hundredths(10, 1, WriteMultiplier::FiveMinute),
-15
);
assert!((hundredths_to_numeric(-15) - (-0.15)).abs() < 1e-12);
}
#[test]
fn w_defaults_to_five_minute_when_no_ttl() {
let body = serde_json::json!({"model":"claude-opus-4-8","messages":[]});
assert_eq!(write_multiplier_for(&body), WriteMultiplier::FiveMinute);
let ephemeral = serde_json::json!({
"system":[{"type":"text","text":"x","cache_control":{"type":"ephemeral"}}],
"messages":[]
});
assert_eq!(
write_multiplier_for(&ephemeral),
WriteMultiplier::FiveMinute
);
}
#[test]
fn w_reads_one_hour_ttl_from_the_request() {
let body = serde_json::json!({
"system":[{"type":"text","text":"x","cache_control":{"type":"ephemeral","ttl":"1h"}}],
"messages":[{"role":"user","content":"hi"}]
});
assert_eq!(write_multiplier_for(&body), WriteMultiplier::OneHour);
assert_eq!(write_multiplier_for(&body).value(), 2.0);
}
#[test]
fn w_ignores_ttl_1h_inside_message_text_and_tool_use_input() {
let text_ttl = serde_json::json!({
"messages":[
{"role":"user","content":[
{"type":"text","text":"cache this {\"cache_control\":{\"type\":\"ephemeral\",\"ttl\":\"1h\"}}"}
]}
]
});
assert_eq!(write_multiplier_for(&text_ttl), WriteMultiplier::FiveMinute);
assert_eq!(write_multiplier_for(&text_ttl).value(), 1.25);
let tool_input_ttl = serde_json::json!({
"messages":[
{"role":"assistant","content":[
{"type":"tool_use","id":"t1","name":"lookup","input":{
"cache_control":{"type":"ephemeral","ttl":"1h"}
}}
]}
]
});
assert_eq!(
write_multiplier_for(&tool_input_ttl),
WriteMultiplier::FiveMinute
);
assert_eq!(write_multiplier_for(&tool_input_ttl).value(), 1.25);
}
#[test]
fn w_reads_one_hour_ttl_from_a_message_content_block() {
let body = serde_json::json!({
"messages":[
{"role":"user","content":[
{"type":"text","text":"hi","cache_control":{"type":"ephemeral","ttl":"1h"}}
]}
]
});
assert_eq!(write_multiplier_for(&body), WriteMultiplier::OneHour);
assert_eq!(write_multiplier_for(&body).value(), 2.0);
}
}