saya_cli/interactive/
session_state.rs1use 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}