use std::collections::{HashMap, VecDeque};
use serde_json::{json, Value};
use sha2::{Digest, Sha256};
pub fn ensure_static_breakpoint(body: &mut Value, static_len: usize) -> bool {
if has_marker(body) || static_len == 0 {
return false;
}
let Some(msgs) = body.get_mut("messages").and_then(|m| m.as_array_mut()) else {
return false;
};
if static_len > msgs.len() {
return false;
}
let boundary = &mut msgs[static_len - 1];
let Some(content) = boundary.get_mut("content") else {
return false;
};
match content {
Value::Array(blocks) => {
let Some(last) = blocks.iter_mut().rev().find_map(|b| b.as_object_mut()) else {
return false;
};
last.insert(
"cache_control".to_string(),
json!({"type": "ephemeral"}),
);
true
}
Value::String(text) => {
if text.is_empty() {
return false;
}
*content = json!([{
"type": "text",
"text": std::mem::take(text),
"cache_control": {"type": "ephemeral"},
}]);
true
}
_ => false,
}
}
fn has_marker(v: &Value) -> bool {
match v {
Value::Object(map) => {
map.contains_key("cache_control") || map.values().any(has_marker)
}
Value::Array(arr) => arr.iter().any(has_marker),
_ => false,
}
}
pub fn exact_cache_key(model: &str, system: &Value, messages: &Value, max_tokens: &Value) -> String {
let canon = json!({
"model": model,
"system": system,
"messages": messages,
"max_tokens": max_tokens,
});
let bytes = serde_json::to_vec(&canon).expect("json");
format!("{:x}", Sha256::digest(bytes))
}
pub struct ExactCache {
capacity: usize,
map: HashMap<String, Value>,
order: VecDeque<String>,
pub hits: u64,
pub misses: u64,
}
impl ExactCache {
pub fn new(capacity: usize) -> Self {
Self { capacity, map: HashMap::new(), order: VecDeque::new(), hits: 0, misses: 0 }
}
pub fn lookup(&mut self, key: &str) -> Option<Value> {
match self.map.get(key) {
Some(v) => {
self.hits += 1;
Some(v.clone())
}
None => {
self.misses += 1;
None
}
}
}
pub fn store(&mut self, key: String, response: Value) {
if self.capacity == 0 {
return;
}
if !self.map.contains_key(&key) {
while self.order.len() >= self.capacity {
if let Some(old) = self.order.pop_front() {
self.map.remove(&old);
}
}
self.order.push_back(key.clone());
}
self.map.insert(key, response);
}
pub fn entries(&self) -> usize {
self.map.len()
}
pub fn hit_rate(&self) -> Option<f64> {
let total = self.hits + self.misses;
if total == 0 {
None
} else {
Some(self.hits as f64 / total as f64)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct OpenAiUsage {
pub input_tokens: u64,
pub cached_read_tokens: u64,
pub output_tokens: u64,
}
impl OpenAiUsage {
pub fn cache_hit_ratio(&self) -> Option<f64> {
if self.input_tokens == 0 {
None
} else {
Some(self.cached_read_tokens as f64 / self.input_tokens as f64)
}
}
}
fn u64_field(v: &Value, key: &str) -> u64 {
v.get(key).and_then(|x| x.as_u64()).unwrap_or(0)
}
pub fn parse_openai_usage(response: &Value) -> OpenAiUsage {
let usage = response.get("usage");
let (input, output) = match usage {
Some(u) => (u64_field(u, "prompt_tokens"), u64_field(u, "completion_tokens")),
None => (0, 0),
};
let cached = usage
.and_then(|u| u.get("prompt_tokens_details"))
.map(|d| u64_field(d, "cached_tokens"))
.unwrap_or(0);
OpenAiUsage { input_tokens: input, cached_read_tokens: cached.min(input), output_tokens: output }
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn breakpoint_placed_on_last_static_string_message() {
let mut body = json!({"model": "m", "messages": [
{"role": "system", "content": "static docs"},
{"role": "user", "content": "dynamic q"},
]});
assert!(ensure_static_breakpoint(&mut body, 1));
let c = &body["messages"][0]["content"];
assert_eq!(c[0]["cache_control"]["type"], json!("ephemeral"));
assert!(body["messages"][1]["content"].is_string());
assert!(!has_marker(&body["messages"][1]));
}
#[test]
fn breakpoint_attaches_to_last_block_of_array_content() {
let mut body = json!({"model": "m", "messages": [
{"role": "user", "content": [
{"type": "text", "text": "a"},
{"type": "text", "text": "b"},
]},
{"role": "user", "content": "tail"},
]});
assert!(ensure_static_breakpoint(&mut body, 1));
let blocks = body["messages"][0]["content"].as_array().unwrap();
assert!(blocks[0].get("cache_control").is_none());
assert_eq!(blocks[1]["cache_control"]["type"], json!("ephemeral"));
}
#[test]
fn breakpoint_refuses_when_client_marks_its_own() {
let mut body = json!({"model": "m", "messages": [
{"role": "user", "content": [
{"type": "text", "text": "x", "cache_control": {"type": "ephemeral"}}
]},
]});
let before = body.clone();
assert!(!ensure_static_breakpoint(&mut body, 1));
assert_eq!(body, before);
}
#[test]
fn breakpoint_refuses_degenerate_inputs() {
let mut no_msgs = json!({"model": "m"});
assert!(!ensure_static_breakpoint(&mut no_msgs, 1));
let mut body = json!({"model": "m", "messages": [{"role": "u", "content": "x"}]});
let before = body.clone();
assert!(!ensure_static_breakpoint(&mut body, 0));
assert!(!ensure_static_breakpoint(&mut body, 5));
assert_eq!(body, before);
let mut empty = json!({"model": "m", "messages": [{"role": "u", "content": ""}]});
assert!(!ensure_static_breakpoint(&mut empty, 1));
}
#[test]
fn cache_key_stable_and_field_sensitive() {
let sys = json!("s");
let msgs = json!([{"role": "user", "content": "hi"}]);
let mt = json!(100);
let k1 = exact_cache_key("m", &sys, &msgs, &mt);
let k2 = exact_cache_key("m", &sys, &msgs, &mt);
assert_eq!(k1, k2);
assert_eq!(k1.len(), 64);
assert_ne!(k1, exact_cache_key("m2", &sys, &msgs, &mt));
assert_ne!(k1, exact_cache_key("m", &sys, &json!([{"role": "user", "content": "bye"}]), &mt));
}
#[test]
fn exact_cache_hit_miss_stats_and_eviction() {
let mut c = ExactCache::new(2);
assert_eq!(c.hit_rate(), None);
assert!(c.lookup("k1").is_none());
c.store("k1".into(), json!({"a": 1}));
c.store("k2".into(), json!({"a": 2}));
assert!(c.lookup("k1").is_some());
assert_eq!(c.entries(), 2);
c.store("k3".into(), json!({"a": 3}));
assert_eq!(c.entries(), 2);
assert!(c.lookup("k2").is_some());
assert!((c.hit_rate().unwrap() - 2.0 / 3.0).abs() < 1e-12);
}
#[test]
fn zero_capacity_cache_never_stores() {
let mut c = ExactCache::new(0);
c.store("k".into(), json!(1));
assert_eq!(c.entries(), 0);
assert!(c.lookup("k").is_none());
}
#[test]
fn openai_usage_parses_cached_tokens() {
let r = json!({"usage": {
"prompt_tokens": 1000,
"completion_tokens": 50,
"prompt_tokens_details": {"cached_tokens": 900, "audio_tokens": 0},
}});
let u = parse_openai_usage(&r);
assert_eq!(u, OpenAiUsage { input_tokens: 1000, cached_read_tokens: 900, output_tokens: 50 });
assert!((u.cache_hit_ratio().unwrap() - 0.9).abs() < 1e-12);
}
#[test]
fn openai_usage_absent_fields_read_zero_no_ratio_without_input() {
let r = json!({"usage": {"prompt_tokens": 10, "completion_tokens": 2}});
let u = parse_openai_usage(&r);
assert_eq!(u.cached_read_tokens, 0);
assert_eq!(u.cache_hit_ratio(), Some(0.0));
let empty = parse_openai_usage(&json!({}));
assert_eq!(empty, OpenAiUsage::default());
assert_eq!(empty.cache_hit_ratio(), None);
}
#[test]
fn openai_usage_cached_clamped_to_input() {
let r = json!({"usage": {"prompt_tokens": 10, "completion_tokens": 1,
"prompt_tokens_details": {"cached_tokens": 999}}});
let u = parse_openai_usage(&r);
assert_eq!(u.cached_read_tokens, 10);
assert_eq!(u.cache_hit_ratio(), Some(1.0));
}
}