agentcore 0.1.0

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

use agentcore::{
    AgentBuilder, AgenticError, AnthropicProvider, HttpTransport, InvocationContext,
    LiteLlmProvider, LlmProvider, MistralProvider, ReadFileTool, GrepTool, ListDirectoryTool,
};

fn build_transport() -> HttpTransport {
    Box::new(|url, headers, body| {
        let url = url.to_string();
        let headers: Vec<(String, String)> = headers
            .into_iter()
            .map(|(k, v)| (k.to_string(), v))
            .collect();
        Box::pin(async move {
            let client = reqwest::Client::new();
            let mut req = client.post(&url).json(&body);
            for (k, v) in &headers {
                req = req.header(k.as_str(), v.as_str());
            }
            let resp = req.send().await.map_err(|e| AgenticError::Other(e.to_string()))?;
            resp.json().await.map_err(|e| AgenticError::Other(e.to_string()))
        })
    })
}

fn build_provider() -> Option<(Arc<dyn LlmProvider>, String)> {
    let transport = build_transport();
    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, transport).base_url(url)), model));
    }
    if let Some(key) = std::env::var("MISTRAL_API_KEY").ok().filter(|k| !k.is_empty()) {
        let model = std::env::var("MISTRAL_MODEL").unwrap_or_else(|_| "mistral-medium-2508".into());
        return Some((Arc::new(MistralProvider::new(key, transport)), model));
    }
    if let Ok(key) = std::env::var("ANTHROPIC_API_KEY") {
        let model = std::env::var("ANTHROPIC_MODEL").unwrap_or_else(|_| "claude-sonnet-4-20250514".into());
        return Some((Arc::new(AnthropicProvider::new(key, transport)), 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, transport).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 agent = AgentBuilder::new()
        .name("reviewer")
        .model(&model)
        .system_prompt(
            "List the top-level directory, read one file, and summarize what this project is. Be concise.",
        )
        .tool(ReadFileTool)
        .tool(GrepTool)
        .tool(ListDirectoryTool)
        .max_turns(10)
        .build()?;

    let mut ctx = InvocationContext::new(provider);
    ctx.prompt = "What is this project?".into();

    let output = agent.run(ctx).await?;
    assert!(!output.response_raw.is_empty());
    assert!(output.statistics.tool_calls >= 1);

    Ok(())
}