1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Example 3: Monitor Tool Use
//!
//! This example demonstrates how to monitor which tools Claude uses
//! by inspecting the message stream.
//!
//! What it does:
//! 1. Asks Claude to perform a multi-step task
//! 2. Tracks all tool uses
//! 3. Prints detailed information about each tool invocation
//!
//! NOTE: This example uses ClaudeClient instead of query() because query()
//! does not support tool calls - when Claude uses a tool, the SDK hangs
//! because it doesn't send tool_result messages back. ClaudeClient uses
//! QueryFull for bidirectional control protocol that properly handles tool calls.
use claude_agent_sdk::{ClaudeAgentOptions, ClaudeClient, ContentBlock, Message};
use futures::stream::StreamExt;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("=== Example 3: Monitor Tool Use ===\n");
// Create output directory
std::fs::create_dir_all("./fixtures")?;
// Configure options
let options = ClaudeAgentOptions::builder()
.allowed_tools(vec!["Write".to_string(), "Read".to_string(), "Bash".to_string()])
.permission_mode(claude_agent_sdk::PermissionMode::AcceptEdits)
.max_turns(10)
.stderr_callback(std::sync::Arc::new(|msg| {
if !msg.trim().is_empty() && !msg.contains("STDERR:") {
eprintln!("CLI: {}", msg.trim());
}
}))
.build();
// Create and connect the client
let mut client = ClaudeClient::new(options);
client.connect().await?;
println!("Asking Claude to create and test a simple Python function...\n");
println!("========================================================\n");
// Query Claude
client
.query("Create a Python file at ./fixtures/math_utils.py with a function that calculates factorial. Then create a test file and run it.")
.await?;
// Track tool usage
let mut tool_usage: HashMap<String, Vec<serde_json::Value>> = HashMap::new();
let mut turn_number = 0;
// Process messages from the response stream
let mut response_stream = client.receive_response();
while let Some(result) = response_stream.next().await {
match result {
Ok(message) => {
match &message {
Message::Assistant(msg) => {
turn_number += 1;
println!("--- Turn {} ---", turn_number);
for block in &msg.message.content {
match block {
ContentBlock::Text(text) => {
println!("💬 Claude: {}", text.text);
},
ContentBlock::ToolUse(tool) => {
println!("🔧 Tool: {}", tool.name);
println!(" ID: {}", tool.id);
println!(" Input: {}", serde_json::to_string_pretty(&tool.input)?);
// Track usage
tool_usage
.entry(tool.name.clone())
.or_default()
.push(tool.input.clone());
},
ContentBlock::Thinking(thinking) => {
println!("🤔 Thinking: {}", thinking.thinking);
},
_ => {},
}
}
println!();
},
Message::System(sys) => {
if sys.subtype == "init" {
println!("System initialized");
if let Some(ref session_id) = sys.session_id {
println!("Session ID: {}", session_id);
}
println!();
}
},
Message::Result(result) => {
println!("\n========================================================");
println!("=== Final Result ===");
println!(
"Duration: {}ms ({:.2}s)",
result.duration_ms,
result.duration_ms as f64 / 1000.0
);
println!(
"API Duration: {}ms ({:.2}s)",
result.duration_api_ms,
result.duration_api_ms as f64 / 1000.0
);
println!("Turns: {}", result.num_turns);
println!("Error: {}", result.is_error);
if let Some(cost) = result.total_cost_usd {
println!("Cost: ${:.4}", cost);
}
println!("Session ID: {}", result.session_id);
if let Some(ref result_text) = result.result {
println!("Result: {}", result_text);
}
},
_ => {},
}
}
Err(e) => {
eprintln!("Error: {:?}", e);
break;
}
}
}
// Print summary
println!("\n========================================================");
println!("=== Tool Usage Summary ===\n");
if tool_usage.is_empty() {
println!("No tools were used.");
} else {
for (tool_name, invocations) in &tool_usage {
println!("🔧 {} - used {} time(s)", tool_name, invocations.len());
for (i, input) in invocations.iter().enumerate() {
println!(" {}. {}", i + 1, serde_json::to_string(input)?);
}
println!();
}
}
// Verify files were created
println!("=== File Verification ===\n");
let files_to_check = vec!["./fixtures/math_utils.py", "./fixtures/test_math_utils.py"];
for file_path in &files_to_check {
if std::path::Path::new(file_path).exists() {
let size = std::fs::metadata(file_path)?.len();
println!("✓ {} ({} bytes)", file_path, size);
} else {
println!("✗ {} (not found)", file_path);
}
}
Ok(())
}