Skip to main content

saya_cli/interactive/
session_state.rs

1use saya_agent::{ChatMessage, ToolMetadata};
2use saya_store::{RedactedSession, RedactedToolMetadata, RedactedTurn, SESSION_VERSION};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct SessionState {
7    pub id: String,
8    pub profile: Option<String>,
9    pub included_profiles: Vec<String>,
10    pub provider: String,
11    pub model: String,
12    pub allow_data_sharing: bool,
13    pub approval_mode: String,
14    pub messages: Vec<SessionLine>,
15    pub turns: Vec<RedactedTurn>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19pub struct SessionLine {
20    pub role: String,
21    pub content: String,
22}
23pub type Session = SessionState;
24
25impl SessionState {
26    pub fn new(id: impl Into<String>, profile: Option<String>, model: impl Into<String>) -> Self {
27        Self {
28            id: id.into(),
29            profile,
30            included_profiles: Vec::new(),
31            provider: "ollama".into(),
32            model: model.into(),
33            allow_data_sharing: false,
34            approval_mode: "ask".into(),
35            messages: Vec::new(),
36            turns: Vec::new(),
37        }
38    }
39
40    pub fn record(&mut self, role: &str, content: impl Into<String>) {
41        self.messages.push(SessionLine {
42            role: role.into(),
43            content: content.into(),
44        });
45    }
46
47    pub fn record_turn(
48        &mut self,
49        user: impl Into<String>,
50        assistant: impl Into<String>,
51        database_derived: bool,
52        tools: Vec<ToolMetadata>,
53    ) {
54        let user = user.into();
55        let assistant = assistant.into();
56        self.messages.push(SessionLine {
57            role: "user".into(),
58            content: user.clone(),
59        });
60        self.messages.push(SessionLine {
61            role: "assistant".into(),
62            content: assistant.clone(),
63        });
64        self.turns.push(RedactedTurn {
65            user,
66            assistant,
67            database_derived,
68            tools: tools
69                .into_iter()
70                .map(|tool| RedactedToolMetadata {
71                    name: tool.name,
72                    status: tool.status,
73                })
74                .collect(),
75        });
76    }
77
78    pub fn provider_history(&self) -> Vec<ChatMessage> {
79        let include_sensitive =
80            self.provider.eq_ignore_ascii_case("ollama") || self.allow_data_sharing;
81        self.turns
82            .iter()
83            .filter(|turn| include_sensitive || !turn.database_derived)
84            .flat_map(|turn| {
85                [
86                    ChatMessage::text("user", turn.user.clone()),
87                    ChatMessage::text("assistant", turn.assistant.clone()),
88                ]
89            })
90            .collect()
91    }
92
93    pub fn redacted(&self) -> RedactedSession {
94        RedactedSession {
95            version: SESSION_VERSION,
96            id: self.id.clone(),
97            profile: self.profile.clone(),
98            included_profiles: self.included_profiles.clone(),
99            provider: self.provider.clone(),
100            model: self.model.clone(),
101            allow_data_sharing: self.allow_data_sharing,
102            approval_mode: self.approval_mode.clone(),
103            turns: self.turns.clone(),
104            profile_names: self.profile_names(),
105            messages: Vec::new(),
106        }
107    }
108
109    fn profile_names(&self) -> Vec<String> {
110        self.profile
111            .iter()
112            .cloned()
113            .chain(self.included_profiles.iter().cloned())
114            .collect()
115    }
116}