Skip to main content

xz_skill/cache/
tool_cache.rs

1use std::collections::HashMap;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::sync::RwLock;
4use std::time::{Duration, Instant};
5
6/// Tool result cache with TTL — reduces redundant HTTP/WASM calls.
7#[derive(Debug)]
8pub struct ToolResultCache {
9    entries: RwLock<HashMap<String, CacheEntry>>,
10    ttl: Duration,
11}
12
13#[derive(Debug, Clone)]
14struct CacheEntry {
15    value: serde_json::Value,
16    created_at: Instant,
17}
18
19impl ToolResultCache {
20    pub fn new(ttl_seconds: u64) -> Self {
21        Self {
22            entries: RwLock::new(HashMap::new()),
23            ttl: Duration::from_secs(ttl_seconds),
24        }
25    }
26
27    /// Generate a cache key from tool name + hashed args.
28    pub fn cache_key(tool_name: &str, args: &serde_json::Value) -> String {
29        let mut hasher = DefaultHasher::new();
30        tool_name.hash(&mut hasher);
31        args.to_string().hash(&mut hasher);
32        format!("tool:{}:{:x}", tool_name, hasher.finish())
33    }
34
35    /// Get a cached tool result if not expired.
36    pub fn get(&self, tool_name: &str, args: &serde_json::Value) -> Option<serde_json::Value> {
37        let key = Self::cache_key(tool_name, args);
38        let entries = self.entries.read().unwrap();
39        if let Some(entry) = entries.get(&key) {
40            if entry.created_at.elapsed() < self.ttl {
41                return Some(entry.value.clone());
42            }
43        }
44        None
45    }
46
47    /// Store a tool result in the cache.
48    pub fn set(&self, tool_name: &str, args: &serde_json::Value, value: &serde_json::Value) {
49        let key = Self::cache_key(tool_name, args);
50        let mut entries = self.entries.write().unwrap();
51        entries.insert(
52            key,
53            CacheEntry {
54                value: value.clone(),
55                created_at: Instant::now(),
56            },
57        );
58    }
59
60    /// Remove all expired entries.
61    pub fn evict_expired(&self) {
62        let mut entries = self.entries.write().unwrap();
63        entries.retain(|_, e| e.created_at.elapsed() < self.ttl);
64    }
65
66    /// Clear all cached entries.
67    pub fn clear(&self) {
68        self.entries.write().unwrap().clear();
69    }
70
71    /// Number of cached entries.
72    pub fn len(&self) -> usize {
73        self.entries.read().unwrap().len()
74    }
75
76    /// Whether the cache is empty.
77    pub fn is_empty(&self) -> bool {
78        self.len() == 0
79    }
80}