agentcore 0.1.1

A minimal Rust crate that gives any application agentic capabilities.
Documentation
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use agentcore::{
    AgentBuilder, AnthropicProvider, Event, LiteLlmProvider, LlmProvider, ToolBuilder, ToolResult,
};

fn build_provider() -> Option<(Arc<dyn LlmProvider>, String)> {
    let client = reqwest::Client::new();
    if let Ok(url) = std::env::var("LITELLM_API_URL") {
        let key = std::env::var("LITELLM_API_KEY").unwrap_or_else(|_| "unused".into());
        let model = std::env::var("LITELLM_MODEL").unwrap_or_else(|_| "claude-sonnet-4-20250514".into());
        return Some((Arc::new(LiteLlmProvider::new(key, client).base_url(url)), model));
    }
    if let Ok(key) = std::env::var("ANTHROPIC_API_KEY") {
        let mut p = AnthropicProvider::new(key, client);
        if let Ok(url) = std::env::var("ANTHROPIC_BASE_URL") {
            p = p.base_url(url);
        }
        let model = std::env::var("ANTHROPIC_MODEL").unwrap_or_else(|_| "claude-sonnet-4-20250514".into());
        return Some((Arc::new(p), model));
    }
    if std::net::TcpStream::connect("127.0.0.1:4000").is_ok() {
        let key = std::env::var("LITELLM_API_KEY").unwrap_or_else(|_| "unused".into());
        let model = std::env::var("LITELLM_MODEL").unwrap_or_else(|_| "claude-sonnet-4-20250514".into());
        return Some((Arc::new(LiteLlmProvider::new(key, client).base_url("http://localhost:4000".into())), model));
    }
    None
}

#[tokio::test]
async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let Some((provider, model)) = build_provider() else { eprintln!("SKIPPED: no provider"); return Ok(()); };

    let echo_tool = ToolBuilder::new("echo", "Echoes the input text back")
        .schema(serde_json::json!({
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "Text to echo"}
            },
            "required": ["text"]
        }))
        .read_only(true)
        .handler(|input, _ctx| {
            Box::pin(async move {
                let text = input["text"].as_str().unwrap_or("").to_string();
                Ok(ToolResult { content: format!("Echo: {text}"), is_error: false })
            })
        })
        .build();

    let output = AgentBuilder::new()
        .name("assistant")
        .model(&model)
        .system_prompt("You are a helpful assistant. You have an echo tool available. Be concise.")
        .max_turns(5)
        .tool(echo_tool)
        .provider(provider)
        .instruction_prompt("Use the echo tool to echo 'Hello from agent!' and then say goodbye.")
        .event_handler(Arc::new(|event| match event {
            Event::TextChunk { content, .. } => print!("{content}"),
            Event::ToolCallStart { tool_name, .. } => eprintln!("\n[tool] {tool_name}"),
            Event::ToolCallEnd { output, is_error, .. } => {
                if is_error { eprintln!("[error] {output}") } else { eprintln!("[result] {output}") }
            }
            Event::AgentEnd { turns, .. } => eprintln!("\n[done in {turns} turn(s)]"),
            _ => {}
        }))
        .cancel_signal(Arc::new(AtomicBool::new(false)))
        .run()
        .await?;

    println!("\n\n--- Output ---");
    println!("{}", output.response_raw);
    println!("\n--- Cost ---");
    println!("Cost: ${:.4}", output.statistics.costs);

    Ok(())
}