sim-lib-agent 0.1.1

Agent runtime surfaces for SIM.
Documentation
use super::shared::{optional_value, values_option};
use crate::{
    SWARM_LAUNCH_CAPABILITY, installed_codecs, maybe_f64_option, maybe_u32_option,
    parse_component_options,
};
use sim_kernel::{Args, CapabilityName, Cx, Error, Result, Value};
use sim_lib_server::{Connection, ServerAddress};
use std::sync::{Arc, Mutex, atomic::Ordering};

use super::super::first_codec;
use super::super::model::{
    AgentFabric, NEXT_SWARM_ID, RuntimeValueSite, SwarmRegistry, fabric_from_value,
    swarm_explain_expr, swarm_status_value_for_table,
};

pub(crate) fn swarm_make_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "swarm/make")?;
    let name = crate::symbol_option(cx, &options, "name", sim_kernel::Symbol::new("swarm"))?;
    let budget = Some(super::super::model::Budget {
        max_turns: maybe_u32_option(cx, &options, "max-turns")?,
        max_cost: maybe_f64_option(cx, &options, "max-cost")?,
    })
    .filter(|budget| budget.max_turns.is_some() || budget.max_cost.is_some());
    let fabric = AgentFabric {
        id: NEXT_SWARM_ID.fetch_add(1, Ordering::Relaxed),
        name,
        members: values_option(cx, &options, "agents")?,
        topology: optional_value(&options, "topology"),
        planner: optional_value(&options, "planner"),
        blackboard: optional_value(&options, "blackboard"),
        budget,
        codecs: installed_codecs(cx),
        last_run: Mutex::new(None),
        runs: Arc::new(Mutex::new(SwarmRegistry {
            next_task_id: 1,
            ..SwarmRegistry::default()
        })),
    };
    cx.factory().opaque(Arc::new(fabric))
}

pub(crate) fn swarm_launch_value(cx: &mut Cx, args: Args) -> Result<Value> {
    cx.require(&CapabilityName::new(SWARM_LAUNCH_CAPABILITY))?;
    let [swarm_value, task, ..] = args.values() else {
        return Err(Error::Eval(
            "swarm/launch expects a swarm and a task".to_owned(),
        ));
    };
    let fabric = fabric_from_value(swarm_value)?;
    let expr = task.object().as_expr(cx)?;
    let result = fabric.realize_expr(cx, expr)?;
    cx.factory().expr(result)
}

pub(crate) fn swarm_explain_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let [swarm_value, rest @ ..] = args.values() else {
        return Err(Error::Eval(
            "swarm/explain expects a swarm and optional task id".to_owned(),
        ));
    };
    let fabric = fabric_from_value(swarm_value)?;
    let task_id = rest.first().map(|value| {
        value.object().as_expr(cx).ok().and_then(|expr| match expr {
            sim_kernel::Expr::String(text) => Some(text),
            sim_kernel::Expr::Symbol(symbol) => Some(symbol.to_string()),
            _ => None,
        })
    });
    let explanation =
        swarm_explain_expr(fabric, task_id.as_ref().and_then(|value| value.as_deref()))?;
    cx.factory().expr(explanation)
}

pub(crate) fn swarm_status_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let [swarm_value] = args.values() else {
        return Err(Error::Eval("swarm/status expects a swarm".to_owned()));
    };
    let fabric = fabric_from_value(swarm_value)?;
    swarm_status_value_for_table(cx, fabric)
}

pub(crate) fn swarm_as_fabric_value(_cx: &mut Cx, args: Args) -> Result<Value> {
    let [swarm_value] = args.values() else {
        return Err(Error::Eval("swarm/as-fabric expects a swarm".to_owned()));
    };
    Ok(swarm_value.clone())
}

pub(crate) fn swarm_as_site_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let [swarm_value] = args.values() else {
        return Err(Error::Eval("swarm/as-site expects a swarm".to_owned()));
    };
    let fabric = fabric_from_value(swarm_value)?;
    let site = Arc::new(RuntimeValueSite {
        value: swarm_value.clone(),
        address: ServerAddress::Local,
        codecs: fabric.codecs.clone(),
        kind: "swarm",
    });
    let connection = Connection::with_session(
        ServerAddress::Local,
        first_codec(&fabric.codecs),
        fabric.codecs.clone(),
        site,
        None,
        sim_lib_server::IsolationPolicy::default(),
    )?;
    cx.factory().opaque(Arc::new(connection))
}