Skip to main content

aether_sessions/
model.rs

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