Skip to main content

engram_core/
insights_handler.rs

1use std::sync::Arc;
2
3use serde::Deserialize;
4use serde_json::{Value, json};
5
6use engram_storage::Memory;
7
8use crate::error::CoreError;
9use crate::export_handler::memory_to_portable_json;
10use crate::server::ServerState;
11
12#[derive(Deserialize)]
13struct InsightsParams {
14    action: String,
15    id: Option<String>,
16}
17
18pub async fn handle(state: &Arc<ServerState>, params: Value) -> Result<Value, CoreError> {
19    let parsed: InsightsParams = serde_json::from_value(params)
20        .map_err(|error| CoreError::DispatchError(error.to_string()))?;
21    match parsed.action.as_str() {
22        "list" => handle_list(state).await,
23        "generate" => Err(CoreError::DispatchError(
24            "insight generation requires engram-trainer (Phase 5)".into(),
25        )),
26        "delete" => handle_delete(state, parsed.id).await,
27        other => Err(CoreError::DispatchError(format!(
28            "invalid insights action: {other}"
29        ))),
30    }
31}
32
33async fn handle_list(state: &Arc<ServerState>) -> Result<Value, CoreError> {
34    let state_clone = Arc::clone(state);
35    tokio::task::spawn_blocking(move || {
36        let database = state_clone.database.lock().unwrap();
37        let insights = query_active_insights(&database)?;
38        let count = insights.len();
39        let serialized: Vec<Value> = insights.iter().map(memory_to_portable_json).collect();
40        Ok::<Value, CoreError>(json!({ "insights": serialized, "count": count }))
41    })
42    .await
43    .map_err(|error| CoreError::SocketError(error.to_string()))?
44}
45
46async fn handle_delete(state: &Arc<ServerState>, id: Option<String>) -> Result<Value, CoreError> {
47    let insight_id =
48        id.ok_or_else(|| CoreError::DispatchError("delete requires 'id' parameter".into()))?;
49    let state_clone = Arc::clone(state);
50    tokio::task::spawn_blocking(move || {
51        let database = state_clone.database.lock().unwrap();
52        let memory = database.get_memory(&insight_id)?;
53        if memory.memory_type != "insight" {
54            return Err(CoreError::DispatchError(format!(
55                "memory {} is not an insight",
56                insight_id
57            )));
58        }
59        database.delete_memory(&insight_id)?;
60        Ok::<Value, CoreError>(json!({ "deleted": insight_id }))
61    })
62    .await
63    .map_err(|error| CoreError::SocketError(error.to_string()))?
64}
65
66fn query_active_insights(database: &engram_storage::Database) -> Result<Vec<Memory>, CoreError> {
67    let mut statement = database
68        .connection()
69        .prepare(
70            "SELECT * FROM memories WHERE memory_type = 'insight' \
71             AND superseded_by IS NULL",
72        )
73        .map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?;
74    let rows = statement
75        .query_map([], engram_storage::row_to_memory)
76        .map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?;
77    let mut insights = Vec::new();
78    for row in rows {
79        insights.push(
80            row.map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?,
81        );
82    }
83    Ok(insights)
84}