use serde::{Deserialize, Serialize};
use crate::session::context_block::{
ContextBlockPriority, ContextBlockStability, ContextBlockType,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheControl {
#[serde(rename = "type", default = "default_cache_type")]
pub cache_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ttl: Option<String>,
}
fn default_cache_type() -> String {
"ephemeral".to_string()
}
impl Default for CacheControl {
fn default() -> Self {
Self {
cache_type: default_cache_type(),
ttl: None,
}
}
}
impl CacheControl {
pub fn ephemeral() -> Self {
Self::default()
}
pub fn ephemeral_with_ttl(ttl: impl Into<String>) -> Self {
Self {
cache_type: default_cache_type(),
ttl: Some(ttl.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PromptBlock {
pub id: String,
pub kind: ContextBlockType,
pub text: String,
pub stability: ContextBlockStability,
pub priority: ContextBlockPriority,
#[serde(default)]
pub cache_anchor: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
impl PromptBlock {
pub fn new(id: impl Into<String>, kind: ContextBlockType, text: impl Into<String>) -> Self {
Self {
id: id.into(),
kind,
text: text.into(),
stability: ContextBlockStability::SessionStable,
priority: ContextBlockPriority::Medium,
cache_anchor: false,
metadata: None,
}
}
pub fn with_stability(mut self, stability: ContextBlockStability) -> Self {
self.stability = stability;
self
}
pub fn with_priority(mut self, priority: ContextBlockPriority) -> Self {
self.priority = priority;
self
}
pub fn as_cache_anchor(mut self) -> Self {
self.cache_anchor = true;
self
}
pub fn with_metadata(mut self, metadata: Option<serde_json::Value>) -> Self {
self.metadata = metadata;
self
}
pub fn is_empty(&self) -> bool {
self.text.trim().is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prompt_block_builder_defaults_and_overrides() {
let b = PromptBlock::new("base", ContextBlockType::Base, "You are Bodhi.")
.with_stability(ContextBlockStability::Stable)
.with_priority(ContextBlockPriority::High)
.as_cache_anchor();
assert_eq!(b.id, "base");
assert_eq!(b.kind, ContextBlockType::Base);
assert_eq!(b.stability, ContextBlockStability::Stable);
assert!(b.cache_anchor);
assert!(!b.is_empty());
}
#[test]
fn prompt_block_serde_round_trip() {
let b =
PromptBlock::new("env", ContextBlockType::EnvSnapshot, "os=darwin").as_cache_anchor();
let json = serde_json::to_string(&b).unwrap();
let back: PromptBlock = serde_json::from_str(&json).unwrap();
assert_eq!(b, back);
}
#[test]
fn cache_control_wire_shape() {
let cc = CacheControl::ephemeral_with_ttl("1h");
let json = serde_json::to_value(&cc).unwrap();
assert_eq!(json["type"], "ephemeral");
assert_eq!(json["ttl"], "1h");
let plain = serde_json::to_value(CacheControl::ephemeral()).unwrap();
assert_eq!(plain["type"], "ephemeral");
assert!(plain.get("ttl").is_none());
}
}