acp-runtime 0.1.2

Rust SDK for the Agent Communication Protocol (ACP)
Documentation
use std::collections::BTreeMap;

use acp_runtime::AcpAgent;
use acp_runtime::messages::DeliveryMode;
use acp_runtime::options::AcpAgentOptions;
use acp_runtime::overlay_framework::OverlayClient;
use serde_json::{Map, Value};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let from_agent_id = env_string(
        "ACP_FROM_AGENT_ID",
        "agent:overlay.rust.sender@localhost:9031".to_string(),
    );
    let target_base_url = env_string("ACP_TARGET_BASE_URL", "http://localhost:9010".to_string());
    let storage_dir = env_string(
        "ACP_STORAGE_DIR",
        ".acp-data-overlay-rust-sender".to_string(),
    );
    let allow_insecure_http = env_bool("ACP_ALLOW_INSECURE_HTTP", true);
    let recipient_agent_id = std::env::var("ACP_RECIPIENT_AGENT_ID").ok();
    let context = env_string("ACP_CONTEXT", "overlay:rust:client".to_string());
    let payload = payload_from_env()?;

    let discovery_scheme = if target_base_url.starts_with("http://") {
        "http".to_string()
    } else {
        "https".to_string()
    };
    let options = AcpAgentOptions {
        storage_dir: storage_dir.into(),
        allow_insecure_http,
        discovery_scheme,
        ..AcpAgentOptions::default()
    };
    let agent = AcpAgent::load_or_create(&from_agent_id, Some(options))?;
    let mut client = OverlayClient::create(agent);
    let result = client.send_acp(
        &target_base_url,
        payload,
        recipient_agent_id.as_deref(),
        Some(context),
        Some(DeliveryMode::Auto),
        120,
    )?;
    println!("{}", serde_json::to_string_pretty(&Value::Object(result))?);
    Ok(())
}

fn payload_from_env() -> Result<Map<String, Value>, Box<dyn std::error::Error>> {
    if let Ok(raw) = std::env::var("ACP_PAYLOAD_JSON") {
        let value: Value = serde_json::from_str(&raw)?;
        let object = value
            .as_object()
            .ok_or("ACP_PAYLOAD_JSON must be a JSON object")?
            .clone();
        return Ok(object);
    }
    let mut default_payload = Map::new();
    default_payload.insert(
        "kind".to_string(),
        Value::String("rust-overlay-client".to_string()),
    );
    default_payload.insert(
        "attributes".to_string(),
        Value::Object(
            BTreeMap::from([
                (
                    "source".to_string(),
                    Value::String("acp example".to_string()),
                ),
                ("mode".to_string(), Value::String("overlay".to_string())),
            ])
            .into_iter()
            .collect(),
        ),
    );
    Ok(default_payload)
}

fn env_string(key: &str, default_value: String) -> String {
    std::env::var(key)
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
        .unwrap_or(default_value)
}

fn env_bool(key: &str, default_value: bool) -> bool {
    std::env::var(key)
        .ok()
        .map(|value| match value.trim().to_lowercase().as_str() {
            "1" | "true" | "yes" | "on" => true,
            "0" | "false" | "no" | "off" => false,
            _ => default_value,
        })
        .unwrap_or(default_value)
}