wasm-agent 0.2.0

A ReAct and native tool-calling agent loop with no I/O, for WASM, edge workers and plugins: parse model replies, dispatch tools, keep history
Documentation
//! A native tool-calling loop. The responses are the JSON bodies OpenAI and
//! Anthropic return; in a real program each comes from your HTTP call, and
//! you send back the tool results this prints.
//!
//! Run with `cargo run --example native_tools`.

use wasm_agent::native::{tool_results_anthropic, tool_results_openai};
use wasm_agent::types::ToolResult;
use wasm_agent::{Agent, AgentConfig, NativeStep, ToolSpec};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut agent = Agent::new(AgentConfig { recover_from_errors: true, ..Default::default() });
    agent.tools_mut().register(
        ToolSpec::new(
            "get_weather",
            "Current weather for a city",
            r#"{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}"#,
        ),
        Box::new(|args: &str| {
            let city = serde_json::from_str::<serde_json::Value>(args).ok().and_then(|v| v["city"].as_str().map(str::to_string));
            match city {
                Some(c) => ToolResult { tool_name: "get_weather".into(), output: format!("{c}: 18 C, light rain"), success: true },
                None => ToolResult { tool_name: "get_weather".into(), output: "missing city".into(), success: false },
            }
        }),
    )?;

    println!("OpenAI tools: {}", serde_json::to_string(&agent.tools().openai_tools()?)?);

    // 1. OpenAI asks for two calls at once.
    let openai = r#"{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[
        {"id":"call_1","type":"function","function":{"name":"get_weather","arguments":"{\"city\":\"Paris\"}"}},
        {"id":"call_2","type":"function","function":{"name":"get_wether","arguments":"{\"city\":\"Lyon\"}"}}]}}]}"#;
    if let NativeStep::ToolCalls(results) = agent.step_response(openai)? {
        println!("send back to OpenAI: {}", serde_json::to_string(&tool_results_openai(&results))?);
    }

    // 2. The same with Anthropic's format.
    let anthropic = r#"{"content":[{"type":"text","text":"Checking."},
        {"type":"tool_use","id":"toolu_1","name":"get_weather","input":{"city":"Lyon"}}],"stop_reason":"tool_use"}"#;
    if let NativeStep::ToolCalls(results) = agent.step_response(anthropic)? {
        println!("send back to Anthropic: {}", serde_json::to_string(&tool_results_anthropic(&results))?);
    }

    // 3. The final answer.
    let done = r#"{"content":[{"type":"text","text":"Paris and Lyon: 18 C with light rain."}],"stop_reason":"end_turn"}"#;
    if let NativeStep::Final(text) = agent.step_response(done)? {
        println!("answer: {text}");
    }
    Ok(())
}