pub mod crush;
pub mod goose;
pub mod jsonl;
pub mod opencode;
use crate::message::{Context, ContextListing, ConversationMessage, Entry};
use std::collections::HashMap;
pub trait ContextReader {
fn list_contexts(&self) -> anyhow::Result<Vec<ContextListing>>;
fn read_context(&self, context_id: &str) -> anyhow::Result<Context>;
}
pub fn active_lineage_ids(entries: &[Entry]) -> Vec<String> {
let parent_map: HashMap<&str, &str> = entries
.iter()
.filter(|e| !e.parent_id.is_empty())
.map(|e| (e.id.as_str(), e.parent_id.as_str()))
.collect();
let mut leaves: Vec<&str> = entries
.iter()
.map(|e| e.id.as_str())
.filter(|&id| !parent_map.values().any(|p| *p == id))
.collect();
leaves.sort_by_key(|&id| entries.iter().position(|e| e.id == id));
let mut active_ids = Vec::new();
for leaf in leaves {
let mut current = leaf;
let mut visited = std::collections::HashSet::new();
active_ids.push(current.to_string());
while let Some(&parent) = parent_map.get(current) {
if parent.is_empty() || !visited.insert(parent) {
break;
}
active_ids.push(parent.to_string());
current = parent;
}
}
active_ids
}
pub fn filter_messages(
messages: Vec<ConversationMessage>,
ids: &[String],
) -> Vec<ConversationMessage> {
if ids.is_empty() {
return messages;
}
let id_set: std::collections::HashSet<&str> =
ids.iter().map(std::string::String::as_str).collect();
messages
.into_iter()
.filter(|m| id_set.contains(m.entry_id.as_str()))
.collect()
}
pub fn parse_id_set(csv: &str) -> Vec<String> {
crate::text::split_csv(csv)
}