use std::sync::Arc;
use crate::memory::store::MemoryStore;
use crate::tool::{param_integer, param_string, ToolSpec};
pub fn memory_tools(store: Option<Arc<std::sync::Mutex<MemoryStore>>>) -> Vec<ToolSpec> {
vec![
save_memory_tool(store.clone()),
search_memory_tool(store.clone()),
list_memories_tool(store.clone()),
forget_memory_tool(store),
]
}
fn get_store(store: &Option<Arc<std::sync::Mutex<MemoryStore>>>) -> Result<std::sync::MutexGuard<'_, MemoryStore>, String> {
store
.as_ref()
.and_then(|s| s.lock().ok())
.ok_or_else(|| "Memory is not initialized. Enable memory in config.".into())
}
fn save_memory_tool(store: Option<Arc<std::sync::Mutex<MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[
("target", param_string("'user' for user-specific facts, 'memory' for general knowledge")),
("content", param_string("The fact to remember (be specific and complete)")),
("category", serde_json::json!({"type": "string", "description": "Category grouping", "default": "general"})),
("importance", serde_json::json!({"type": "integer", "description": "How important (1-5)", "default": 3})),
("tags", serde_json::json!({"type": "string", "description": "Comma-separated tags", "default": ""})),
]);
ToolSpec::new("save_memory", "Save a fact to persistent memory so it can be recalled later", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let target = args.get("target").and_then(|v| v.as_str()).unwrap_or("memory");
let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
let category = args.get("category").and_then(|v| v.as_str()).unwrap_or("general");
let importance = args.get("importance").and_then(|v| v.as_i64()).unwrap_or(3).clamp(1, 5) as i32;
let tags_str = args.get("tags").and_then(|v| v.as_str()).unwrap_or("");
let tags: Vec<String> = if tags_str.is_empty() { vec![] } else { tags_str.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect() };
let mid = guard.save_memory(target, content, category, importance, &tags).map_err(|e| format!("Failed to save memory: {}", e))?;
Ok(format!("Saved as memory #{} (target={}, importance={})", mid, target, importance))
}),
)
}
fn search_memory_tool(store: Option<Arc<std::sync::Mutex<MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[
("query", param_string("Search terms to match against memory content and tags")),
("limit", serde_json::json!({"type": "integer", "description": "Max results", "default": 5})),
]);
ToolSpec::new("search_memory", "Search saved memories for relevant facts", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
let limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(5).min(20);
let results = guard.search_memories(query, limit).map_err(|e| format!("Search failed: {}", e))?;
if results.is_empty() { return Ok("No matching memories found.".into()); }
let mut lines = vec!["Matching memories:".to_string()];
for m in &results { lines.push(format!(" #{} [{}/{}] (imp={}) [{}]: {}", m.id, m.target, m.category, m.importance, m.created_at, m.content)); }
Ok(lines.join("\n"))
}),
)
}
fn list_memories_tool(store: Option<Arc<std::sync::Mutex<MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[
("target", serde_json::json!({"type": "string", "description": "'user', 'memory', or empty for all", "default": ""})),
("limit", serde_json::json!({"type": "integer", "description": "Max entries", "default": 10})),
]);
ToolSpec::new("list_memories", "List recent memories, optionally filtered by target", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let target = args.get("target").and_then(|v| v.as_str()).unwrap_or("");
let limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(10).min(50);
let t = if target.is_empty() { None } else { Some(target) };
if let Some(t) = t { if t != "user" && t != "memory" { return Err(format!("Invalid target '{}'. Use 'user', 'memory', or leave empty.", t)); } }
let results = guard.list_memories(t, limit).map_err(|e| format!("List failed: {}", e))?;
if results.is_empty() { return Ok("No memories saved yet.".into()); }
let mut lines = vec![format!("Memories ({}):", if target.is_empty() { "all" } else { target })];
for m in &results {
let tag_str = if m.tags.is_empty() { String::new() } else { format!(" [{}]", m.tags.join(", ")) };
lines.push(format!(" #{} [{}/imp={}] {}{}", m.id, m.category, m.importance, m.content, tag_str));
}
Ok(lines.join("\n"))
}),
)
}
fn forget_memory_tool(store: Option<Arc<std::sync::Mutex<MemoryStore>>>) -> ToolSpec {
let (params, _) = crate::tool::required_params(&[("memory_id", param_integer("The ID of the memory to delete"))]);
ToolSpec::new("forget_memory", "Delete a specific memory by its ID", params,
Arc::new(move |args| {
let guard = get_store(&store)?;
let memory_id = args.get("memory_id").and_then(|v| v.as_i64()).unwrap_or(0);
if guard.delete_memory(memory_id).map_err(|e| format!("Failed to delete: {}", e))? {
Ok(format!("Memory #{} deleted.", memory_id))
} else { Ok(format!("Memory #{} not found.", memory_id)) }
}),
)
}