Skip to main content

aether_sessions/
transcript.rs

1use crate::model::{SessionEvent, UserEvent};
2use aether_core::events::{AgentEvent, ContextEvent, ToolEvent, TurnEvent, task_created_result};
3use llm::{ChatMessage, Context, ToolCallError, ToolCallResult};
4
5pub fn context_from_events(events: &[SessionEvent]) -> Context {
6    let mut context = Context::new(vec![], vec![]);
7    let mut pending = PendingTurn::default();
8    for event in events {
9        match event {
10            SessionEvent::User(event) => {
11                pending.flush(&mut context);
12                apply_user_event(&mut context, event);
13            }
14            SessionEvent::Agent(event) => apply_agent_event(&mut context, event, &mut pending),
15            SessionEvent::Control(_) => {}
16        }
17    }
18    pending.flush(&mut context);
19    context
20}
21
22pub fn conversation_messages_from_events(events: &[SessionEvent]) -> Vec<ChatMessage> {
23    context_from_events(events).messages().iter().filter(|message| !message.is_system()).cloned().collect()
24}
25
26/// Tool results are logged before the reply that requested them is recorded,
27/// and task updates that land in between follow that reply into the context.
28#[derive(Default)]
29struct PendingTurn {
30    tool_results: Vec<Result<ToolCallResult, ToolCallError>>,
31    task_messages: Vec<ChatMessage>,
32}
33
34impl PendingTurn {
35    fn record(&mut self, context: &mut Context, reply: &ChatMessage) {
36        let pending = std::mem::take(self);
37        context.add_message(reply.clone());
38        for result in pending.tool_results {
39            context.add_message(ChatMessage::ToolCallResult(result));
40        }
41        for message in pending.task_messages {
42            context.add_message(message);
43        }
44    }
45
46    /// Results whose reply was never recorded are dropped along with it.
47    fn flush(&mut self, context: &mut Context) {
48        let pending = std::mem::take(self);
49        for message in pending.task_messages {
50            context.add_message(message);
51        }
52    }
53}
54
55fn apply_user_event(ctx: &mut Context, event: &UserEvent) {
56    match event {
57        UserEvent::Message { message_id, content, .. } => {
58            ctx.add_message(ChatMessage::user_with_id(message_id.clone(), content.clone()));
59        }
60        UserEvent::ClearContext => ctx.clear_conversation(),
61    }
62}
63
64fn apply_agent_event(ctx: &mut Context, event: &AgentEvent, pending: &mut PendingTurn) {
65    match event {
66        AgentEvent::Context(ContextEvent::ReplyRecorded { message }) => pending.record(ctx, message),
67        AgentEvent::Tool(ToolEvent::Result { result, .. }) => pending.tool_results.push(Ok(result.clone())),
68        AgentEvent::Tool(ToolEvent::TaskCreated { request, task_id, .. }) => {
69            pending.tool_results.push(Ok(task_created_result(request, task_id)));
70        }
71        AgentEvent::Tool(ToolEvent::Error { error }) => pending.tool_results.push(Err(error.clone())),
72        AgentEvent::Turn(TurnEvent::AutoContinue { message_id, content, .. }) => {
73            pending.flush(ctx);
74            ctx.add_message(ChatMessage::user_with_id(message_id.clone(), content.clone()));
75        }
76        AgentEvent::Turn(TurnEvent::Ended { .. }) => pending.flush(ctx),
77        AgentEvent::Context(ContextEvent::Cleared) => {
78            ctx.clear_conversation();
79            *pending = PendingTurn::default();
80        }
81        AgentEvent::Context(ContextEvent::CompactionResult { message_id, summary, .. }) => {
82            pending.flush(ctx);
83            *ctx = ctx.with_compacted_summary(message_id.clone(), summary);
84        }
85        AgentEvent::Tool(
86            event @ (ToolEvent::TaskCompleted { .. } | ToolEvent::TaskFailed { .. } | ToolEvent::TaskCancelled { .. }),
87        ) => {
88            if let Some(message) = event.task_context_message() {
89                if pending.tool_results.is_empty() {
90                    ctx.add_message(message);
91                } else {
92                    pending.task_messages.push(message);
93                }
94            }
95        }
96        _ => {}
97    }
98}