sim-lib-agent 0.1.2

Agent runtime surfaces for SIM.
Documentation
use super::super::{
    model::{AgentComponent, ComponentBackend, RetrieverBackend, SandboxBackend, component_value},
    options::{
        capabilities_option, expr_option, parse_component_options, path_option, string_option,
        strings_option,
    },
};
use crate::{
    ComponentKind, FILE_READ_CAPABILITY, NETWORK_CAPABILITY, SANDBOX_CAPABILITY,
    SANDBOX_SUBPROCESS_CAPABILITY, SANDBOX_WASM_CAPABILITY, util::installed_codecs,
};
use sim_kernel::{Args, CapabilityName, Cx, Expr, Result, Symbol, Value};
use sim_lib_server::{ServerAddress, parse_duration};
use std::path::PathBuf;

pub(crate) fn retriever_vector_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "retriever/vector")?;
    let store = string_option(cx, &options, "store", "default")?;
    let corpus = strings_option(cx, &options, "corpus")?;
    let mut spec = vec![(Symbol::new("store"), Expr::String(store.clone()))];
    if let Some(corpus_expr) = expr_option(cx, &options, "corpus")? {
        spec.push((Symbol::new("corpus"), corpus_expr));
    }
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("retriever", "vector"),
            kind: ComponentKind::Retriever,
            capabilities: Vec::new(),
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec,
            backend: ComponentBackend::Retriever(RetrieverBackend::Vector { store, corpus }),
        },
    )
}

pub(crate) fn retriever_web_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "retriever/web")?;
    let endpoint = string_option(cx, &options, "endpoint", "https://example.invalid")?;
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("retriever", "web"),
            kind: ComponentKind::Retriever,
            capabilities: vec![CapabilityName::new(NETWORK_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec: vec![(Symbol::new("endpoint"), Expr::String(endpoint.clone()))],
            backend: ComponentBackend::Retriever(RetrieverBackend::Web { endpoint }),
        },
    )
}

pub(crate) fn retriever_file_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "retriever/file")?;
    let root = path_option(cx, &options, "root")?;
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("retriever", "file"),
            kind: ComponentKind::Retriever,
            capabilities: vec![CapabilityName::new(FILE_READ_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec: vec![(
                Symbol::new("root"),
                root.as_ref()
                    .map(|path| Expr::String(path.display().to_string()))
                    .unwrap_or(Expr::Nil),
            )],
            backend: ComponentBackend::Retriever(RetrieverBackend::File { root }),
        },
    )
}

pub(crate) fn retriever_db_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "retriever/db")?;
    let path = match path_option(cx, &options, "path")? {
        Some(path) => path,
        None => PathBuf::from(string_option(cx, &options, "dsn", "memory://db")?),
    };
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("retriever", "db"),
            kind: ComponentKind::Retriever,
            capabilities: vec![CapabilityName::new(FILE_READ_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec: vec![(
                Symbol::new("path"),
                Expr::String(path.display().to_string()),
            )],
            backend: ComponentBackend::Retriever(RetrieverBackend::Db { path }),
        },
    )
}

pub(crate) fn sandbox_wasm_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "sandbox/wasm")?;
    let region = string_option(cx, &options, "region", "default")?;
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("sandbox", "wasm"),
            kind: ComponentKind::Sandbox,
            capabilities: vec![CapabilityName::new(SANDBOX_WASM_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec: vec![(Symbol::new("region"), Expr::String(region.clone()))],
            backend: ComponentBackend::Sandbox(SandboxBackend::Wasm { region }),
        },
    )
}

pub(crate) fn sandbox_subprocess_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "sandbox/subprocess")?;
    let command = options
        .get("command")
        .map(|value| {
            crate::util::stringish_from_value(
                cx,
                value.clone(),
                "sandbox/subprocess :command expects a string or symbol",
            )
        })
        .transpose()?;
    let max_time = match options.get("max-time") {
        Some(value) => parse_duration(&value.object().as_expr(cx)?)?,
        None => std::time::Duration::from_secs(2),
    };
    let max_output_bytes = match options.get("max-output") {
        Some(value) => match value.object().as_expr(cx)? {
            Expr::String(text) => text.parse::<usize>().map_err(|_| {
                sim_kernel::Error::Eval(
                    "sandbox/subprocess :max-output expects an integer".to_owned(),
                )
            })?,
            Expr::Number(number) => number.canonical.parse::<usize>().map_err(|_| {
                sim_kernel::Error::Eval(
                    "sandbox/subprocess :max-output expects an integer".to_owned(),
                )
            })?,
            _ => {
                return Err(sim_kernel::Error::Eval(
                    "sandbox/subprocess :max-output expects an integer".to_owned(),
                ));
            }
        },
        None => 16 * 1024,
    };
    let mut spec = Vec::new();
    if let Some(command) = &command {
        spec.push((Symbol::new("command"), Expr::String(command.clone())));
    }
    spec.push((
        Symbol::new("max-time"),
        Expr::String(format!("{}ms", max_time.as_millis())),
    ));
    spec.push((
        Symbol::new("max-output"),
        Expr::Number(sim_kernel::NumberLiteral {
            domain: Symbol::qualified("numbers", "f64"),
            canonical: max_output_bytes.to_string(),
        }),
    ));
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("sandbox", "subprocess"),
            kind: ComponentKind::Sandbox,
            capabilities: vec![CapabilityName::new(SANDBOX_SUBPROCESS_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec,
            backend: ComponentBackend::Sandbox(SandboxBackend::Subprocess {
                command,
                max_time,
                max_output_bytes,
            }),
        },
    )
}

pub(crate) fn sandbox_capability_restricted_value(cx: &mut Cx, args: Args) -> Result<Value> {
    let options = parse_component_options(cx, args, "sandbox/capability-restricted")?;
    let allowed = if options.contains_key("allow") {
        capabilities_option(cx, &options, "allow")?
    } else {
        capabilities_option(cx, &options, "allowed")?
    };
    component_value(
        cx,
        AgentComponent {
            symbol: Symbol::qualified("sandbox", "capability-restricted"),
            kind: ComponentKind::Sandbox,
            capabilities: vec![CapabilityName::new(SANDBOX_CAPABILITY)],
            address: ServerAddress::Local,
            codecs: installed_codecs(cx),
            spec: vec![(
                Symbol::new("allow"),
                Expr::List(
                    allowed
                        .iter()
                        .map(|capability| Expr::String(capability.as_str().to_owned()))
                        .collect(),
                ),
            )],
            backend: ComponentBackend::Sandbox(SandboxBackend::CapabilityRestricted { allowed }),
        },
    )
}