Skip to main content

aether_sessions/
model.rs

1use aether_core::events::{AgentEvent, ContextEvent, LlmCallOutcome, MessageEvent, ModelEvent, ToolEvent, TurnEvent};
2use llm::ContentBlock;
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(rename_all = "camelCase")]
8pub struct SessionMeta {
9    pub session_id: String,
10    pub cwd: PathBuf,
11    pub model: String,
12    #[serde(default)]
13    pub selected_mode: Option<String>,
14    pub created_at: String,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18#[serde(tag = "type", rename_all = "camelCase")]
19pub enum UserEvent {
20    Message { content: Vec<ContentBlock> },
21    ClearContext,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25#[serde(tag = "type", rename_all = "camelCase")]
26pub enum SessionControlEvent {
27    AgentSwitched { from: Option<String>, to: Option<String> },
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
32#[allow(clippy::large_enum_variant)]
33pub enum SessionEvent {
34    User(UserEvent),
35    Agent(AgentEvent),
36    Control(SessionControlEvent),
37}
38
39impl SessionEvent {
40    pub fn content(&self) -> Option<String> {
41        self.user_content().or_else(|| match self {
42            Self::Agent(event) => event.content(),
43            Self::User(_) | Self::Control(_) => None,
44        })
45    }
46
47    pub fn user_content(&self) -> Option<String> {
48        match self {
49            Self::User(UserEvent::Message { content }) => {
50                let text = ContentBlock::join_text(content);
51                (!text.is_empty()).then_some(text)
52            }
53            Self::User(UserEvent::ClearContext) | Self::Agent(_) | Self::Control(_) => None,
54        }
55    }
56
57    pub fn is_persisted(&self) -> bool {
58        match self {
59            Self::User(_) | Self::Control(_) => true,
60            Self::Agent(event) => match event {
61                AgentEvent::Message(
62                    MessageEvent::Text { is_complete, .. } | MessageEvent::Thought { is_complete, .. },
63                ) => *is_complete,
64                AgentEvent::Tool(
65                    ToolEvent::Call { .. }
66                    | ToolEvent::Result { .. }
67                    | ToolEvent::Error { .. }
68                    | ToolEvent::TaskCreated { .. }
69                    | ToolEvent::TaskCompleted { .. }
70                    | ToolEvent::TaskFailed { .. }
71                    | ToolEvent::TaskCancelled { .. },
72                )
73                | AgentEvent::Turn(
74                    TurnEvent::RetryScheduled { .. }
75                    | TurnEvent::AutoContinue { .. }
76                    | TurnEvent::Ended { .. }
77                    | TurnEvent::LlmCallEnded { outcome: LlmCallOutcome::Failed { .. }, .. },
78                )
79                | AgentEvent::Context(
80                    ContextEvent::CompactionStarted { .. }
81                    | ContextEvent::CompactionEnded { .. }
82                    | ContextEvent::CompactionResult { .. }
83                    | ContextEvent::UsageUpdated { .. }
84                    | ContextEvent::Cleared,
85                )
86                | AgentEvent::Model(ModelEvent::Switched { .. })
87                | AgentEvent::SessionUsage(_) => true,
88                AgentEvent::Tool(
89                    ToolEvent::CallUpdate { .. }
90                    | ToolEvent::ExecutionStarted { .. }
91                    | ToolEvent::Progress { .. }
92                    | ToolEvent::DisplayUpdate { .. }
93                    | ToolEvent::SubAgentProgress { .. }
94                    | ToolEvent::TaskStatus { .. }
95                    | ToolEvent::DefinitionsUpdated { .. },
96                )
97                | AgentEvent::Turn(
98                    TurnEvent::Started { .. }
99                    | TurnEvent::LlmCallStarted { .. }
100                    | TurnEvent::LlmCallEnded {
101                        outcome: LlmCallOutcome::Completed { .. } | LlmCallOutcome::Cancelled,
102                        ..
103                    },
104                ) => false,
105            },
106        }
107    }
108}
109
110/// The most recent session-usage event, whose totals a resumed session continues from.
111pub fn last_session_usage(events: &[SessionEvent]) -> Option<&llm::SessionUsageEvent> {
112    events.iter().rev().find_map(|event| match event {
113        SessionEvent::Agent(AgentEvent::SessionUsage(usage)) => Some(usage),
114        _ => None,
115    })
116}
117
118pub fn last_agent_from_events(initial: Option<String>, events: &[SessionEvent]) -> Option<String> {
119    events
120        .iter()
121        .rev()
122        .find_map(|event| match event {
123            SessionEvent::Control(SessionControlEvent::AgentSwitched { to, .. }) => Some(to.clone()),
124            _ => None,
125        })
126        .unwrap_or(initial)
127}