refactor: consolidate hypotheses, agent_signals, and process_logs into action-based smart tools
This commit is contained in:
1 parent
79209da711
commit
3b146f91c2
17 files changed
+438
-359
No files matched your search
+88
-74
@@ -1,90 +1,81 @@
|
||||
use crate::router::McpTool;
|
||||
use crate::state::MemoryState;
|
||||
use crate::tools::{GetRecentLogsTool, WatchProcessLogsTool};
|
||||
use crate::tools::{ProcessLogAction, ProcessLogsTool};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct WatchProcessLogsHandler;
|
||||
pub struct ProcessLogsHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for WatchProcessLogsHandler {
|
||||
impl McpTool for ProcessLogsHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"watch_process_logs"
|
||||
"process_logs"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<WatchProcessLogsTool>(
|
||||
"watch_process_logs",
|
||||
"Tail a specific log file in the background.",
|
||||
crate::mcp::tool_def::<ProcessLogsTool>(
|
||||
"process_logs",
|
||||
"Monitor, tail, and manage process logs: watch a log file, tail recent output, or clear log files.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: WatchProcessLogsTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let tool_args: ProcessLogsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let safe_path = crate::handlers::utils::validate_safe_path(&tool_args.file_path)?;
|
||||
if !safe_path.exists() {
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"File does not exist: {}",
|
||||
tool_args.file_path
|
||||
)));
|
||||
|
||||
match tool_args.action {
|
||||
ProcessLogAction::Watch => {
|
||||
if !safe_path.exists() {
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"File does not exist: {}",
|
||||
tool_args.file_path
|
||||
)));
|
||||
}
|
||||
Ok(format!("Started watching logs for {}", tool_args.file_path))
|
||||
}
|
||||
ProcessLogAction::Get => {
|
||||
let max_lines = tool_args.max_lines.unwrap_or(100);
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let mut file = File::open(&safe_path).map_err(|e| {
|
||||
crate::error::AppError::Internal(format!("Failed to open file: {}", e))
|
||||
})?;
|
||||
let len = file.metadata().map_err(|e| e.to_string())?.len();
|
||||
|
||||
let read_size = std::cmp::min(16 * 1024, len);
|
||||
file.seek(SeekFrom::End(-(read_size as i64)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut vec_buf = Vec::new();
|
||||
file.read_to_end(&mut vec_buf)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let buffer = String::from_utf8_lossy(&vec_buf).to_string();
|
||||
let lines: Vec<&str> = buffer.lines().collect();
|
||||
let recent_lines = if lines.len() > max_lines {
|
||||
lines[lines.len() - max_lines..].join("\n")
|
||||
} else {
|
||||
buffer
|
||||
};
|
||||
|
||||
Ok(recent_lines)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
ProcessLogAction::Clear => {
|
||||
if safe_path.exists() {
|
||||
std::fs::write(&safe_path, "").map_err(|e| {
|
||||
crate::error::AppError::Internal(format!("Failed to clear log file: {}", e))
|
||||
})?;
|
||||
}
|
||||
Ok(format!("Cleared logs in {}", tool_args.file_path))
|
||||
}
|
||||
}
|
||||
Ok(format!("Started watching logs for {}", tool_args.file_path))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetRecentLogsHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for GetRecentLogsHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"get_recent_logs"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<GetRecentLogsTool>(
|
||||
"get_recent_logs",
|
||||
"Get the recent logs (last 100 lines) from a watched file.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: GetRecentLogsTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let safe_path = crate::handlers::utils::validate_safe_path(&tool_args.file_path)?;
|
||||
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let mut file = File::open(&safe_path).map_err(|e| {
|
||||
crate::error::AppError::Internal(format!("Failed to open file: {}", e))
|
||||
})?;
|
||||
let len = file.metadata().map_err(|e| e.to_string())?.len();
|
||||
|
||||
let read_size = std::cmp::min(16 * 1024, len);
|
||||
file.seek(SeekFrom::End(-(read_size as i64)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut vec_buf = Vec::new();
|
||||
file.read_to_end(&mut vec_buf)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let buffer = String::from_utf8_lossy(&vec_buf).to_string();
|
||||
|
||||
let lines: Vec<&str> = buffer.lines().collect();
|
||||
let recent_lines = if lines.len() > 100 {
|
||||
lines[lines.len() - 100..].join("\n")
|
||||
} else {
|
||||
buffer
|
||||
};
|
||||
|
||||
Ok(recent_lines)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +87,16 @@ mod tests {
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_watch_process_logs() {
|
||||
async fn test_process_logs_watch() {
|
||||
let dir = tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
let handler = WatchProcessLogsHandler;
|
||||
let handler = ProcessLogsHandler;
|
||||
|
||||
let log_file = dir.path().join("test.log");
|
||||
std::fs::write(&log_file, "line1\nline2").unwrap();
|
||||
|
||||
let args = json!({
|
||||
"action": "watch",
|
||||
"file_path": log_file.to_str().unwrap()
|
||||
});
|
||||
|
||||
@@ -117,15 +109,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_recent_logs() {
|
||||
async fn test_process_logs_get() {
|
||||
let dir = tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
let handler = GetRecentLogsHandler;
|
||||
let handler = ProcessLogsHandler;
|
||||
|
||||
let log_file = dir.path().join("test_recent.log");
|
||||
std::fs::write(&log_file, "line1\nline2\nline3").unwrap();
|
||||
|
||||
let args = json!({
|
||||
"action": "get",
|
||||
"file_path": log_file.to_str().unwrap()
|
||||
});
|
||||
|
||||
@@ -139,10 +132,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_recent_logs_with_large_file() {
|
||||
async fn test_process_logs_clear() {
|
||||
let dir = tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
let handler = ProcessLogsHandler;
|
||||
|
||||
let log_file = dir.path().join("test_clear.log");
|
||||
std::fs::write(&log_file, "line1\nline2\nline3").unwrap();
|
||||
|
||||
let args = json!({
|
||||
"action": "clear",
|
||||
"file_path": log_file.to_str().unwrap()
|
||||
});
|
||||
|
||||
let result = handler.execute(args, state.clone()).await.unwrap();
|
||||
assert!(result.contains("Cleared logs"));
|
||||
|
||||
let content = std::fs::read_to_string(&log_file).unwrap();
|
||||
assert_eq!(content, "");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_logs_get_with_large_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
let handler = GetRecentLogsHandler;
|
||||
let handler = ProcessLogsHandler;
|
||||
|
||||
let log_file = dir.path().join("large_test.log");
|
||||
let mut buffer = String::new();
|
||||
@@ -152,6 +166,7 @@ mod tests {
|
||||
std::fs::write(&log_file, buffer).unwrap();
|
||||
|
||||
let args = serde_json::json!({
|
||||
"action": "get",
|
||||
"file_path": log_file.to_str().unwrap()
|
||||
});
|
||||
|
||||
@@ -163,4 +178,3 @@ mod tests {
|
||||
assert!(result.contains("line"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user