orchestral-runtime 0.2.0

Thread Runtime with concurrency, interruption, scheduling, LLM planners, actions, and API for Orchestral
Documentation
use super::*;

pub(super) fn task_state_from_execution(result: &ExecutionResult) -> TaskState {
    match result {
        ExecutionResult::Completed => TaskState::Done,
        ExecutionResult::Failed { error, .. } => TaskState::Failed {
            reason: error.clone(),
            recoverable: false,
        },
        ExecutionResult::WaitingUser {
            prompt, approval, ..
        } => TaskState::WaitingUser {
            prompt: prompt.clone(),
            reason: approval
                .as_ref()
                .map(|a| WaitUserReason::Approval {
                    reason: a.reason.clone(),
                    command: a.command.clone(),
                })
                .unwrap_or(WaitUserReason::Input),
        },
        ExecutionResult::WaitingEvent { event_type, .. } => TaskState::WaitingEvent {
            event_type: event_type.clone(),
        },
    }
}

pub(super) fn restore_checkpoint(dag: &mut orchestral_core::executor::ExecutionDag, task: &Task) {
    for step_id in &task.completed_step_ids {
        dag.mark_completed(step_id.as_str());
    }
}

pub(super) fn complete_wait_step_for_resume(
    dag: &mut orchestral_core::executor::ExecutionDag,
    plan: &orchestral_core::types::Plan,
    completed_steps: &[StepId],
    event: &Event,
) {
    let target_kind = match event {
        Event::UserInput { .. } => StepKind::WaitUser,
        Event::ExternalEvent { .. } => StepKind::WaitEvent,
        _ => return,
    };

    let completed: std::collections::HashSet<&str> =
        completed_steps.iter().map(|id| id.as_str()).collect();
    let candidates = plan
        .steps
        .iter()
        .filter(|s| {
            s.kind == target_kind
                && !completed.contains(s.id.as_str())
                && wait_step_matches_resume_event(s, event)
                && s.depends_on
                    .iter()
                    .all(|dep| completed.contains(dep.as_str()))
        })
        .collect::<Vec<_>>();
    if candidates.len() == 1 {
        dag.mark_completed(candidates[0].id.as_str());
    }
}

fn wait_step_matches_resume_event(step: &orchestral_core::types::Step, event: &Event) -> bool {
    match event {
        Event::ExternalEvent { kind, .. } if step.kind == StepKind::WaitEvent => step
            .params
            .get("event_type")
            .and_then(|v| v.as_str())
            .map(|expected| expected == "*" || expected.eq_ignore_ascii_case(kind))
            .unwrap_or(true),
        Event::UserInput { .. } if step.kind == StepKind::WaitUser => true,
        _ => false,
    }
}

pub(super) fn apply_resume_event_to_working_set(ws: &mut WorkingSet, event: &Event) {
    match event {
        Event::UserInput { payload, .. } => {
            ws.set_task("resume_user_input", payload.clone());
            ws.set_task("last_event_payload", payload.clone());
        }
        Event::ExternalEvent { payload, kind, .. } => {
            ws.set_task("resume_external_event", payload.clone());
            ws.set_task("last_event_payload", payload.clone());
            ws.set_task("last_event_kind", Value::String(kind.clone()));
        }
        _ => {}
    }
}

pub(super) fn interaction_state_from_task(state: &TaskState) -> InteractionState {
    match state {
        TaskState::Done => InteractionState::Completed,
        TaskState::Failed { .. } => InteractionState::Failed,
        TaskState::WaitingUser { .. } => InteractionState::WaitingUser,
        TaskState::WaitingEvent { .. } => InteractionState::WaitingEvent,
        TaskState::Paused => InteractionState::Paused,
        TaskState::Planning | TaskState::Executing => InteractionState::Active,
    }
}