exeora-cli 0.8.4

Native Exeora CLI and local tool executor
Documentation
use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
use exeora_cli::{protocol::ToolName, tools::ToolEngine};
use serde_json::{Value, json};
use std::{fs, path::Path};
use tempfile::TempDir;
use tokio::runtime::Runtime;
use tokio_util::sync::CancellationToken;

fn execute(
    runtime: &Runtime,
    engine: &ToolEngine,
    root: &Path,
    tool: ToolName,
    arguments: Value,
) -> Value {
    runtime
        .block_on(engine.execute(root, tool, arguments, CancellationToken::new()))
        .unwrap()
}

fn fixture() -> TempDir {
    let root = TempDir::new().unwrap();
    fs::create_dir(root.path().join("src")).unwrap();
    let body = "fn indexed_symbol() { println!(\"exeora benchmark\"); }\n".repeat(128);
    for index in 0..128 {
        fs::write(root.path().join(format!("src/module-{index:03}.rs")), &body).unwrap();
    }
    root
}

#[cfg(unix)]
const INTERACTIVE_COMMAND: &str = "cat";
#[cfg(windows)]
const INTERACTIVE_COMMAND: &str = "more";

fn tool_calls(c: &mut Criterion) {
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    let engine = ToolEngine::new().unwrap();
    let root = fixture();
    let read_size = fs::metadata(root.path().join("src/module-000.rs"))
        .unwrap()
        .len();

    let mut files = c.benchmark_group("tool calls/files");
    files.throughput(Throughput::Bytes(read_size));
    files.bench_function("read_file cached 7 KiB", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::ReadFile,
                json!({"path":"src/module-000.rs"}),
            )
        })
    });
    files.throughput(Throughput::Elements(128));
    files.bench_function("list_files 128 entries", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::ListFiles,
                json!({"path":"src", "recursive":true}),
            )
        })
    });
    files.throughput(Throughput::Bytes(read_size * 128));
    files.bench_function("grep 128 files parallel", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::Grep,
                json!({"pattern":"indexed_symbol", "path":"src", "maxResults":200}),
            )
        })
    });
    files.throughput(Throughput::Bytes(64 * 1024));
    files.bench_function("write_file overwrite 64 KiB", |b| {
        let content = "x".repeat(64 * 1024);
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::WriteFile,
                json!({"path":"output.txt", "content":content}),
            )
        })
    });
    files.bench_function("edit_file unique replacement", |b| {
        b.iter_batched(
            || fs::write(root.path().join("edit.txt"), "before\nneedle\nafter\n").unwrap(),
            |_| {
                execute(
                    &runtime,
                    &engine,
                    root.path(),
                    ToolName::EditFile,
                    json!({"path":"edit.txt", "oldString":"needle", "newString":"replacement"}),
                )
            },
            BatchSize::PerIteration,
        )
    });
    files.finish();

    let mut processes = c.benchmark_group("tool calls/processes");
    processes.sample_size(20);
    processes.bench_function("run_command spawn and capture", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::RunCommand,
                json!({"command":"printf exeora"}),
            )
        })
    });
    processes.bench_function("start_command and kill_command lifecycle", |b| {
        b.iter(|| {
            let started = execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::StartCommand,
                json!({"command":INTERACTIVE_COMMAND}),
            );
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::KillCommand,
                json!({"processId":started["processId"]}),
            )
        })
    });

    let persistent = execute(
        &runtime,
        &engine,
        root.path(),
        ToolName::StartCommand,
        json!({"command":INTERACTIVE_COMMAND}),
    );
    let process_id = persistent["processId"].as_str().unwrap().to_owned();
    processes.bench_function("send_command_input", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::SendCommandInput,
                json!({"processId":process_id, "data":"ping", "newline":true}),
            )
        })
    });
    execute(
        &runtime,
        &engine,
        root.path(),
        ToolName::SendCommandInput,
        json!({"processId":process_id, "data":"x".repeat(150_000), "newline":false}),
    );
    runtime.block_on(async { tokio::time::sleep(std::time::Duration::from_millis(50)).await });
    processes.bench_function("get_command_output", |b| {
        b.iter(|| {
            execute(
                &runtime,
                &engine,
                root.path(),
                ToolName::GetCommandOutput,
                json!({"processId":process_id, "cursor":0}),
            )
        })
    });
    execute(
        &runtime,
        &engine,
        root.path(),
        ToolName::KillCommand,
        json!({"processId":process_id}),
    );
    processes.finish();

    c.bench_function("tool schema initialization", |b| {
        b.iter(|| std::hint::black_box(ToolEngine::new().unwrap()))
    });
}

criterion_group!(benches, tool_calls);
criterion_main!(benches);