#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CachePolicy {
#[default]
None,
Ephemeral5m,
Ephemeral1h,
}
impl CachePolicy {
pub fn is_cached(self) -> bool {
!matches!(self, CachePolicy::None)
}
}
#[derive(Debug, Clone)]
pub struct PromptBlock {
pub label: &'static str,
pub text: String,
pub cache: CachePolicy,
}
impl PromptBlock {
pub fn plain(label: &'static str, text: impl Into<String>) -> Self {
Self {
label,
text: text.into(),
cache: CachePolicy::None,
}
}
pub fn cached_long(label: &'static str, text: impl Into<String>) -> Self {
Self {
label,
text: text.into(),
cache: CachePolicy::Ephemeral1h,
}
}
pub fn cached_short(label: &'static str, text: impl Into<String>) -> Self {
Self {
label,
text: text.into(),
cache: CachePolicy::Ephemeral5m,
}
}
}
pub fn flatten_blocks(blocks: &[PromptBlock]) -> String {
let mut out = String::new();
let mut first = true;
for b in blocks {
if b.text.is_empty() {
continue;
}
if !first {
out.push_str("\n\n");
}
out.push_str(&b.text);
first = false;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_policy_default_is_none() {
assert_eq!(CachePolicy::default(), CachePolicy::None);
assert!(!CachePolicy::default().is_cached());
assert!(CachePolicy::Ephemeral5m.is_cached());
assert!(CachePolicy::Ephemeral1h.is_cached());
}
#[test]
fn flatten_drops_empty_blocks_and_joins_with_double_newline() {
let blocks = vec![
PromptBlock::cached_long("a", "hello"),
PromptBlock::plain("b", ""),
PromptBlock::cached_short("c", "world"),
];
assert_eq!(flatten_blocks(&blocks), "hello\n\nworld");
}
#[test]
fn flatten_empty_list_yields_empty_string() {
assert_eq!(flatten_blocks(&[]), "");
}
#[test]
fn convenience_constructors_set_expected_policy() {
assert_eq!(PromptBlock::plain("x", "y").cache, CachePolicy::None);
assert_eq!(
PromptBlock::cached_long("x", "y").cache,
CachePolicy::Ephemeral1h
);
assert_eq!(
PromptBlock::cached_short("x", "y").cache,
CachePolicy::Ephemeral5m
);
}
}