aether_sessions/
transcript.rs1use 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#[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 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}