1use serde_json::{Map, Value};
42use sha2::{Digest, Sha256};
43
44pub const KEY_PREFIX: &str = "anthropic-cache";
45
46pub enum System<'a> {
49 Text(&'a str),
51 Blocks(&'a Value),
53 None,
55}
56
57pub fn find_breakpoints(blocks: &Value) -> Vec<usize> {
61 let Some(arr) = blocks.as_array() else {
62 return Vec::new();
63 };
64 arr.iter()
65 .enumerate()
66 .filter_map(|(i, b)| match b.as_object() {
67 Some(o) => match o.get("cache_control") {
68 Some(v) if !v.is_null() => Some(i),
69 _ => None,
70 },
71 None => None,
72 })
73 .collect()
74}
75
76pub fn scope_blocks(blocks: &Value) -> Vec<Value> {
80 let Some(arr) = blocks.as_array() else {
81 return Vec::new();
82 };
83 if arr.is_empty() {
84 return Vec::new();
85 }
86 let bps = find_breakpoints(blocks);
87 let end = match bps.last() {
88 Some(&last) => last + 1,
89 None => arr.len(),
90 };
91 arr[..end].to_vec()
92}
93
94pub fn compute_cache_key(model: &str, system: System<'_>, tools: Option<&Value>) -> String {
103 let scoped = scope_blocks(&normalize_system(system));
104 let tools_vec: Vec<Value> = match tools.and_then(|v| v.as_array()) {
105 Some(arr) => arr.to_vec(),
106 None => Vec::new(),
107 };
108
109 let mut body = Map::new();
110 body.insert("model".to_string(), Value::String(model.to_string()));
111 body.insert("system".to_string(), Value::Array(scoped));
112 body.insert("tools".to_string(), Value::Array(tools_vec));
113 let body = Value::Object(body);
114
115 let blob = canonical_json(&body);
116 let mut hasher = Sha256::new();
117 hasher.update(blob.as_bytes());
118 let digest = hex_lower(&hasher.finalize());
119 format!("{KEY_PREFIX}:{model}:sha256:{digest}")
120}
121
122pub fn canonical_json(value: &Value) -> String {
126 let mut out = String::new();
127 write_canonical(value, &mut out);
128 out
129}
130
131fn normalize_system(system: System<'_>) -> Value {
132 match system {
133 System::None => Value::Array(Vec::new()),
134 System::Text(s) => {
135 let mut block = Map::new();
136 block.insert("type".to_string(), Value::String("text".to_string()));
137 block.insert("text".to_string(), Value::String(s.to_string()));
138 Value::Array(vec![Value::Object(block)])
139 }
140 System::Blocks(v) => match v.as_array() {
141 Some(arr) => Value::Array(arr.clone()),
142 None => Value::Array(Vec::new()),
143 },
144 }
145}
146
147fn write_canonical(value: &Value, out: &mut String) {
148 match value {
149 Value::Null => out.push_str("null"),
150 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
151 Value::Number(n) => out.push_str(&n.to_string()),
152 Value::String(s) => write_json_string(s, out),
153 Value::Array(arr) => {
154 out.push('[');
155 for (i, item) in arr.iter().enumerate() {
156 if i > 0 {
157 out.push(',');
158 }
159 write_canonical(item, out);
160 }
161 out.push(']');
162 }
163 Value::Object(map) => {
164 let mut keys: Vec<&String> = map.keys().collect();
165 keys.sort();
166 out.push('{');
167 for (i, k) in keys.iter().enumerate() {
168 if i > 0 {
169 out.push(',');
170 }
171 write_json_string(k, out);
172 out.push(':');
173 write_canonical(&map[*k], out);
174 }
175 out.push('}');
176 }
177 }
178}
179
180fn write_json_string(s: &str, out: &mut String) {
181 out.push('"');
184 for c in s.chars() {
185 match c {
186 '"' => out.push_str("\\\""),
187 '\\' => out.push_str("\\\\"),
188 '\x08' => out.push_str("\\b"),
189 '\x0c' => out.push_str("\\f"),
190 '\n' => out.push_str("\\n"),
191 '\r' => out.push_str("\\r"),
192 '\t' => out.push_str("\\t"),
193 c if (c as u32) < 0x20 => {
194 let code = c as u32;
195 out.push_str(&format!("\\u{code:04x}"));
196 }
197 c => out.push(c),
198 }
199 }
200 out.push('"');
201}
202
203fn hex_lower(bytes: &[u8]) -> String {
204 const HEX: &[u8; 16] = b"0123456789abcdef";
205 let mut s = String::with_capacity(bytes.len() * 2);
206 for &b in bytes {
207 s.push(HEX[(b >> 4) as usize] as char);
208 s.push(HEX[(b & 0x0f) as usize] as char);
209 }
210 s
211}