#![allow(clippy::expect_used, clippy::doc_markdown)]
use std::sync::Arc;
use loopctl::config::LoopConfig;
use loopctl::engine::BareLoop;
use loopctl::engine::loop_core::Loop;
use loopctl::testing::{MockApiClient, MockResponse, MockToolCall};
use loopctl::tool::{FnTool, ToolOutput, ToolRegistry};
use serde_json::json;
fn echo_fn(
input: serde_json::Value,
_ctx: &loopctl::tool::ToolContext,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = Result<ToolOutput, loopctl::tool::ToolError>>
+ Send
+ 'static,
>,
> {
Box::pin(async move {
let text = input
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("(empty)");
Ok(ToolOutput::text(format!("echo: {text}")))
})
}
#[tokio::main]
async fn main() {
let client = MockApiClient::new("echo-model").with_responses(vec![
MockResponse {
text: String::new(),
tool_call: Some(MockToolCall {
id: "call_1".into(),
name: "echo".into(),
input: json!({"message": "Hello from the model!"}),
}),
stop_reason: "tool_use".into(),
},
MockResponse {
text: "I echoed your message. Done!".into(),
tool_call: None,
stop_reason: "end_turn".into(),
},
]);
let mut tools = ToolRegistry::new();
tools.register(
FnTool::new(
"echo".into(),
"Echo back the provided message.".into(),
json!({
"type": "object",
"properties": {
"message": {"type": "string", "description": "The text to echo"}
},
"required": ["message"]
}),
echo_fn,
)
.read_only(),
);
let mut agent = BareLoop::new(Arc::new(client), tools, LoopConfig::default());
let result = agent
.run("Please echo something.")
.await
.expect("session should succeed");
println!("Turns: {}", result.total_turns);
println!("Tool calls: {}", result.tool_calls);
println!("Output: {}", result.final_output.unwrap_or_default());
}