// src/agent/tools/astro/research/note.rs // SaveNoteTool — 保存研究笔记 use async_trait::async_trait; use serde_json::json; use tracing::info; use crate::agent::tools::{AgentTool, InterruptBehavior, ToolContext, ToolOutput}; pub struct SaveNoteTool; #[async_trait] impl AgentTool for SaveNoteTool { fn name(&self) -> &str { "save_note" } fn description(&self) -> &str { "将研究中间结果或最终结论保存为 Markdown 笔记文件。适用于记录阅读摘要、研究思路、文献分析等。" } fn parameters(&self) -> serde_json::Value { json!({ "type": "object", "properties": { "title": { "type": "string", "description": "笔记标题,将被用于文件名(特殊字符会被过滤)" }, "content": { "type": "string", "description": "笔记正文内容,支持 Markdown 格式(含 LaTeX 数学公式)" } }, "required": ["title", "content"] }) } fn group(&self) -> &str { "as:research" } fn interrupt_behavior(&self) -> InterruptBehavior { InterruptBehavior::Block } async fn execute(&self, args: serde_json::Value, ctx: &ToolContext) -> ToolOutput { let title = match args.get("title").and_then(|t| t.as_str()) { Some(t) => t.to_string(), None => return ToolOutput::error("缺少必需参数 'title'"), }; let content = match args.get("content").and_then(|c| c.as_str()) { Some(c) => c.to_string(), None => return ToolOutput::error("缺少必需参数 'content'"), }; info!("[SaveNote] 保存笔记: {}", title); let state = &ctx.app_state; // 文件名安全化 let safe_title: String = title .chars() .map(|c| { if c.is_alphanumeric() || c == '-' || c == '_' || c == ' ' { c } else { '_' } }) .collect::() .replace(' ', "_"); let notes_dir = state.config.library_dir.join("notes"); if let Err(e) = std::fs::create_dir_all(¬es_dir) { return ToolOutput::error(format!("创建笔记目录失败: {}", e)); } let filename = format!("{}.md", safe_title); let filepath = notes_dir.join(&filename); let full_content = format!( "---\ntitle: {}\ndate: {}\ngenerated_by: AstroResearch Agent\n---\n\n{}", title, chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), content ); match std::fs::write(&filepath, &full_content) { Ok(_) => ToolOutput::success( format!("笔记已保存: {}", filename), json!({ "filename": filename, "path": filepath.to_string_lossy(), "size": full_content.len() }), ), Err(e) => ToolOutput::error(format!("保存笔记失败: {}", e)), } } }