axiomsync 1.0.1

Local retrieval runtime and CLI for AxiomSync.
Documentation
use std::fs;

use crate::error::Result;
use crate::jsonl::{jsonl_all_lines_invalid, parse_jsonl_tolerant};
use crate::models::{Message, SearchContext};

use super::Session;
use super::archive::read_relevant_archive_messages;

impl Session {
    pub fn get_context_for_search(
        &self,
        query: &str,
        max_archives: usize,
        max_messages: usize,
    ) -> Result<SearchContext> {
        if max_messages == 0 {
            return Ok(SearchContext {
                session_id: self.session_id.clone(),
                recent_messages: Vec::new(),
            });
        }

        let active_messages = self.read_messages()?;
        let archive_budget = max_messages.saturating_sub(active_messages.len());
        let mut archive_messages = if max_archives == 0 || archive_budget == 0 {
            Vec::new()
        } else {
            read_relevant_archive_messages(self, query, max_archives, archive_budget)?
        };

        archive_messages.extend(active_messages);
        if archive_messages.len() > max_messages {
            archive_messages = archive_messages[archive_messages.len() - max_messages..].to_vec();
        }

        Ok(SearchContext {
            session_id: self.session_id.clone(),
            recent_messages: archive_messages,
        })
    }

    pub(super) fn read_messages(&self) -> Result<Vec<Message>> {
        let path = self.messages_path()?;
        if !path.exists() {
            return Ok(Vec::new());
        }

        let content = fs::read_to_string(&path)?;
        let parsed = parse_jsonl_tolerant::<Message>(&content);
        if parsed.items.is_empty() && parsed.skipped_lines > 0 {
            return Err(jsonl_all_lines_invalid(
                "session messages",
                Some(path.to_string_lossy().as_ref()),
                parsed.skipped_lines,
                parsed.first_error.as_ref(),
            ));
        }
        Ok(parsed.items)
    }
}