1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Cache hint types for prompt caching across providers.
//!
//! Context regions are assembled in order of volatility (most stable first).
//! Cache breakpoints are inserted at region boundaries. Providers translate
//! these breakpoints into their native caching APIs.
use serde::{Deserialize, Serialize};
/// Cache hint for a region or message boundary.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CacheHint {
/// Always cache - content never changes (pinned, system, tools, compact history).
Always,
/// Cache until content hash changes (compacting regions between compaction events).
UntilChanged,
/// Same cacheability as [`CacheHint::UntilChanged`], and additionally marks
/// the start of the most recently changed stretch of that tier.
///
/// A provider that spends one cache breakpoint per run of same-hint blocks
/// sees the change of hint as a run boundary, so the blocks ahead of the
/// mutation end up in a cache entry of their own instead of sharing one
/// with the block that just changed. Nothing else about the block differs:
/// assembly sorts it to the same position as `UntilChanged`, and its text
/// is untouched.
RecentlyChanged,
/// Cache the stable prefix of a sliding window.
/// `stable_fraction` is 0.0..1.0 (default 0.75 = oldest 75% of messages are stable).
SlidingPrefix {
/// How much of the window counts as stable, in `0.0..1.0`. The oldest
/// that fraction is cached; the newest tail is not, because it is what
/// changes every turn and would invalidate the whole prefix with it.
stable_fraction: f32,
},
/// Never cache (temporary, clearable, new messages).
Never,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_hint_always_equality() {
assert_eq!(CacheHint::Always, CacheHint::Always);
assert_ne!(CacheHint::Always, CacheHint::Never);
}
#[test]
fn cache_hint_never_equality() {
assert_eq!(CacheHint::Never, CacheHint::Never);
assert_ne!(CacheHint::Never, CacheHint::UntilChanged);
}
#[test]
fn cache_hint_until_changed_equality() {
assert_eq!(CacheHint::UntilChanged, CacheHint::UntilChanged);
}
#[test]
fn cache_hint_recently_changed_is_distinct_from_until_changed() {
assert_eq!(CacheHint::RecentlyChanged, CacheHint::RecentlyChanged);
assert_ne!(CacheHint::RecentlyChanged, CacheHint::UntilChanged);
let dbg = format!("{:?}", CacheHint::RecentlyChanged);
assert!(dbg.contains("RecentlyChanged"));
}
#[test]
fn cache_hint_sliding_prefix_equality() {
let a = CacheHint::SlidingPrefix {
stable_fraction: 0.75,
};
let b = CacheHint::SlidingPrefix {
stable_fraction: 0.75,
};
assert_eq!(a, b);
let c = CacheHint::SlidingPrefix {
stable_fraction: 0.5,
};
assert_ne!(a, c);
}
#[test]
fn cache_hint_clone() {
let hint = CacheHint::SlidingPrefix {
stable_fraction: 0.8,
};
let cloned = hint;
assert_eq!(
cloned,
CacheHint::SlidingPrefix {
stable_fraction: 0.8
}
);
}
#[test]
fn cache_hint_debug() {
let hint = CacheHint::Always;
let dbg = format!("{:?}", hint);
assert!(dbg.contains("Always"));
}
#[test]
fn cache_hint_serde_roundtrip() {
let hints = vec![
CacheHint::Always,
CacheHint::UntilChanged,
CacheHint::RecentlyChanged,
CacheHint::SlidingPrefix {
stable_fraction: 0.75,
},
CacheHint::Never,
];
for hint in hints {
let json = serde_json::to_string(&hint).unwrap();
let parsed: CacheHint = serde_json::from_str(&json).unwrap();
assert_eq!(hint, parsed);
}
}
}