orchestral-runtime 0.2.0

Thread Runtime with concurrency, interruption, scheduling, LLM planners, actions, and API for Orchestral
Documentation
use std::sync::Arc;

use orchestral_core::executor::ActionPreflightHook;
use serde_json::Value;

use crate::action::test_hooks::forced_action_failure_reason;

pub(crate) fn default_action_preflight_hook() -> ActionPreflightHook {
    Arc::new(|action_name: &str, params: &Value| action_preflight_error(action_name, params))
}

pub(super) fn action_preflight_error(action_name: &str, params: &Value) -> Option<String> {
    if let Some(reason) = forced_action_failure_reason(action_name) {
        return Some(reason);
    }
    if action_name != "shell" {
        return None;
    }
    let obj = params.as_object()?;
    let command = obj.get("command")?.as_str()?.trim();
    if command.is_empty() {
        return None;
    }
    let command_token = shell_command_first_token(command);
    let command_for_path_check = command_token.unwrap_or(command);

    if command_for_path_check.contains('/')
        && !std::path::Path::new(command_for_path_check).exists()
    {
        return Some(format!(
            "command path '{}' does not exist",
            command_for_path_check
        ));
    }

    let command_name = std::path::Path::new(command_for_path_check)
        .file_name()
        .and_then(|v| v.to_str())
        .unwrap_or(command_for_path_check);
    let is_python = command_name.starts_with("python");
    if !is_python {
        return None;
    }

    let args = obj.get("args").and_then(|v| v.as_array())?;
    let script = args.iter().find_map(|value| {
        value
            .as_str()
            .map(str::trim)
            .filter(|arg| !arg.is_empty() && arg.ends_with(".py"))
    })?;

    let path = std::path::Path::new(script);
    let base_dir = resolve_preflight_base_dir(obj);
    let abs = if path.is_absolute() {
        path.to_path_buf()
    } else {
        base_dir.join(path)
    };
    if abs.exists() {
        None
    } else {
        Some(format!("python script '{}' does not exist", abs.display()))
    }
}

fn shell_command_first_token(command: &str) -> Option<&str> {
    command.split_whitespace().next().map(str::trim)
}

fn resolve_preflight_base_dir(obj: &serde_json::Map<String, Value>) -> std::path::PathBuf {
    if let Some(workdir) = obj.get("workdir").and_then(|v| v.as_str()) {
        let workdir = workdir.trim();
        if !workdir.is_empty() {
            let workdir_path = std::path::Path::new(workdir);
            if workdir_path.is_absolute() {
                return workdir_path.to_path_buf();
            }
            if let Ok(cwd) = std::env::current_dir() {
                return cwd.join(workdir_path);
            }
        }
    }
    std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."))
}

pub(super) fn deterministic_action_error_reason(message: &str) -> Option<&'static str> {
    let lower = message.to_ascii_lowercase();
    if lower.contains("failed preflight") {
        return Some("preflight_rejected");
    }
    if lower.contains("no such file or directory") || lower.contains("can't open file") {
        return Some("missing_file_or_path");
    }
    if lower.contains("does not exist") {
        return Some("missing_file_or_path");
    }
    if lower.contains("command not found") {
        return Some("missing_command");
    }
    None
}