langcontinuation 0.1.0

Continuation-passing workflow engine for durable Rust programs and AI agent systems.
Documentation
//! Generate a static HTML visualization for one batch workflow root.
//!
//! Run with the batch feature and a Postgres database URL:
//!
//! ```sh
//! cargo run --features batch --example batch_visualizer -- \
//!   --root-run-id RUN --output run.html
//! ```

#[cfg(feature = "batch")]
use arrrg::CommandLine;
#[cfg(feature = "batch")]
use arrrg_derive::CommandLine;
#[cfg(feature = "batch")]
use langcontinuation::batch::visualizer::{
    HtmlOptions, VisualizationOptions, build_root_workflow_visualization, render_visualization_html,
};

#[cfg(feature = "batch")]
const USAGE: &str = "\
Usage: batch_visualizer [OPTIONS]

Generate a self-contained HTML artifact for one batch root workflow.
";

#[cfg(feature = "batch")]
#[derive(Clone, Debug, Default, Eq, PartialEq, CommandLine)]
struct Options {
    #[arrrg(required, "Root workflow run id to visualize.", "RUN")]
    root_run_id: String,
    #[arrrg(required, "Path to write the generated HTML artifact.", "PATH")]
    output: String,
    #[arrrg(optional, "Postgres database URL. Defaults to DATABASE_URL.", "URL")]
    database_url: Option<String>,
    #[arrrg(optional, "Maximum events to embed before failing.", "N")]
    max_events: Option<usize>,
    #[arrrg(flag, "Include full workflow/continuation/provider error strings.")]
    include_errors: bool,
    #[arrrg(
        flag,
        "Include workflow snapshots and continuation/provider request and response bodies."
    )]
    break_glass: bool,
}

#[cfg(feature = "batch")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = std::env::args().skip(1).collect::<Vec<_>>();
    let arg_refs = args.iter().map(String::as_str).collect::<Vec<_>>();
    let (options, free) = Options::from_arguments_relaxed(USAGE, &arg_refs);
    if !free.is_empty() {
        eprintln!(
            "unexpected positional arguments: {}\n\n{USAGE}",
            free.join(" ")
        );
        std::process::exit(64);
    }
    let database_url = match options
        .database_url
        .or_else(|| std::env::var("DATABASE_URL").ok())
    {
        Some(database_url) => database_url,
        None => {
            eprintln!("--database-url or DATABASE_URL is required\n\n{USAGE}");
            std::process::exit(64);
        }
    };

    let pool = sqlx::PgPool::connect(&database_url).await?;
    let document = build_root_workflow_visualization(
        &pool,
        &options.root_run_id,
        VisualizationOptions {
            include_errors: options.include_errors,
            break_glass: options.break_glass,
            max_events: options
                .max_events
                .unwrap_or_else(|| VisualizationOptions::default().max_events),
        },
    )
    .await?;
    let html = render_visualization_html(
        &document,
        HtmlOptions {
            title: Some(format!("Batch workflow {}", options.root_run_id)),
        },
    )?;
    std::fs::write(&options.output, html)?;
    println!(
        "wrote {} events for root {} to {}",
        document.overview.total_events, document.root_run_id, options.output
    );
    Ok(())
}

#[cfg(not(feature = "batch"))]
fn main() {
    eprintln!("rebuild with `--features batch` to use the batch visualizer example");
    std::process::exit(64);
}