thal 0.0.1

Reactive semantic runtime — molecules, reactions, and effect actors for building LLM-backed applications as dataflow programs.
Documentation
pub mod actors;
pub mod error;
pub mod llm;
pub mod oauth;
pub mod oauth_openai;
pub mod runtime;
pub mod setup;
pub mod syntax;
pub mod value;
pub mod verify;

pub use error::Error;
pub use runtime::{Reactor, ReactorHandle};
pub use value::Value;
pub use verify::VerifiedProgram;

use std::path::{Path, PathBuf};

pub fn load<P: AsRef<Path>>(path: P) -> Result<VerifiedProgram, Error> {
    let mut program = syntax::parse_file(path)?;
    merge_auto_loaded(&mut program)?;
    verify::run_passes(program)
}

pub fn load_str(source: &str) -> Result<VerifiedProgram, Error> {
    let program = syntax::parse_str(source)?;
    verify::run_passes(program)
}

/// Auto-loaded `.thal` files live in `~/.config/thal/auto/`. The setup
/// wizard writes provider declarations there so programs pick them up
/// without anyone pasting into the user's `.thal`. Tests use `load_str`,
/// which deliberately bypasses auto-load to stay hermetic.
pub fn auto_load_dir() -> Option<PathBuf> {
    if let Some(d) = std::env::var_os("XDG_CONFIG_HOME") {
        return Some(PathBuf::from(d).join("thal").join("auto"));
    }
    if let Some(home) = std::env::var_os("HOME") {
        return Some(PathBuf::from(home).join(".config").join("thal").join("auto"));
    }
    None
}

fn merge_auto_loaded(program: &mut syntax::Program) -> Result<(), Error> {
    let dir = match auto_load_dir() {
        Some(d) => d,
        None => return Ok(()),
    };
    if !dir.exists() {
        return Ok(());
    }
    let entries = std::fs::read_dir(&dir).map_err(Error::Io)?;
    let mut paths: Vec<PathBuf> = Vec::new();
    for entry in entries.flatten() {
        let p = entry.path();
        if p.extension().and_then(|s| s.to_str()) == Some("thal") {
            paths.push(p);
        }
    }
    paths.sort();
    for p in paths {
        let extra = syntax::parse_file(&p)
            .map_err(|e| Error::Parse(format!("auto-load {}: {e}", p.display())))?;
        program.types.extend(extra.types);
        program.mixins.extend(extra.mixins);
        program.molecules.extend(extra.molecules);
        program.reactions.extend(extra.reactions);
    }
    Ok(())
}