use super::store::Session;
use super::traits::AgentMessage;
#[derive(Debug, Clone)]
pub struct StatefulDelta {
pub previous_response_id: String,
pub msg_offset: usize,
pub pending_outputs: Vec<(String, String)>,
}
pub struct StatefulSession<M: AgentMessage> {
session: Session<M>,
last_response_id: Option<String>,
last_msg_count: usize,
pending_tool_outputs: Vec<(String, String)>,
}
impl<M: AgentMessage> StatefulSession<M> {
pub fn new(session: Session<M>) -> Self {
Self {
session,
last_response_id: None,
last_msg_count: 0,
pending_tool_outputs: Vec::new(),
}
}
pub fn delta(&self) -> Option<StatefulDelta> {
let id = self.last_response_id.as_ref()?;
let total = self.session.messages().len();
if self.last_msg_count == 0 || self.last_msg_count > total {
return None;
}
Some(StatefulDelta {
previous_response_id: id.clone(),
msg_offset: self.last_msg_count,
pending_outputs: self.pending_tool_outputs.clone(),
})
}
pub fn store_response(
&mut self,
response_id: Option<String>,
msg_count: usize,
outputs: Vec<(String, String)>,
) {
self.last_response_id = response_id;
self.last_msg_count = msg_count;
self.pending_tool_outputs = outputs;
}
pub fn clear(&mut self) {
self.last_response_id = None;
self.last_msg_count = 0;
self.pending_tool_outputs = Vec::new();
}
pub fn session(&self) -> &Session<M> {
&self.session
}
pub fn session_mut(&mut self) -> &mut Session<M> {
&mut self.session
}
pub fn push(&mut self, role: <M as AgentMessage>::Role, content: String) -> &M {
self.session.push(role, content)
}
pub fn messages(&self) -> &[M] {
self.session.messages()
}
pub fn is_empty(&self) -> bool {
self.session.is_empty()
}
pub fn len(&self) -> usize {
self.session.len()
}
pub fn is_stateful(&self) -> bool {
self.last_response_id.is_some()
}
pub fn into_inner(self) -> Session<M> {
self.session
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::simple::{SimpleMsg, SimpleRole};
fn make_session() -> Session<SimpleMsg> {
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
Session::new(dir.to_str().unwrap(), 60).unwrap()
}
#[test]
fn new_has_no_delta() {
let session = make_session();
let stateful = StatefulSession::new(session);
assert!(stateful.delta().is_none());
assert!(!stateful.is_stateful());
}
#[test]
fn store_and_delta() {
let mut session = make_session();
session.push(SimpleRole::System, "system prompt".into());
session.push(SimpleRole::User, "hello".into());
session.push(SimpleRole::Assistant, "hi".into());
let mut stateful = StatefulSession::new(session);
stateful.store_response(
Some("resp_123".into()),
3,
vec![("call_1".into(), r#"{"ok":true}"#.into())],
);
assert!(stateful.is_stateful());
let delta = stateful.delta().unwrap();
assert_eq!(delta.previous_response_id, "resp_123");
assert_eq!(delta.msg_offset, 3);
assert_eq!(delta.pending_outputs.len(), 1);
stateful.push(SimpleRole::User, "next question".into());
let delta2 = stateful.delta().unwrap();
assert_eq!(delta2.msg_offset, 3); assert_eq!(stateful.len(), 4);
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn clear_removes_state() {
let mut session = make_session();
session.push(SimpleRole::User, "test".into());
let mut stateful = StatefulSession::new(session);
stateful.store_response(Some("resp_abc".into()), 1, vec![]);
assert!(stateful.is_stateful());
stateful.clear();
assert!(!stateful.is_stateful());
assert!(stateful.delta().is_none());
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn delegate_methods() {
let session = make_session();
let mut stateful = StatefulSession::new(session);
assert!(stateful.is_empty());
assert_eq!(stateful.len(), 0);
stateful.push(SimpleRole::User, "hello".into());
assert!(!stateful.is_empty());
assert_eq!(stateful.len(), 1);
assert_eq!(stateful.messages()[0].content(), "hello");
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn into_inner() {
let mut session = make_session();
session.push(SimpleRole::User, "preserved".into());
let stateful = StatefulSession::new(session);
let inner = stateful.into_inner();
assert_eq!(inner.messages()[0].content(), "preserved");
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn delta_none_when_count_exceeds_messages() {
let session = make_session();
let mut stateful = StatefulSession::new(session);
stateful.store_response(Some("resp_x".into()), 100, vec![]);
assert!(stateful.delta().is_none());
let dir = std::env::temp_dir().join("sgr_stateful_test");
let _ = std::fs::remove_dir_all(&dir);
}
}