use std::collections::HashMap;
use std::sync::Arc;
use async_stream::try_stream;
use async_trait::async_trait;
use futures::stream::{BoxStream, StreamExt};
use tokio::sync::RwLock;
use crate::error::Error;
use crate::middleware::ProviderMiddleware;
use crate::provider::Provider;
use crate::stream_event::StreamEvent;
use crate::text::{Step, TextRequest};
use crate::value_objects::{AssistantMessage, FinishReason, Message, ToolCall};
#[async_trait]
pub trait ConversationStore: Send + Sync {
async fn load(&self, id: &str) -> Result<Vec<Message>, Error>;
async fn save(&self, id: &str, messages: &[Message]) -> Result<(), Error>;
}
#[derive(Debug, Default)]
pub struct InMemoryConversationStore {
conversations: RwLock<HashMap<String, Vec<Message>>>,
}
impl InMemoryConversationStore {
pub fn new() -> Self {
Self::default()
}
}
#[async_trait]
impl<T: ConversationStore + ?Sized> ConversationStore for Arc<T> {
async fn load(&self, id: &str) -> Result<Vec<Message>, Error> {
T::load(self, id).await
}
async fn save(&self, id: &str, messages: &[Message]) -> Result<(), Error> {
T::save(self, id, messages).await
}
}
#[async_trait]
impl ConversationStore for InMemoryConversationStore {
async fn load(&self, id: &str) -> Result<Vec<Message>, Error> {
Ok(self
.conversations
.read()
.await
.get(id)
.cloned()
.unwrap_or_default())
}
async fn save(&self, id: &str, messages: &[Message]) -> Result<(), Error> {
self.conversations
.write()
.await
.insert(id.to_string(), messages.to_vec());
Ok(())
}
}
fn is_final_step(finish_reason: FinishReason, tool_calls: &[ToolCall]) -> bool {
finish_reason != FinishReason::ToolCalls || tool_calls.is_empty()
}
pub struct PersistenceMiddleware<S: ConversationStore> {
store: Arc<S>,
}
impl<S: ConversationStore> PersistenceMiddleware<S> {
pub fn new(store: S) -> Self {
Self {
store: Arc::new(store),
}
}
}
#[async_trait]
impl<S: ConversationStore + 'static> ProviderMiddleware for PersistenceMiddleware<S> {
async fn text_step(
&self,
mut request: TextRequest,
next: &dyn Provider,
) -> Result<Step, Error> {
let Some(id) = request.conversation_id.clone() else {
return next.text_step(&request).await;
};
let history = self.store.load(&id).await?;
let new_turn = std::mem::take(&mut request.messages);
request.messages = history
.iter()
.cloned()
.chain(new_turn.iter().cloned())
.collect();
let step = next.text_step(&request).await?;
if is_final_step(step.finish_reason, &step.tool_calls) {
let mut to_save = history;
to_save.extend(new_turn);
to_save.push(Message::Assistant(AssistantMessage {
content: step.text.clone(),
tool_calls: step.tool_calls.clone(),
}));
self.store.save(&id, &to_save).await?;
}
Ok(step)
}
async fn stream_text_once(
&self,
mut request: TextRequest,
next: &dyn Provider,
) -> Result<BoxStream<'static, Result<StreamEvent, Error>>, Error> {
let Some(id) = request.conversation_id.clone() else {
return next.stream_text_once(&request).await;
};
let history = self.store.load(&id).await?;
let new_turn = std::mem::take(&mut request.messages);
request.messages = history
.iter()
.cloned()
.chain(new_turn.iter().cloned())
.collect();
let mut inner = next.stream_text_once(&request).await?;
let store = Arc::clone(&self.store);
let stream = try_stream! {
let mut text = String::new();
let mut tool_calls: Vec<ToolCall> = Vec::new();
let mut finish_reason = FinishReason::Stop;
while let Some(event) = inner.next().await {
let event = event?;
match &event {
StreamEvent::TextDelta { text: delta } => text.push_str(delta),
StreamEvent::ToolCall(call) => tool_calls.push(call.clone()),
StreamEvent::StepFinish { finish_reason: step_finish_reason, .. } => {
finish_reason = *step_finish_reason;
}
StreamEvent::StreamStart { .. }
| StreamEvent::ToolCallDelta { .. }
| StreamEvent::ToolResult(_)
| StreamEvent::StreamEnd { .. } => {}
}
yield event;
}
if is_final_step(finish_reason, &tool_calls) {
let mut to_save = history;
to_save.extend(new_turn);
to_save.push(Message::Assistant(AssistantMessage {
content: if text.is_empty() { None } else { Some(text) },
tool_calls,
}));
store.save(&id, &to_save).await?;
}
};
Ok(stream.boxed())
}
}