feat: add save_item_from_mcp functionality to core services

Co-authored-by: aider (openai/andrew/openrouter/google/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-08-25 12:48:10 -03:00
parent da59401ca7
commit afe23aaa40
4 changed files with 128 additions and 92 deletions

View File

@@ -1,16 +1,12 @@
use anyhow::{Result, anyhow};
use serde_json::Value;
use std::collections::HashMap;
use std::io::{Write, Read};
use std::str::FromStr;
use log::{debug, warn};
use crate::modes::server::common::AppState;
use crate::core::async_item_service::AsyncItemService;
use crate::core::error::CoreError;
use crate::db;
use crate::compression_engine::{CompressionType, get_compression_engine};
use crate::meta_plugin::{MetaPluginType, get_meta_plugin};
#[derive(Debug, thiserror::Error)]
pub enum ToolError {
@@ -41,93 +37,50 @@ impl KeepTools {
pub async fn save_item(&self, args: Option<Value>) -> Result<String, ToolError> {
let args = args.ok_or_else(|| ToolError::InvalidArguments("Missing arguments".to_string()))?;
let content = args.get("content")
let content = args
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidArguments("Missing 'content' field".to_string()))?;
let tags: Vec<String> = args.get("tags")
let tags: Vec<String> = args
.get("tags")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
let metadata: HashMap<String, String> = args.get("metadata")
let metadata: HashMap<String, String> = args
.get("metadata")
.and_then(|v| v.as_object())
.map(|obj| obj.iter().filter_map(|(k, v)| {
v.as_str().map(|s| (k.clone(), s.to_string()))
}).collect())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default();
debug!("MCP: Saving item with {} bytes, {} tags, {} metadata entries",
content.len(), tags.len(), metadata.len());
debug!(
"MCP: Saving item with {} bytes, {} tags, {} metadata entries",
content.len(),
tags.len(),
metadata.len()
);
let service = AsyncItemService::new(self.state.data_dir.clone(), self.state.db.clone());
let item_with_meta = service
.save_item_from_mcp(content.as_bytes().to_vec(), tags, metadata)
.await
.map_err(|e| ToolError::Other(anyhow!(e)))?;
let item_id = item_with_meta
.item
.id
.ok_or_else(|| anyhow!("Failed to get item ID"))?;
let mut conn = self.state.db.lock().await;
// Create new item
let item = db::create_item(&mut *conn, CompressionType::LZ4)?;
let item_id = item.id.ok_or_else(|| anyhow!("Failed to get item ID"))?;
// Save content to file
let mut item_path = self.state.data_dir.clone();
item_path.push(item_id.to_string());
let compression_engine = get_compression_engine(CompressionType::LZ4)?;
let mut writer = compression_engine.create(item_path)?;
writer.write_all(content.as_bytes())?;
drop(writer); // Ensure file is closed
// Add tags
for tag in &tags {
db::add_tag(&mut *conn, item_id, tag)?;
}
// Add custom metadata
for (key, value) in &metadata {
db::add_meta(&mut *conn, item_id, key, value)?;
}
// Run metadata plugins
let meta_plugins = vec![
MetaPluginType::FileMime,
MetaPluginType::FileEncoding,
MetaPluginType::Binary,
MetaPluginType::LineCount,
MetaPluginType::WordCount,
MetaPluginType::DigestSha256,
MetaPluginType::Uid,
MetaPluginType::User,
MetaPluginType::Hostname,
];
for plugin_type in meta_plugins {
let plugin_type_clone = plugin_type.clone();
let mut plugin = get_meta_plugin(plugin_type);
if plugin.is_supported() {
if let Err(e) = plugin.initialize(&*conn, item_id) {
warn!("Failed to initialize plugin {:?}: {}", plugin_type_clone, e);
continue;
}
let mut item_path = self.state.data_dir.clone();
item_path.push(item_id.to_string());
// Process the file content through the plugin
let mut item_path = self.state.data_dir.clone();
item_path.push(item_id.to_string());
let compression_engine = get_compression_engine(CompressionType::LZ4)?;
let mut reader = compression_engine.open(item_path)?;
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)?;
plugin.update(&buffer, &*conn);
if let Err(e) = plugin.finalize(&*conn) {
warn!("Failed to finalize plugin {:?}: {}", plugin_type_clone, e);
}
}
}
Ok(format!("Successfully saved item with ID: {}", item_id))
}