use saya_agent::{ChatMessage, ToolMetadata};
use saya_store::{RedactedSession, RedactedToolMetadata, RedactedTurn, SESSION_VERSION};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SessionState {
pub id: String,
pub profile: Option<String>,
pub included_profiles: Vec<String>,
pub provider: String,
pub model: String,
pub allow_data_sharing: bool,
pub approval_mode: String,
pub messages: Vec<SessionLine>,
pub turns: Vec<RedactedTurn>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SessionLine {
pub role: String,
pub content: String,
}
pub type Session = SessionState;
impl SessionState {
pub fn new(id: impl Into<String>, profile: Option<String>, model: impl Into<String>) -> Self {
Self {
id: id.into(),
profile,
included_profiles: Vec::new(),
provider: "ollama".into(),
model: model.into(),
allow_data_sharing: false,
approval_mode: "ask".into(),
messages: Vec::new(),
turns: Vec::new(),
}
}
pub fn record(&mut self, role: &str, content: impl Into<String>) {
self.messages.push(SessionLine {
role: role.into(),
content: content.into(),
});
}
pub fn record_turn(
&mut self,
user: impl Into<String>,
assistant: impl Into<String>,
database_derived: bool,
tools: Vec<ToolMetadata>,
) {
let user = user.into();
let assistant = assistant.into();
self.messages.push(SessionLine {
role: "user".into(),
content: user.clone(),
});
self.messages.push(SessionLine {
role: "assistant".into(),
content: assistant.clone(),
});
self.turns.push(RedactedTurn {
user,
assistant,
database_derived,
tools: tools
.into_iter()
.map(|tool| RedactedToolMetadata {
name: tool.name,
status: tool.status,
})
.collect(),
});
}
pub fn provider_history(&self) -> Vec<ChatMessage> {
let include_sensitive =
self.provider.eq_ignore_ascii_case("ollama") || self.allow_data_sharing;
self.turns
.iter()
.filter(|turn| include_sensitive || !turn.database_derived)
.flat_map(|turn| {
[
ChatMessage::text("user", turn.user.clone()),
ChatMessage::text("assistant", turn.assistant.clone()),
]
})
.collect()
}
pub fn redacted(&self) -> RedactedSession {
RedactedSession {
version: SESSION_VERSION,
id: self.id.clone(),
profile: self.profile.clone(),
included_profiles: self.included_profiles.clone(),
provider: self.provider.clone(),
model: self.model.clone(),
allow_data_sharing: self.allow_data_sharing,
approval_mode: self.approval_mode.clone(),
turns: self.turns.clone(),
profile_names: self.profile_names(),
messages: Vec::new(),
}
}
fn profile_names(&self) -> Vec<String> {
self.profile
.iter()
.cloned()
.chain(self.included_profiles.iter().cloned())
.collect()
}
}