use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use futures_util::StreamExt;
use localharness::{allow_all, Agent, ClosureTool, MockAgentConfig, MockConnection};
use serde_json::json;
#[tokio::main]
async fn main() -> localharness::Result<()> {
let calls = Arc::new(AtomicU64::new(0));
let add = ClosureTool::with_state(
"add",
"Add two integers and return their sum.",
json!({
"type": "object",
"properties": {
"a": { "type": "integer" },
"b": { "type": "integer" }
},
"required": ["a", "b"]
}),
calls.clone(),
|calls, args, _ctx| async move {
calls.fetch_add(1, Ordering::SeqCst);
let a = args["a"].as_i64().unwrap_or(0);
let b = args["b"].as_i64().unwrap_or(0);
Ok(json!({ "sum": a + b }))
},
);
let backend = MockConnection::builder()
.turn(|t| {
t.tool_call("add", json!({ "a": 17, "b": 25 }))
.text("17 + 25 = 42")
})
.build();
let agent = Agent::start_mock(
MockAgentConfig::new(backend)
.with_tool(add)
.with_policies(vec![allow_all()]),
)
.await?;
let response = agent.chat("What is 17 plus 25?").await?;
let mut tool_calls = response.tool_calls();
if let Some(Ok(call)) = tool_calls.next().await {
println!("model called tool `{}` with args {}", call.name, call.args);
}
println!("final answer: {}", response.text().await?);
println!("tool executed {} time(s)", calls.load(Ordering::SeqCst));
agent.shutdown().await?;
Ok(())
}