1use std::io::{self, Write};
14
15use ag_ui::client::{HttpAgent, InterruptExt as _, RunParams, interrupts_of, resume_run};
16use ag_ui::{Event, EventType, ResumeEntry, RunAgentInput, Tool};
17use futures_util::StreamExt as _;
18use serde_json::{Value, json};
19
20pub async fn trace(
27 agent: &HttpAgent,
28 thread: &str,
29 said: &str,
30 tools: Vec<Tool>,
31 approve: bool,
32 out: &mut impl Write,
33) -> io::Result<usize> {
34 let mut input: RunAgentInput = RunParams::new(thread, format!("{thread}-run-1"))
35 .user(format!("{thread}-msg-1"), said)
36 .tools(tools)
37 .into();
38
39 let mut printed = 0;
40 let mut round = 1;
41
42 loop {
43 writeln!(out, "--- run {round} · {}", input.run_id)?;
44 let (count, paused) = stream_once(agent, input.clone(), approve, out).await?;
45 printed += count;
46
47 let Some(entries) = paused else {
48 return Ok(printed);
49 };
50 round += 1;
54 input = resume_run(&input, format!("{thread}-run-{round}"), entries);
55 }
56}
57
58async fn stream_once(
60 agent: &HttpAgent,
61 input: RunAgentInput,
62 approve: bool,
63 out: &mut impl Write,
64) -> io::Result<(usize, Option<Vec<ResumeEntry>>)> {
65 let mut events = agent.run(input);
66 let mut printed = 0;
67 let mut resume = None;
68
69 while let Some(event) = events.next().await {
70 let event = match event {
71 Ok(event) => event,
72 Err(error) => {
75 writeln!(out, " !! {error}")?;
76 break;
77 }
78 };
79 printed += 1;
80 writeln!(
81 out,
82 " {:<26} {}",
83 name(event.event_type()),
84 payload(&event)
85 )?;
86
87 let interrupts = interrupts_of(&event);
88 if !interrupts.is_empty() {
89 resume = Some(
90 interrupts
91 .iter()
92 .map(|interrupt| {
93 if approve {
94 interrupt.resolve(json!({"confirm": true}))
95 } else {
96 interrupt.cancel()
97 }
98 })
99 .collect(),
100 );
101 }
102 }
103 Ok((printed, resume))
104}
105
106fn name(kind: EventType) -> String {
108 serde_json::to_value(kind)
109 .ok()
110 .and_then(|value| value.as_str().map(str::to_owned))
111 .unwrap_or_else(|| format!("{kind:?}"))
112}
113
114fn payload(event: &Event) -> String {
116 let Ok(Value::Object(mut fields)) = serde_json::to_value(event) else {
117 return String::new();
118 };
119 fields.remove("type");
120 fields.remove("timestamp");
121 serde_json::to_string(&fields).unwrap_or_default()
122}