use std::collections::HashSet;
use serde_json::Value;
use crate::llmtrim::ir::Request;
use crate::llmtrim::provider::Provider;
pub fn compressible_pointers(req: &Request, provider: &dyn Provider) -> Vec<String> {
let frozen = frozen_pointers(req, provider);
provider
.content_text_pointers(req)
.into_iter()
.filter(|p| !frozen.contains(p) && !is_instruction(req, provider, p))
.collect()
}
fn is_instruction(req: &Request, provider: &dyn Provider, pointer: &str) -> bool {
pointer.starts_with("/system")
|| pointer.starts_with("/instructions")
|| provider.role_at(req, pointer) == Some(crate::llmtrim::provider::Role::System)
}
pub fn frozen_pointers(req: &Request, provider: &dyn Provider) -> HashSet<String> {
let raw = req.raw();
let automatic = has_automatic_cache_marker(req);
let system_frozen = automatic || raw.get("system").is_some_and(has_cache_control);
let messages = raw.get("messages").and_then(Value::as_array);
let marked_until = messages.and_then(|msgs| {
msgs.iter()
.enumerate()
.filter(|(_, m)| has_cache_control(m))
.map(|(i, _)| i)
.max()
});
let automatic_until = automatic
.then(|| messages.map(Vec::len).filter(|len| *len >= 2))
.flatten()
.map(|len| len - 2);
let frozen_until = marked_until.max(automatic_until);
if frozen_until.is_none() && !system_frozen {
return HashSet::new();
}
provider
.content_text_pointers(req)
.into_iter()
.filter(|p| is_frozen(p, frozen_until, system_frozen))
.collect()
}
fn has_automatic_cache_marker(req: &Request) -> bool {
let raw = req.raw();
if raw.get("cache_control").is_some_and(|c| !c.is_null()) {
return true;
}
req.cache_stage_enabled() && routes_to_anthropic(raw)
}
pub(crate) fn router_model(raw: &Value) -> Option<(&str, &str)> {
raw.get("model")
.and_then(Value::as_str)
.map(|m| m.trim_start_matches(['~', '@']))
.and_then(|m| m.split_once('/'))
}
pub(crate) fn routes_to_anthropic(raw: &Value) -> bool {
router_model(raw).is_some_and(|(vendor, _)| vendor.eq_ignore_ascii_case("anthropic"))
}
pub(crate) fn has_cache_control(v: &Value) -> bool {
match v {
Value::Object(m) => m.contains_key("cache_control") || m.values().any(has_cache_control),
Value::Array(a) => a.iter().any(has_cache_control),
_ => false,
}
}
fn is_frozen(ptr: &str, frozen_until: Option<usize>, system_frozen: bool) -> bool {
if let Some(rest) = ptr.strip_prefix("/system") {
return system_frozen && (rest.is_empty() || rest.starts_with('/'));
}
if let Some(rest) = ptr.strip_prefix("/messages/") {
let idx = rest
.split('/')
.next()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(usize::MAX);
return frozen_until.is_some_and(|until| idx <= until);
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::llmtrim::ir::ProviderKind;
use crate::llmtrim::provider::for_kind;
use serde_json::json;
fn req(v: Value) -> Request {
Request::from_value(ProviderKind::Anthropic, v)
}
#[test]
fn instructions_are_never_compressible_even_uncached() {
let r = req(json!({
"system": [
{"type": "text", "text": "Good (Korean session): {\"title\": \"결제 모듈 리팩토링\"}"},
],
"messages": [{"role": "user", "content": "summarise this session"}],
}));
let p = for_kind(ProviderKind::Anthropic);
assert!(
frozen_pointers(&r, p.as_ref()).is_empty(),
"no cache_control ⇒ nothing frozen"
);
let c = compressible_pointers(&r, p.as_ref());
assert!(
!c.iter().any(|p| p.starts_with("/system")),
"system stays out of reach: {c:?}"
);
assert!(
c.iter().any(|p| p.starts_with("/messages")),
"the session content is still compressible: {c:?}"
);
let r = req(json!({
"system": "Return JSON with a single \"title\" field.",
"messages": [
{"role": "system", "content": "never fold me"},
{"role": "user", "content": "but fold me"},
],
}));
let c = compressible_pointers(&r, p.as_ref());
assert_eq!(c, vec!["/messages/1/content".to_string()], "got {c:?}");
}
#[test]
fn no_markers_means_everything_compressible() {
let r = req(json!({
"messages": [
{"role": "user", "content": "first turn"},
{"role": "assistant", "content": "ok"},
{"role": "user", "content": "second turn"},
]
}));
let p = for_kind(ProviderKind::Anthropic);
assert!(frozen_pointers(&r, p.as_ref()).is_empty());
assert_eq!(
compressible_pointers(&r, p.as_ref()).len(),
p.content_text_pointers(&r).len(),
"no cache_control → all content compressible"
);
}
#[test]
fn cache_control_freezes_the_prefix_through_the_last_marker() {
let r = req(json!({
"messages": [
{"role": "user", "content": [{"type": "text", "text": "cached A"}]},
{"role": "user", "content": [
{"type": "text", "text": "cached B", "cache_control": {"type": "ephemeral"}}
]},
{"role": "user", "content": [{"type": "text", "text": "live turn"}]},
]
}));
let p = for_kind(ProviderKind::Anthropic);
let comp = compressible_pointers(&r, p.as_ref());
assert!(
comp.iter().all(|x| x.starts_with("/messages/2")),
"only the live turn: {comp:?}"
);
let frozen = frozen_pointers(&r, p.as_ref());
assert!(frozen.contains("/messages/0/content/0/text"));
assert!(frozen.contains("/messages/1/content/0/text"));
}
#[test]
fn cache_controlled_system_is_frozen() {
let r = req(json!({
"system": [{"type": "text", "text": "stable instructions", "cache_control": {"type": "ephemeral"}}],
"messages": [{"role": "user", "content": "ask"}],
}));
let p = for_kind(ProviderKind::Anthropic);
let frozen = frozen_pointers(&r, p.as_ref());
assert!(
frozen.contains("/system/0/text"),
"marked system is frozen: {frozen:?}"
);
assert!(
compressible_pointers(&r, p.as_ref())
.iter()
.any(|x| x.starts_with("/messages/0"))
);
}
#[test]
fn top_level_automatic_marker_freezes_everything_but_the_newest_turn() {
let r = req(json!({
"cache_control": {"type": "ephemeral", "ttl": "1h"},
"messages": [
{"role": "user", "content": [{"type": "text", "text": "turn one"}]},
{"role": "user", "content": [
{"type": "text", "text": "a 10k-line build log from an earlier turn"}
]},
{"role": "user", "content": [{"type": "text", "text": "the new tool result"}]},
]
}));
let p = for_kind(ProviderKind::Anthropic);
let frozen = frozen_pointers(&r, p.as_ref());
assert!(frozen.contains("/messages/0/content/0/text"));
assert!(frozen.contains("/messages/1/content/0/text"));
let comp = compressible_pointers(&r, p.as_ref());
assert!(
comp.iter().all(|x| x.starts_with("/messages/2")),
"only the turn that hasn't been cached yet: {comp:?}"
);
}
#[test]
fn automatic_marker_leaves_the_opening_request_compressible() {
let r = req(json!({
"cache_control": {"type": "ephemeral"},
"messages": [{"role": "user", "content": [{"type": "text", "text": "opening ask"}]}],
}));
let p = for_kind(ProviderKind::Anthropic);
assert!(
compressible_pointers(&r, p.as_ref())
.iter()
.any(|x| x.starts_with("/messages/0")),
"single-message request stays compressible"
);
}
#[test]
fn automatic_marker_freezes_the_system_prompt() {
let r = req(json!({
"cache_control": {"type": "ephemeral", "ttl": "1h"},
"system": [{"type": "text", "text": "stable instructions"}],
"messages": [
{"role": "user", "content": "one"},
{"role": "user", "content": "two"},
],
}));
let p = for_kind(ProviderKind::Anthropic);
assert!(
frozen_pointers(&r, p.as_ref()).contains("/system/0/text"),
"system is inside the automatic prefix"
);
}
#[test]
fn explicit_marker_past_the_automatic_boundary_still_wins() {
let r = req(json!({
"cache_control": {"type": "ephemeral", "ttl": "1h"},
"messages": [
{"role": "user", "content": [{"type": "text", "text": "one"}]},
{"role": "user", "content": [
{"type": "text", "text": "two", "cache_control": {"type": "ephemeral"}}
]},
]
}));
let p = for_kind(ProviderKind::Anthropic);
let frozen = frozen_pointers(&r, p.as_ref());
assert!(frozen.contains("/messages/1/content/0/text"));
assert!(
compressible_pointers(&r, p.as_ref()).is_empty(),
"the explicit marker covers the whole request"
);
}
fn routed_anthropic_request(cache_stage_enabled: bool) -> Request {
let mut r = Request::from_value(
ProviderKind::OpenAi,
json!({
"model": "anthropic/claude-sonnet-4.5",
"messages": [
{"role": "user", "content": "an earlier turn"},
{"role": "assistant", "content": "ok"},
{"role": "user", "content": "the new tool result"},
]
}),
);
r.set_cache_stage_enabled(cache_stage_enabled);
r
}
#[test]
fn routed_anthropic_model_freezes_before_the_marker_exists() {
let r = routed_anthropic_request(true);
let p = for_kind(ProviderKind::OpenAi);
let comp = compressible_pointers(&r, p.as_ref());
assert!(
comp.iter().all(|x| x.starts_with("/messages/2")),
"only the newest turn: {comp:?}"
);
}
#[test]
fn routed_anthropic_does_not_freeze_when_the_cache_stage_is_off() {
let r = routed_anthropic_request(false);
let p = for_kind(ProviderKind::OpenAi);
assert!(
frozen_pointers(&r, p.as_ref()).is_empty(),
"no marker will be written, so there is no cached prefix to protect",
);
let comp = compressible_pointers(&r, p.as_ref());
assert!(
comp.iter().any(|x| x.starts_with("/messages/0")),
"the earlier turns stay compressible: {comp:?}"
);
}
#[test]
fn explicit_top_level_marker_freezes_even_with_the_cache_stage_off() {
let mut r = req(json!({
"cache_control": {"type": "ephemeral", "ttl": "1h"},
"messages": [
{"role": "user", "content": [{"type": "text", "text": "earlier"}]},
{"role": "user", "content": [{"type": "text", "text": "newest"}]},
]
}));
r.set_cache_stage_enabled(false);
let p = for_kind(ProviderKind::Anthropic);
assert!(
frozen_pointers(&r, p.as_ref()).contains("/messages/0/content/0/text"),
"an already-present marker means the upstream really has cached this",
);
}
#[test]
fn implicitly_cached_route_keeps_everything_compressible() {
let r = Request::from_value(
ProviderKind::OpenAi,
json!({
"model": "openai/gpt-5.2",
"messages": [
{"role": "user", "content": "one"},
{"role": "user", "content": "two"},
]
}),
);
let p = for_kind(ProviderKind::OpenAi);
assert!(frozen_pointers(&r, p.as_ref()).is_empty());
}
#[test]
fn null_cache_control_is_not_an_automatic_marker() {
let r = req(json!({
"cache_control": null,
"messages": [
{"role": "user", "content": "one"},
{"role": "user", "content": "two"},
]
}));
let p = for_kind(ProviderKind::Anthropic);
assert!(frozen_pointers(&r, p.as_ref()).is_empty());
}
}