1use std::io::Write;
11
12use agent_base::{RuntimeEvent, UserEvent};
13use anyhow::Result;
14
15use crate::session::SessionContext;
16
17pub fn save_turn_log(session_ctx: &SessionContext, turn: u32, events: &[RuntimeEvent], user_input: &str) -> Result<()> {
22 let turn_path = session_ctx.turn_path(turn as usize);
23 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(&turn_path)?;
24
25 let meta = serde_json::json!({
26 "turn": turn,
27 "timestamp": chrono::Utc::now().to_rfc3339(),
28 "user_input": user_input,
29 });
30 writeln!(file, "{}", serde_json::to_string(&meta)?)?;
31
32 for event in events {
33 let line = event_to_jsonl(event);
34 writeln!(file, "{}", line)?;
35 }
36
37 writeln!(file, "{}", serde_json::to_string(&serde_json::json!({"type": "turn_end", "turn": turn}))?)?;
38
39 file.flush()?;
40
41 Ok(())
42}
43
44pub fn event_to_value(event: &RuntimeEvent) -> serde_json::Value {
46 match event {
47 RuntimeEvent::ThoughtDelta { text, .. } => {
48 serde_json::json!({"type": "thought_delta", "text": text})
49 },
50 RuntimeEvent::TextDelta { text, .. } => {
51 serde_json::json!({"type": "text_delta", "text": text})
52 },
53 RuntimeEvent::ToolCallStarted { tool_name, args_json, .. } => {
54 let args: serde_json::Value = serde_json::from_str(args_json).unwrap_or(serde_json::Value::Null);
55 serde_json::json!({"type": "tool_call_started", "tool": tool_name, "args": args})
56 },
57 RuntimeEvent::ToolCallFinished { tool_name, summary, .. } => {
58 serde_json::json!({"type": "tool_call_finished", "tool": tool_name, "summary": summary})
59 },
60 RuntimeEvent::AwaitingApproval { request, .. } => {
61 serde_json::json!({"type": "approval_request", "title": request.title, "message": request.message})
62 },
63 RuntimeEvent::PlanUpdated { explanation, plan, .. } => {
64 serde_json::json!({"type": "plan_updated", "explanation": explanation, "plan": plan})
65 },
66 RuntimeEvent::UserEvent { event: UserEvent::Structured { event_type, data }, .. } => {
67 serde_json::json!({"type": "user_event", "event_type": event_type, "data": data})
68 },
69 RuntimeEvent::UserEvent { .. } => serde_json::json!({"type": "other"}),
70 RuntimeEvent::RunCancelled { .. } => serde_json::json!({"type": "run_cancelled"}),
71 RuntimeEvent::RunFinished { .. } => serde_json::json!({"type": "run_finished"}),
72 RuntimeEvent::Checkpoint { .. } => serde_json::json!({"type": "checkpoint"}),
73 }
74}
75
76pub fn event_to_jsonl(event: &RuntimeEvent) -> String {
78 let value = event_to_value(event);
79 serde_json::to_string(&value).unwrap_or_else(|_| r#"{"type":"serialize_error"}"#.to_string())
80}