remem-ai 0.6.13

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, redact_optional, ReadResourceParams,
    ReadResourceSpec, ResourceProjectionPolicy, SafeResourceRef,
};
use super::super::types::DbState;

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

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

struct Observations;

struct ObservationRow {
    id: i64,
    project: Option<String>,
    observation_type: String,
    title: Option<String>,
    status: String,
    branch: Option<String>,
    created_at_epoch: Option<i64>,
    reference_time_epoch: Option<i64>,
    session_row_id: Option<i64>,
}

#[derive(Serialize)]
struct ObservationItem {
    id: i64,
    project: Option<String>,
    observation_type: String,
    status: String,
    title: Option<String>,
    summary: String,
    preview: String,
    created_at_epoch: Option<i64>,
    reference_time_epoch: Option<i64>,
    branch: Option<String>,
    references: Vec<SafeResourceRef>,
}

const SELECT_OBSERVATION: &str = "SELECT o.id, COALESCE(o.project, p.project_key),
            COALESCE(o.observation_type, o.type), o.title,
            COALESCE(o.status, 'active'), o.branch, o.created_at_epoch,
            o.reference_time_epoch, o.session_row_id
     FROM observations o LEFT JOIN projects p ON p.id = o.project_id";

impl ReadResourceSpec for Observations {
    type Row = ObservationRow;
    type Item = ObservationItem;

    const KIND: CursorKind = CursorKind::Observations;
    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_OBSERVATION}
             WHERE (?1 IS NULL OR o.id < ?1)
               AND (?2 IS NULL OR COALESCE(o.project, p.project_key) = ?2)
               AND (?3 IS NULL OR COALESCE(o.status, 'active') = ?3)
             ORDER BY o.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_OBSERVATION} WHERE o.id = ?1"),
            params![id],
            map_row,
        )
        .optional()
        .map_err(Into::into)
    }

    fn project(
        row: Self::Row,
        policy: &ResourceProjectionPolicy,
    ) -> anyhow::Result<Option<Self::Item>> {
        let mut visible = vec![row.observation_type.as_str(), row.status.as_str()];
        visible.extend(row.project.as_deref());
        visible.extend(row.title.as_deref());
        visible.extend(row.branch.as_deref());
        if policy.suppresses(&visible, &[]) {
            return Ok(None);
        }
        let observation_type = redact_bounded(&row.observation_type);
        let title = redact_optional(row.title);
        let summary = title
            .clone()
            .unwrap_or_else(|| format!("Observation {observation_type}"));
        let preview = title.clone().unwrap_or_default();
        let references = row
            .session_row_id
            .filter(|id| *id > 0)
            .map(|id| SafeResourceRef {
                kind: "session",
                id,
                title: None,
                status: None,
            })
            .into_iter()
            .collect();
        Ok(Some(ObservationItem {
            id: row.id,
            project: redact_optional(row.project),
            observation_type,
            status: redact_bounded(&row.status),
            title,
            summary,
            preview,
            created_at_epoch: row.created_at_epoch,
            reference_time_epoch: row.reference_time_epoch,
            branch: redact_optional(row.branch),
            references,
        }))
    }
}

fn map_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<ObservationRow> {
    Ok(ObservationRow {
        id: row.get(0)?,
        project: row.get(1)?,
        observation_type: row.get(2)?,
        title: row.get(3)?,
        status: row.get(4)?,
        branch: row.get(5)?,
        created_at_epoch: row.get(6)?,
        reference_time_epoch: row.get(7)?,
        session_row_id: row.get(8)?,
    })
}