1use async_trait::async_trait;
6use serde::Deserialize;
7use serde_json::Value;
8
9use crate::storage::{
10 deactivate_memory, delete_memory_permanently, insert_memory, list_memories, recall_memories,
11 update_memory, Memory, MemoryFilter, MemoryType,
12};
13use crate::tool::{Tool, ToolContext, ToolResult};
14use crate::error::Result;
15
16#[derive(Debug, Deserialize)]
21struct MemorizeArgs {
22 title: String,
23 content: String,
24 #[serde(default = "default_memory_type")]
25 memory_type: String,
26 #[serde(default)]
27 tags: Vec<String>,
28 #[serde(default)]
29 update_if_exists: bool,
30}
31
32fn default_memory_type() -> String {
33 "note".to_string()
34}
35
36pub struct MemorizeTool;
37
38impl MemorizeTool {
39 pub fn new() -> Self {
40 Self
41 }
42}
43
44impl Default for MemorizeTool {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50#[async_trait]
51impl Tool for MemorizeTool {
52 fn name(&self) -> &str {
53 "memorize"
54 }
55
56 fn description(&self) -> &str {
57 "Store important information in long-term memory. Use for user preferences, key facts, project notes, etc."
58 }
59
60 fn parameters_schema(&self) -> Value {
61 serde_json::json!({
62 "type": "object",
63 "properties": {
64 "title": {
65 "type": "string",
66 "description": "Short, descriptive title for the memory (for easy retrieval)"
67 },
68 "content": {
69 "type": "string",
70 "description": "Full content of the memory"
71 },
72 "memory_type": {
73 "type": "string",
74 "description": "Type of memory: fact, preference, note, task, or custom",
75 "default": "note"
76 },
77 "tags": {
78 "type": "array",
79 "items": { "type": "string" },
80 "description": "Tags for categorization and filtering",
81 "default": []
82 },
83 "update_if_exists": {
84 "type": "boolean",
85 "description": "If true, update existing memory with the same title",
86 "default": false
87 }
88 },
89 "required": ["title", "content"]
90 })
91 }
92
93 fn requires_confirmation(&self) -> bool {
94 false
95 }
96
97 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult> {
98 let parsed: MemorizeArgs = match serde_json::from_value(args) {
99 Ok(a) => a,
100 Err(e) => return Ok(ToolResult::error(format!("Argument parsing failed: {}", e))),
101 };
102
103 if parsed.title.trim().is_empty() {
104 return Ok(ToolResult::error("Title cannot be empty".to_string()));
105 }
106 if parsed.content.trim().is_empty() {
107 return Ok(ToolResult::error("Content cannot be empty".to_string()));
108 }
109
110 let db_path = match crate::storage::resolve_db_path(&ctx.working_dir, false) {
111 Ok(p) => p,
112 Err(e) => return Ok(ToolResult::error(format!("Failed to resolve DB path: {}", e))),
113 };
114
115 let conn = match rusqlite::Connection::open(&db_path) {
116 Ok(c) => c,
117 Err(e) => return Ok(ToolResult::error(format!("Failed to open DB: {}", e))),
118 };
119
120 let memory_type = MemoryType::from_str(&parsed.memory_type);
121 let mut memory = Memory::new(
122 parsed.title.clone(),
123 parsed.content.clone(),
124 memory_type,
125 parsed.tags.clone(),
126 );
127
128 if ctx.session_id.to_string() != "" {
129 memory = memory.with_session_id(ctx.session_id.to_string());
130 }
131
132 if parsed.update_if_exists {
133 let filter = MemoryFilter {
135 session_id: Some(ctx.session_id.to_string()),
136 only_active: true,
137 ..Default::default()
138 };
139 if let Ok(existing) = list_memories(&conn, &filter, Some(100)) {
140 if let Some(mut existing) = existing.into_iter().find(|m| m.title == parsed.title) {
141 existing.content = parsed.content;
142 existing.memory_type = MemoryType::from_str(&parsed.memory_type);
143 existing.tags = parsed.tags;
144 if let Err(e) = update_memory(&conn, &existing) {
145 return Ok(ToolResult::error(format!("Failed to update memory: {}", e)));
146 }
147 return Ok(ToolResult::success(format!(
148 "Updated memory: {} (ID: {})",
149 existing.title, existing.id
150 )));
151 }
152 }
153 }
154
155 if let Err(e) = insert_memory(&conn, &memory) {
157 return Ok(ToolResult::error(format!("Failed to store memory: {}", e)));
158 }
159
160 Ok(ToolResult::success(format!(
161 "Stored memory: {} (ID: {})",
162 memory.title, memory.id
163 )))
164 }
165}
166
167#[derive(Debug, Deserialize)]
172struct RecallArgs {
173 query: Option<String>,
174 memory_type: Option<String>,
175 tags: Option<Vec<String>>,
176 limit: Option<usize>,
177 since: Option<String>,
178 session_id: Option<String>,
179 chat_id: Option<String>,
180}
181
182pub struct RecallTool;
183
184impl RecallTool {
185 pub fn new() -> Self {
186 Self
187 }
188}
189
190impl Default for RecallTool {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196#[async_trait]
197impl Tool for RecallTool {
198 fn name(&self) -> &str {
199 "recall"
200 }
201
202 fn description(&self) -> &str {
203 "Retrieve relevant memories from long-term memory. Use when you need context or past information."
204 }
205
206 fn parameters_schema(&self) -> Value {
207 serde_json::json!({
208 "type": "object",
209 "properties": {
210 "query": {
211 "type": "string",
212 "description": "Keywords to search in title, content, and tags"
213 },
214 "memory_type": {
215 "type": "string",
216 "description": "Filter by memory type: fact, preference, note, task"
217 },
218 "tags": {
219 "type": "array",
220 "items": { "type": "string" },
221 "description": "Filter by tags (any match)"
222 },
223 "limit": {
224 "type": "integer",
225 "description": "Maximum number of memories to return",
226 "default": 10
227 },
228 "since": {
229 "type": "string",
230 "description": "Only return memories created after this ISO 8601 timestamp"
231 },
232 "session_id": {
233 "type": "string",
234 "description": "Filter by session ID"
235 },
236 "chat_id": {
237 "type": "string",
238 "description": "Filter by chat ID (Bot platforms)"
239 }
240 }
241 })
242 }
243
244 fn requires_confirmation(&self) -> bool {
245 false
246 }
247
248 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult> {
249 let parsed: RecallArgs = match serde_json::from_value(args) {
250 Ok(a) => a,
251 Err(e) => return Ok(ToolResult::error(format!("Argument parsing failed: {}", e))),
252 };
253
254 let db_path = match crate::storage::resolve_db_path(&ctx.working_dir, false) {
255 Ok(p) => p,
256 Err(e) => return Ok(ToolResult::error(format!("Failed to resolve DB path: {}", e))),
257 };
258
259 let conn = match rusqlite::Connection::open(&db_path) {
260 Ok(c) => c,
261 Err(e) => return Ok(ToolResult::error(format!("Failed to open DB: {}", e))),
262 };
263
264 let filter = MemoryFilter {
265 memory_type: parsed.memory_type.as_ref().map(|s| MemoryType::from_str(s)),
266 tags: parsed.tags,
267 session_id: parsed.session_id.or(Some(ctx.session_id.to_string())),
268 chat_id: parsed.chat_id,
269 since: parsed.since,
270 only_active: true,
271 };
272
273 let limit = parsed.limit.unwrap_or(10);
274
275 let memories = if let Some(query) = &parsed.query {
276 if query.trim().is_empty() {
277 list_memories(&conn, &filter, Some(limit))
278 } else {
279 recall_memories(&conn, query, &filter, limit)
280 }
281 } else {
282 list_memories(&conn, &filter, Some(limit))
283 };
284
285 match memories {
286 Ok(memories) if memories.is_empty() => Ok(ToolResult::success(
287 "No memories found matching the criteria.".to_string(),
288 )),
289 Ok(memories) => {
290 let mut result = format!("Found {} memories:\n\n", memories.len());
291 for (i, memory) in memories.iter().enumerate() {
292 result.push_str(&format!(
293 "{}. [{}] {}\n {}\n Tags: {}\n Created: {}\n\n",
294 i + 1,
295 memory.memory_type.as_str(),
296 memory.title,
297 memory.content,
298 if memory.tags.is_empty() {
299 "(none)".to_string()
300 } else {
301 memory.tags.join(", ")
302 },
303 memory.created_at
304 ));
305 }
306 Ok(ToolResult::success(result))
307 }
308 Err(e) => Ok(ToolResult::error(format!("Failed to recall memories: {}", e))),
309 }
310 }
311}
312
313#[derive(Debug, Deserialize)]
318struct ForgetArgs {
319 memory_id: Option<String>,
320 title: Option<String>,
321 #[serde(default)]
322 permanent: bool,
323}
324
325pub struct ForgetTool;
326
327impl ForgetTool {
328 pub fn new() -> Self {
329 Self
330 }
331}
332
333impl Default for ForgetTool {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339#[async_trait]
340impl Tool for ForgetTool {
341 fn name(&self) -> &str {
342 "forget"
343 }
344
345 fn description(&self) -> &str {
346 "Remove or deactivate memories you no longer need."
347 }
348
349 fn parameters_schema(&self) -> Value {
350 serde_json::json!({
351 "type": "object",
352 "properties": {
353 "memory_id": {
354 "type": "string",
355 "description": "Specific memory ID to remove"
356 },
357 "title": {
358 "type": "string",
359 "description": "Remove memories matching this title"
360 },
361 "permanent": {
362 "type": "boolean",
363 "description": "If true, permanently delete. If false, just deactivate",
364 "default": false
365 }
366 }
367 })
368 }
369
370 fn requires_confirmation(&self) -> bool {
371 true
372 }
373
374 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult> {
375 let parsed: ForgetArgs = match serde_json::from_value(args) {
376 Ok(a) => a,
377 Err(e) => return Ok(ToolResult::error(format!("Argument parsing failed: {}", e))),
378 };
379
380 if parsed.memory_id.is_none() && parsed.title.is_none() {
381 return Ok(ToolResult::error(
382 "Either memory_id or title must be provided".to_string(),
383 ));
384 }
385
386 let db_path = match crate::storage::resolve_db_path(&ctx.working_dir, false) {
387 Ok(p) => p,
388 Err(e) => return Ok(ToolResult::error(format!("Failed to resolve DB path: {}", e))),
389 };
390
391 let conn = match rusqlite::Connection::open(&db_path) {
392 Ok(c) => c,
393 Err(e) => return Ok(ToolResult::error(format!("Failed to open DB: {}", e))),
394 };
395
396 if let Some(memory_id) = parsed.memory_id {
397 let result = if parsed.permanent {
398 delete_memory_permanently(&conn, &memory_id)
399 } else {
400 deactivate_memory(&conn, &memory_id)
401 };
402
403 match result {
404 Ok(_) => Ok(ToolResult::success(format!(
405 "Memory {} has been {}.",
406 memory_id,
407 if parsed.permanent {
408 "permanently deleted"
409 } else {
410 "deactivated"
411 }
412 ))),
413 Err(e) => Ok(ToolResult::error(format!("Failed to remove memory: {}", e))),
414 }
415 } else if let Some(title) = parsed.title {
416 let filter = MemoryFilter {
417 session_id: Some(ctx.session_id.to_string()),
418 only_active: true,
419 ..Default::default()
420 };
421 match list_memories(&conn, &filter, Some(100)) {
422 Ok(memories) => {
423 let matching: Vec<_> = memories.into_iter().filter(|m| m.title == title).collect();
424 if matching.is_empty() {
425 return Ok(ToolResult::error(format!("No memory found with title: {}", title)));
426 }
427
428 let mut removed = 0;
429 for memory in &matching {
430 let result = if parsed.permanent {
431 delete_memory_permanently(&conn, &memory.id)
432 } else {
433 deactivate_memory(&conn, &memory.id)
434 };
435 if result.is_ok() {
436 removed += 1;
437 }
438 }
439
440 Ok(ToolResult::success(format!(
441 "Removed {} memory{} with title: {}",
442 removed,
443 if removed == 1 { "" } else { "s" },
444 title
445 )))
446 }
447 Err(e) => Ok(ToolResult::error(format!("Failed to find memories: {}", e))),
448 }
449 } else {
450 Ok(ToolResult::error(
451 "Internal error: neither memory_id nor title".to_string(),
452 ))
453 }
454 }
455}
456
457#[derive(Debug, Deserialize)]
462struct ListMemoriesArgs {
463 limit: Option<usize>,
464 memory_type: Option<String>,
465}
466
467pub struct ListMemoriesTool;
468
469impl ListMemoriesTool {
470 pub fn new() -> Self {
471 Self
472 }
473}
474
475impl Default for ListMemoriesTool {
476 fn default() -> Self {
477 Self::new()
478 }
479}
480
481#[async_trait]
482impl Tool for ListMemoriesTool {
483 fn name(&self) -> &str {
484 "list_memories"
485 }
486
487 fn description(&self) -> &str {
488 "List all active memories for a quick overview."
489 }
490
491 fn parameters_schema(&self) -> Value {
492 serde_json::json!({
493 "type": "object",
494 "properties": {
495 "limit": {
496 "type": "integer",
497 "description": "Maximum number of memories to list",
498 "default": 20
499 },
500 "memory_type": {
501 "type": "string",
502 "description": "Filter by memory type: fact, preference, note, task"
503 }
504 }
505 })
506 }
507
508 fn requires_confirmation(&self) -> bool {
509 false
510 }
511
512 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult> {
513 let parsed: ListMemoriesArgs = match serde_json::from_value(args) {
514 Ok(a) => a,
515 Err(e) => return Ok(ToolResult::error(format!("Argument parsing failed: {}", e))),
516 };
517
518 let db_path = match crate::storage::resolve_db_path(&ctx.working_dir, false) {
519 Ok(p) => p,
520 Err(e) => return Ok(ToolResult::error(format!("Failed to resolve DB path: {}", e))),
521 };
522
523 let conn = match rusqlite::Connection::open(&db_path) {
524 Ok(c) => c,
525 Err(e) => return Ok(ToolResult::error(format!("Failed to open DB: {}", e))),
526 };
527
528 let filter = MemoryFilter {
529 memory_type: parsed.memory_type.as_ref().map(|s| MemoryType::from_str(s)),
530 session_id: Some(ctx.session_id.to_string()),
531 only_active: true,
532 ..Default::default()
533 };
534
535 match list_memories(&conn, &filter, parsed.limit) {
536 Ok(memories) if memories.is_empty() => Ok(ToolResult::success(
537 "No active memories yet. Use `memorize` to store something!".to_string(),
538 )),
539 Ok(memories) => {
540 let mut result = format!("Your memories ({} total):\n\n", memories.len());
541 for (i, memory) in memories.iter().enumerate() {
542 result.push_str(&format!(
543 "{}. [{}] {}\n ID: {}\n Tags: {}\n\n",
544 i + 1,
545 memory.memory_type.as_str(),
546 memory.title,
547 memory.id,
548 if memory.tags.is_empty() {
549 "(none)".to_string()
550 } else {
551 memory.tags.join(", ")
552 }
553 ));
554 }
555 Ok(ToolResult::success(result))
556 }
557 Err(e) => Ok(ToolResult::error(format!("Failed to list memories: {}", e))),
558 }
559 }
560}