remem-ai 0.6.62

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use axum::{
    extract::{Path, Query, State},
    response::Response,
};
use rusqlite::{params, Connection, OptionalExtension};
use serde::Serialize;

use super::super::cursor::CursorKind;
use super::super::read_resources::{
    detail_resource, list_resource, redact_bounded, ReadResourceParams, ReadResourceSpec,
    ResourceProjectionPolicy, SafeResourceRef,
};
use super::super::types::DbState;

pub(in crate::api) async fn handle_list_sessions(
    State(_state): State<DbState>,
    Query(params): Query<ReadResourceParams>,
) -> Response {
    list_resource::<Sessions>(params)
}

pub(in crate::api) async fn handle_session_detail(
    State(_state): State<DbState>,
    Path(id): Path<String>,
) -> Response {
    detail_resource::<Sessions>(id)
}

struct Sessions;

struct SessionRow {
    id: i64,
    host_id: i64,
    host: String,
    project_id: i64,
    project: String,
    started_at_epoch: Option<i64>,
    last_seen_at_epoch: i64,
    status: String,
}

#[derive(Serialize)]
struct SessionItem {
    id: i64,
    host: String,
    project: String,
    status: String,
    summary: String,
    started_at_epoch: Option<i64>,
    last_seen_at_epoch: i64,
    references: Vec<SafeResourceRef>,
}

const SELECT_SESSION: &str = "SELECT s.id, s.host_id, h.name, s.project_id, p.project_key,
            s.started_at_epoch, s.last_seen_at_epoch, s.status
     FROM sessions s
     JOIN hosts h ON h.id = s.host_id
     JOIN projects p ON p.id = s.project_id";

impl ReadResourceSpec for Sessions {
    type Row = SessionRow;
    type Item = SessionItem;

    const KIND: CursorKind = CursorKind::Sessions;
    fn row_id(row: &Self::Row) -> i64 {
        row.id
    }

    fn load_batch(
        conn: &Connection,
        resume_before_id: Option<i64>,
        project: Option<&str>,
        status: Option<&str>,
        limit: usize,
    ) -> anyhow::Result<Vec<Self::Row>> {
        let sql = format!(
            "{SELECT_SESSION}
             WHERE (?1 IS NULL OR s.id < ?1)
               AND (?2 IS NULL OR p.project_key = ?2)
               AND (?3 IS NULL OR s.status = ?3)
             ORDER BY s.id DESC LIMIT ?4"
        );
        let mut stmt = conn.prepare(&sql)?;
        let rows = stmt.query_map(
            params![resume_before_id, project, status, limit as i64],
            map_row,
        )?;
        rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
    }

    fn load_one(conn: &Connection, id: i64) -> anyhow::Result<Option<Self::Row>> {
        conn.query_row(
            &format!("{SELECT_SESSION} WHERE s.id = ?1"),
            params![id],
            map_row,
        )
        .optional()
        .map_err(Into::into)
    }

    fn project(
        row: Self::Row,
        policy: &ResourceProjectionPolicy,
    ) -> anyhow::Result<Option<Self::Item>> {
        if policy.suppresses(&[&row.host, &row.project, &row.status], &[]) {
            return Ok(None);
        }
        let host = redact_bounded(&row.host);
        let project = redact_bounded(&row.project);
        let status = redact_bounded(&row.status);
        Ok(Some(SessionItem {
            id: row.id,
            summary: format!("Session on {host}"),
            host: host.clone(),
            project: project.clone(),
            status,
            started_at_epoch: row.started_at_epoch,
            last_seen_at_epoch: row.last_seen_at_epoch,
            references: vec![
                SafeResourceRef {
                    kind: "host",
                    id: row.host_id,
                    title: Some(host),
                    status: None,
                },
                SafeResourceRef {
                    kind: "project",
                    id: row.project_id,
                    title: Some(project),
                    status: None,
                },
            ],
        }))
    }
}

fn map_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<SessionRow> {
    Ok(SessionRow {
        id: row.get(0)?,
        host_id: row.get(1)?,
        host: row.get(2)?,
        project_id: row.get(3)?,
        project: row.get(4)?,
        started_at_epoch: row.get(5)?,
        last_seen_at_epoch: row.get(6)?,
        status: row.get(7)?,
    })
}