aether_sessions/
transcript.rs1use crate::model::{SessionEvent, UserEvent};
2use aether_core::events::{AgentEvent, ContextEvent, MessageEvent, ToolEvent, TurnEvent, task_created_result};
3use llm::types::IsoString;
4use llm::{AssistantReasoning, ChatMessage, Context, ToolCallError, ToolCallResult};
5
6pub fn context_from_events(events: &[SessionEvent]) -> Context {
7 let mut context = Context::new(vec![], vec![]);
8 let mut acc = TurnAccumulator::default();
9 for event in events {
10 match event {
11 SessionEvent::User(event) => apply_user_event(&mut context, event),
12 SessionEvent::Agent(event) => apply_agent_event(&mut context, event, &mut acc),
13 SessionEvent::Control(_) => {}
14 }
15 }
16 context
17}
18
19pub fn conversation_messages_from_events(events: &[SessionEvent]) -> Vec<ChatMessage> {
20 context_from_events(events).messages().iter().filter(|message| !message.is_system()).cloned().collect()
21}
22
23#[derive(Default)]
24struct TurnAccumulator {
25 text: String,
26 reasoning: String,
27 tool_results: Vec<Result<ToolCallResult, ToolCallError>>,
28}
29
30fn apply_user_event(ctx: &mut Context, event: &UserEvent) {
31 match event {
32 UserEvent::Message { content } => {
33 ctx.add_message(ChatMessage::User { content: content.clone(), timestamp: IsoString::now() });
34 }
35 UserEvent::ClearContext => {
36 ctx.clear_conversation();
37 }
38 }
39}
40
41fn apply_agent_event(ctx: &mut Context, event: &AgentEvent, acc: &mut TurnAccumulator) {
42 match event {
43 AgentEvent::Message(MessageEvent::Text { chunk, is_complete: true, .. }) => {
44 acc.text.clone_from(chunk);
45 }
46 AgentEvent::Message(MessageEvent::Thought { chunk, is_complete: true, .. }) => {
47 acc.reasoning.clone_from(chunk);
48 }
49 AgentEvent::Tool(ToolEvent::Result { result, .. }) => {
50 acc.tool_results.push(Ok(result.clone()));
51 }
52 AgentEvent::Tool(ToolEvent::TaskCreated { request, task_id, .. }) => {
53 acc.tool_results.push(Ok(task_created_result(request, task_id)));
54 }
55 AgentEvent::Tool(ToolEvent::Error { error }) => {
56 acc.tool_results.push(Err(error.clone()));
57 }
58 AgentEvent::Turn(TurnEvent::Ended { .. }) => {
59 let text = std::mem::take(&mut acc.text);
60 let reasoning_text = std::mem::take(&mut acc.reasoning);
61 let tools = std::mem::take(&mut acc.tool_results);
62 if !text.is_empty() || !tools.is_empty() {
63 let reasoning = AssistantReasoning::from_parts(reasoning_text, None);
64 ctx.push_assistant_turn(&text, reasoning, tools);
65 }
66 }
67 AgentEvent::Context(ContextEvent::Cleared) => {
68 ctx.clear_conversation();
69 acc.text.clear();
70 acc.reasoning.clear();
71 acc.tool_results.clear();
72 }
73 AgentEvent::Context(ContextEvent::CompactionResult { summary, .. }) => {
74 *ctx = ctx.with_compacted_summary(summary);
75 }
76 AgentEvent::Tool(
77 event @ (ToolEvent::TaskCompleted { .. } | ToolEvent::TaskFailed { .. } | ToolEvent::TaskCancelled { .. }),
78 ) => {
79 if let Some(message) = event.task_context_message() {
80 ctx.add_message(message);
81 }
82 }
83 _ => {}
84 }
85}