use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use anyhow::Result;
use async_trait::async_trait;
use crate::agents::Generator;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Turn {
pub role: TurnRole,
pub text: String,
#[serde(default)]
pub perf: Option<TurnPerf>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TurnRole {
User,
Assistant,
}
impl TurnRole {
pub fn as_str(&self) -> &'static str {
match self {
TurnRole::User => "User",
TurnRole::Assistant => "Assistant",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TurnPerf {
pub prompt_tokens: usize,
pub completion_tokens: usize,
pub latency: Duration,
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct SessionId(pub String);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionConfig {
pub chat_backend: String,
pub chat_model: String,
pub embed_backend: String,
pub embed_model: String,
pub memory_strategy: String, pub memory_backend: String, pub memory_model: String,
pub top_k: usize,
pub similarity_threshold: f64,
pub model_ctx_tokens: usize,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionData {
pub id: SessionId,
pub created: SystemTime,
pub updated: SystemTime,
pub config: SessionConfig,
pub turns: Vec<Turn>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionManifest {
pub id: SessionId,
pub created: SystemTime,
pub updated: SystemTime,
pub turn_count: usize,
pub summary: Option<String>,
pub path: PathBuf,
}
#[async_trait]
pub trait SessionStore: Send + Sync {
async fn save(&self, session: &SessionData) -> Result<()>;
async fn load(&self, id: &SessionId) -> Result<Option<SessionData>>;
async fn list(&self) -> Result<Vec<SessionManifest>>;
async fn delete(&self, id: &SessionId) -> Result<()>;
fn name(&self) -> &'static str;
}
#[async_trait]
pub trait HistoryStrategy: Send + Sync {
async fn build_context(
&self,
store: &dyn SessionStore,
current_query: &str,
) -> Result<String>;
fn name(&self) -> &'static str;
}
pub struct LogHistory;
#[async_trait]
impl HistoryStrategy for LogHistory {
async fn build_context(
&self,
store: &dyn SessionStore,
_current_query: &str,
) -> Result<String> {
let manifests = store.list().await?;
let Some(latest) = manifests.last() else {
return Ok(String::new());
};
let Some(session) = store.load(&latest.id).await? else {
return Ok(String::new());
};
let mut out = format!(
"[Previous session — {:?}]\n",
session.created
);
for turn in &session.turns {
out.push_str(match turn.role {
TurnRole::User => "User: ",
TurnRole::Assistant => "Assistant: ",
});
out.push_str(&turn.text);
out.push('\n');
}
Ok(out)
}
fn name(&self) -> &'static str {
"log"
}
}
pub struct SummaryHistory {
agent: Box<dyn Generator>,
}
impl SummaryHistory {
pub fn new(agent: Box<dyn Generator>) -> Self {
Self { agent }
}
}
#[async_trait]
impl HistoryStrategy for SummaryHistory {
async fn build_context(
&self,
store: &dyn SessionStore,
current_query: &str,
) -> Result<String> {
let manifests = store.list().await?;
if manifests.is_empty() {
return Ok(String::new());
}
let mut prompt = String::from(
"Summarise the following past research sessions in one paragraph. "
);
prompt.push_str("Focus on topics discussed and conclusions reached.\n\n");
for m in &manifests {
let Some(session) = store.load(&m.id).await? else {
continue;
};
prompt.push_str(&format!("## Session {}\n", m.id.0));
for turn in &session.turns {
prompt.push_str(match turn.role {
TurnRole::User => "User: ",
TurnRole::Assistant => "Assistant: ",
});
prompt.push_str(&turn.text);
prompt.push('\n');
}
prompt.push('\n');
}
prompt.push_str(&format!(
"Current query: {}\n\nSummary:",
current_query
));
let summary = self.agent.generate(&prompt).await?;
Ok(format!(
"[Summary of {} previous session(s)]\n{}\n",
manifests.len(),
summary.trim()
))
}
fn name(&self) -> &'static str {
"summary"
}
}