use std::io::Write;
use futures::StreamExt;
use tkach::message::Content;
use tkach::{Agent, CancellationToken, Message, StreamEvent, providers::Anthropic};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = dotenvy::dotenv_override();
let dir = std::env::temp_dir().join("tkach_streaming_multi_tool");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir)?;
let target = dir.join("config.toml");
let agent = Agent::builder()
.provider(Anthropic::from_env())
.model(tkach::model::claude::HAIKU_20251001)
.system(
"You are a concise assistant. Use tools when needed. \
Never fabricate file contents — always verify by reading.",
)
.tools(tkach::tools::defaults())
.max_turns(8)
.max_tokens(2048)
.working_dir(&dir)
.build()
.unwrap();
let mut stream = agent.stream(
vec![Message::user_text(format!(
"Do these three steps using the tools available:\n\
1. Use the write tool to create a file at {} containing exactly: \
[server]\\nport = 8080\\n\
2. Use the edit tool to change 8080 to 9090.\n\
3. Use the read tool to read the resulting file, then tell me what port it shows.",
target.display()
))],
CancellationToken::new(),
);
print!("> ");
std::io::stdout().flush()?;
let mut tools_called = Vec::new();
let mut delta_count = 0usize;
while let Some(event) = stream.next().await {
match event? {
StreamEvent::ContentDelta(text) => {
delta_count += 1;
print!("{text}");
std::io::stdout().flush()?;
}
StreamEvent::ToolUse { name, .. } => {
eprintln!("\n[tool: {name}]");
tools_called.push(name);
}
_ => {}
}
}
println!();
let result = stream.into_result().await?;
eprintln!();
eprintln!("--- summary ---");
eprintln!("tools called : {tools_called:?}");
eprintln!("delta count : {delta_count}");
eprintln!(
"tokens : {} in / {} out",
result.usage.input_tokens, result.usage.output_tokens
);
eprintln!("stop reason : {:?}", result.stop_reason);
eprintln!("turns (delta msgs / 2): {}", result.new_messages.len() / 2);
eprintln!();
assert!(
delta_count >= 1,
"expected at least one ContentDelta, got 0"
);
assert!(
tools_called.iter().any(|t| t == "write"),
"expected `write` tool call, got: {tools_called:?}"
);
assert!(
tools_called.iter().any(|t| t == "edit"),
"expected `edit` tool call, got: {tools_called:?}"
);
assert!(
tools_called.iter().any(|t| t == "read"),
"expected `read` tool call, got: {tools_called:?}"
);
let on_disk = std::fs::read_to_string(&target)?;
assert!(
on_disk.contains("9090"),
"config.toml should contain '9090' after edit, got:\n{on_disk}"
);
assert!(
!on_disk.contains("8080"),
"config.toml should NOT contain '8080' after edit, got:\n{on_disk}"
);
assert!(
result.text.contains("9090"),
"final text should report port 9090, got: {:?}",
result.text
);
assert!(
result.new_messages.len() >= 3,
"expected at least 3 delta messages (≥1 tool round + final), got {}",
result.new_messages.len()
);
let saw_edit_tool_use_with_9090 = result.new_messages.iter().any(|m| {
m.content.iter().any(|c| {
matches!(c, Content::ToolUse { name, input, .. }
if name == "edit" && input.to_string().contains("9090"))
})
});
assert!(
saw_edit_tool_use_with_9090,
"edit ToolUse should have been recorded in history with 9090 in input"
);
eprintln!("✓ all assertions passed");
Ok(())
}