remem-ai 0.6.24

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use rusqlite::params;

use crate::memory;

use super::super::helpers::{
    error_response, memory_to_item_with_conn, open_request_db, staleness_error_response,
};
use super::super::types::MemoryDetailParams;
use super::super::types::{DbState, MemoryDetailResponse, MemoryEdgeItem};

pub(in crate::api) async fn handle_memory_detail(
    State(_state): State<DbState>,
    Path(id): Path<i64>,
    Query(params): Query<MemoryDetailParams>,
) -> impl IntoResponse {
    let conn = match open_request_db() {
        Ok(conn) => conn,
        Err(response) => return response,
    };

    let policy_filter = if params.include_suppressed.unwrap_or(false) {
        "1=1".to_string()
    } else {
        crate::memory::suppression::memory_policy_filter_sql("memories")
    };
    let memory_row = match conn.query_row(
        &format!(
            "SELECT {}, version FROM memories WHERE id = ?1 AND {}",
            crate::memory::types::MEMORY_COLS,
            policy_filter
        ),
        params![id],
        |row| {
            Ok((
                crate::memory::types::map_memory_row_pub(row)?,
                row.get::<_, i64>(13)?,
            ))
        },
    ) {
        Ok(m) => m,
        Err(rusqlite::Error::QueryReturnedNoRows) => {
            return error_response(
                StatusCode::NOT_FOUND,
                "not_found",
                &format!("memory {id} not found"),
            )
            .into_response()
        }
        Err(err) => {
            return error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "query_failed",
                &err.to_string(),
            )
            .into_response()
        }
    };
    let (mem, version) = memory_row;

    let result = (|| -> anyhow::Result<(Vec<String>, Vec<MemoryEdgeItem>)> {
        let mut stmt = conn.prepare(
            "SELECT e.canonical_name FROM memory_entities me \
             JOIN entities e ON e.id = me.entity_id \
             WHERE me.memory_id = ?1 ORDER BY e.mention_count DESC",
        )?;
        let entities: Vec<String> = stmt
            .query_map(params![id], |row| row.get::<_, String>(0))?
            .collect::<Result<Vec<_>, rusqlite::Error>>()?;

        let mut stmt = conn.prepare(
            "SELECT id, edge_type, from_memory_id, to_memory_id, confidence FROM memory_edges \
             WHERE from_memory_id = ?1 OR to_memory_id = ?1 ORDER BY created_at_epoch DESC, id DESC",
        )?;
        let edges: Vec<MemoryEdgeItem> = stmt
            .query_map(params![id], |row| {
                Ok(MemoryEdgeItem {
                    id: row.get(0)?,
                    edge_type: row.get(1)?,
                    from_memory_id: row.get(2)?,
                    to_memory_id: row.get(3)?,
                    confidence: row.get(4)?,
                })
            })?
            .collect::<Result<Vec<_>, rusqlite::Error>>()?;
        Ok((entities, edges))
    })();

    let (entities, edges) = match result {
        Ok(v) => v,
        Err(err) => {
            return error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "detail_failed",
                &err.to_string(),
            )
            .into_response()
        }
    };

    let memory = match memory_to_item_with_conn(&conn, &mem) {
        Ok(mut item) => {
            item.version = Some(version);
            item
        }
        Err(err) => return staleness_error_response(&err).into_response(),
    };

    if let Err(err) = memory::mark_memories_accessed(&conn, &[id]) {
        return error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "db_error",
            &err.to_string(),
        )
        .into_response();
    }

    Json(MemoryDetailResponse {
        memory,
        entities,
        edges,
    })
    .into_response()
}