Skip to main content

leviath_core/
cache.rs

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