use serde_json::{json, Value};
const MAX_BREAKPOINTS: usize = 4;
pub fn apply_anthropic_prompt_cache(body: &mut Value, ttl: &str) {
let marker = build_marker(ttl);
let mut budget = MAX_BREAKPOINTS;
if let Some(obj) = body.as_object_mut() {
if let Some(system) = obj.get_mut("system") {
if mark_system(system, &marker) {
budget -= 1;
}
}
if budget == 0 {
return;
}
if let Some(messages) = obj.get_mut("messages").and_then(|v| v.as_array_mut()) {
let mut targets: Vec<usize> = messages
.iter()
.enumerate()
.filter(|(_, m)| {
m.get("role")
.and_then(|r| r.as_str())
.map(|r| r != "system")
.unwrap_or(true)
})
.map(|(i, _)| i)
.collect();
let take = budget.min(targets.len());
let start = targets.len().saturating_sub(take);
targets.drain(..start);
for idx in targets {
mark_message(&mut messages[idx], &marker);
}
}
}
}
fn build_marker(ttl: &str) -> Value {
if ttl == "1h" {
json!({"type": "ephemeral", "ttl": "1h"})
} else {
json!({"type": "ephemeral"})
}
}
fn mark_system(system: &mut Value, marker: &Value) -> bool {
match system {
Value::String(s) => {
if s.is_empty() {
return false;
}
*system = json!([{
"type": "text",
"text": s.clone(),
"cache_control": marker,
}]);
true
}
Value::Array(blocks) => {
if let Some(last) = blocks.last_mut() {
attach_marker_to_block(last, marker);
true
} else {
false
}
}
_ => false,
}
}
fn mark_message(msg: &mut Value, marker: &Value) {
let Some(obj) = msg.as_object_mut() else {
return;
};
let content = obj.entry("content").or_insert(Value::Null);
match content {
Value::String(s) => {
*content = json!([{
"type": "text",
"text": s.clone(),
"cache_control": marker,
}]);
}
Value::Array(blocks) => {
if let Some(last) = blocks.last_mut() {
attach_marker_to_block(last, marker);
}
}
_ => {
}
}
}
fn attach_marker_to_block(block: &mut Value, marker: &Value) {
if let Some(obj) = block.as_object_mut() {
obj.insert("cache_control".to_string(), marker.clone());
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn ephemeral() -> Value {
json!({"type": "ephemeral"})
}
#[test]
fn marks_system_string_as_block_array() {
let mut body = json!({
"model": "claude-sonnet-4-5",
"system": "You are helpful.",
"messages": [],
});
apply_anthropic_prompt_cache(&mut body, "5m");
let sys = &body["system"];
assert!(sys.is_array());
assert_eq!(sys[0]["text"], "You are helpful.");
assert_eq!(sys[0]["cache_control"], ephemeral());
}
#[test]
fn empty_system_string_no_marker() {
let mut body = json!({
"model": "claude-sonnet-4-5",
"system": "",
"messages": [
{"role": "user", "content": "hi"},
],
});
apply_anthropic_prompt_cache(&mut body, "5m");
assert!(body["system"].is_string());
assert_eq!(
body["messages"][0]["content"][0]["cache_control"],
ephemeral()
);
}
#[test]
fn marks_last_three_non_system_messages() {
let mut body = json!({
"system": "S",
"messages": [
{"role": "user", "content": "m1"},
{"role": "assistant", "content": "m2"},
{"role": "user", "content": "m3"},
{"role": "assistant", "content": "m4"},
{"role": "user", "content": "m5"},
],
});
apply_anthropic_prompt_cache(&mut body, "5m");
assert!(body["messages"][0]["content"].is_string(), "m1 untouched");
assert!(body["messages"][1]["content"].is_string(), "m2 untouched");
for i in [2, 3, 4] {
assert_eq!(
body["messages"][i]["content"][0]["cache_control"],
ephemeral(),
"m{} should be marked",
i + 1
);
}
}
#[test]
fn marks_last_block_when_content_is_array() {
let mut body = json!({
"system": "S",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "first"},
{"type": "text", "text": "second"},
],
},
],
});
apply_anthropic_prompt_cache(&mut body, "5m");
let blocks = &body["messages"][0]["content"];
assert!(blocks[0].get("cache_control").is_none());
assert_eq!(blocks[1]["cache_control"], ephemeral());
}
#[test]
fn ttl_1h_marker() {
let mut body = json!({
"system": "S",
"messages": [],
});
apply_anthropic_prompt_cache(&mut body, "1h");
assert_eq!(body["system"][0]["cache_control"]["ttl"], "1h");
}
#[test]
fn budget_capped_at_four_total() {
let mut body = json!({
"system": "S",
"messages": [
{"role": "user", "content": "m1"},
{"role": "assistant", "content": "m2"},
{"role": "user", "content": "m3"},
{"role": "assistant", "content": "m4"},
{"role": "user", "content": "m5"},
],
});
apply_anthropic_prompt_cache(&mut body, "5m");
assert!(body["messages"][1]["content"].is_string());
}
#[test]
fn no_messages_just_system() {
let mut body = json!({
"system": "S",
"messages": [],
});
apply_anthropic_prompt_cache(&mut body, "5m");
assert_eq!(body["system"][0]["cache_control"], ephemeral());
}
#[test]
fn missing_system_field_safe() {
let mut body = json!({
"messages": [{"role": "user", "content": "hi"}],
});
apply_anthropic_prompt_cache(&mut body, "5m");
assert_eq!(
body["messages"][0]["content"][0]["cache_control"],
ephemeral()
);
}
}