1use std::io::Write;
3
4use anyhow::Result;
5use serde::Serialize;
6
7use crate::output::Emitter;
8use crate::util::discover::SessionFile;
9
10pub struct ToolsOpts {
13 pub session: String,
14 pub max_tokens: usize,
15}
16
17#[derive(Serialize, Debug)]
20struct ToolRecord {
21 #[serde(rename = "type")]
22 record_type: &'static str,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 timestamp: Option<String>,
25 role: String,
26 tool_name: String,
27 input_preview: String,
28}
29
30pub fn run<W: Write>(_opts: &ToolsOpts, file: &SessionFile, em: &mut Emitter<W>) -> Result<()> {
33 let records = crate::cmd::parse_records(file)?;
34 let start = std::time::Instant::now();
35
36 let mut count = 0usize;
37 'outer: for record in &records {
38 let Some(msg) = record.as_message() else { continue };
39
40 let tools = msg.tool_names();
41 if tools.is_empty() {
42 continue;
43 }
44
45 if let crate::models::MessageContent::Blocks(blocks) = &msg.message.content {
46 for block in blocks {
47 if let crate::models::ContentBlock::ToolUse { name, input, .. } = block {
48 let preview: String = input.to_string().chars().take(200).collect();
49 let rec = ToolRecord {
50 record_type: "tool_call",
51 timestamp: msg.timestamp.clone(),
52 role: record.role().to_string(),
53 tool_name: name.clone(),
54 input_preview: preview,
55 };
56 if !em.emit(&rec)? {
57 break 'outer;
58 }
59 count += 1;
60 }
61 }
62 }
63 }
64
65 let summary = crate::output::SummaryRecord {
66 record_type: "summary",
67 count,
68 files_scanned: None,
69 elapsed_ms: start.elapsed().as_millis(),
70 };
71 em.emit(&summary)?;
72
73 em.flush()?;
74 Ok(())
75}