microclaw_engine/internal/tool_runtime/
tool_cache.rs1use std::collections::HashMap;
8use std::time::Duration;
9
10use sha2::{Digest, Sha256};
11
12pub fn default_ttls() -> HashMap<&'static str, Duration> {
16 let mut m = HashMap::new();
17 m.insert("web_fetch", Duration::from_secs(15 * 60));
18 m.insert("web_search", Duration::from_secs(10 * 60));
19 m.insert("osv_check", Duration::from_secs(60 * 60));
20 m.insert("describe_image", Duration::from_secs(60 * 60));
21 m.insert("session_search", Duration::from_secs(60));
22 m
23}
24
25pub fn normalize_input_for_key(input: &serde_json::Value) -> serde_json::Value {
30 match input {
31 serde_json::Value::Object(map) => {
32 let mut keys: Vec<_> = map
33 .keys()
34 .filter(|k| !k.starts_with("__microclaw_"))
35 .cloned()
36 .collect();
37 keys.sort();
38 let mut out = serde_json::Map::new();
39 for k in keys {
40 if let Some(v) = map.get(&k) {
41 out.insert(k, normalize_input_for_key(v));
42 }
43 }
44 serde_json::Value::Object(out)
45 }
46 serde_json::Value::Array(arr) => {
47 serde_json::Value::Array(arr.iter().map(normalize_input_for_key).collect())
48 }
49 other => other.clone(),
50 }
51}
52
53pub fn cache_key(tool_name: &str, input: &serde_json::Value) -> String {
56 let canonical = serde_json::to_string(&normalize_input_for_key(input)).unwrap_or_default();
57 let mut hasher = Sha256::new();
58 hasher.update(tool_name.as_bytes());
59 hasher.update(b":");
60 hasher.update(canonical.as_bytes());
61 format!("{:x}", hasher.finalize())
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use serde_json::json;
68
69 #[test]
70 fn object_key_order_is_irrelevant() {
71 let a = json!({"b": 1, "a": 2});
72 let b = json!({"a": 2, "b": 1});
73 assert_eq!(cache_key("t", &a), cache_key("t", &b));
74 }
75
76 #[test]
77 fn auth_context_is_stripped() {
78 let with_auth = json!({"q": "x", "__microclaw_auth": {"caller_chat_id": 1}});
79 let without = json!({"q": "x"});
80 assert_eq!(cache_key("t", &with_auth), cache_key("t", &without));
81 }
82
83 #[test]
84 fn different_tools_collide_differently() {
85 let v = json!({"q": "x"});
86 assert_ne!(cache_key("a", &v), cache_key("b", &v));
87 }
88
89 #[test]
90 fn array_order_matters() {
91 let a = json!([1, 2, 3]);
92 let b = json!([3, 2, 1]);
93 assert_ne!(cache_key("t", &a), cache_key("t", &b));
94 }
95}