remem-ai 0.5.11

Persistent memory for Claude Code and Codex — single binary, automatic context
Documentation
use axum::{
    extract::{Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};

use crate::memory::service;

use super::super::helpers::{error_response, memory_to_item, open_request_db};
use super::super::types::{
    DbState, MemoryItem, Meta, MultiHopInfo, RawHitItem, SearchParams, SearchResponse,
};

pub(in crate::api) fn search_request_from_params(params: SearchParams) -> service::SearchRequest {
    let limit = params.limit.unwrap_or(20).min(100);
    let offset = params.offset.unwrap_or(0).max(0);

    service::SearchRequest {
        query: params.query,
        project: params.project,
        memory_type: params.memory_type,
        limit,
        offset,
        include_stale: params
            .include_stale
            .unwrap_or_else(service::default_include_stale),
        branch: params.branch,
        multi_hop: params.multi_hop.unwrap_or(false),
        explain: false,
    }
}

pub(in crate::api) async fn handle_search(
    State(_state): State<DbState>,
    Query(params): Query<SearchParams>,
) -> impl IntoResponse {
    let conn = match open_request_db() {
        Ok(conn) => conn,
        Err(response) => return response,
    };
    let req = search_request_from_params(params);
    let limit = req.limit;
    let offset = req.offset;

    const RAW_PREVIEW_CHARS: usize = 300;

    match service::search_memories(&conn, &req) {
        Ok(results) => {
            let count = results.memories.len();
            let items: Vec<MemoryItem> = results.memories.iter().map(memory_to_item).collect();
            let raw_hits: Vec<RawHitItem> = results
                .raw_hits
                .into_iter()
                .map(|msg| RawHitItem {
                    id: msg.id,
                    session_id: msg.session_id,
                    project: msg.project,
                    role: msg.role,
                    preview: msg.content.chars().take(RAW_PREVIEW_CHARS).collect(),
                    source: msg.source,
                    branch: msg.branch,
                    created_at_epoch: msg.created_at_epoch,
                })
                .collect();
            Json(SearchResponse {
                data: items,
                meta: Meta {
                    count,
                    has_more: results.has_more,
                    limit,
                    offset,
                },
                multi_hop: results.multi_hop.map(|meta| MultiHopInfo {
                    hops: meta.hops,
                    entities_discovered: meta.entities_discovered,
                }),
                raw_hits,
            })
            .into_response()
        }
        Err(err) => error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "search_failed",
            &err.to_string(),
        )
        .into_response(),
    }
}