orchestral-runtime 0.1.0

Thread Runtime with concurrency, interruption, scheduling, LLM planners, actions, and API for Orchestral
Documentation
use std::collections::HashMap;
use std::fs;
use std::path::Path;

use serde_json::{json, Value};

use super::support::normalize_document_updates;

pub(super) fn apply_document_patch(
    patch_spec: &Value,
    report_path: Option<&str>,
    inspection: Option<&Value>,
    patch_candidates: Option<&Value>,
) -> Result<HashMap<String, Value>, String> {
    let updates =
        normalize_document_updates(patch_spec, report_path, inspection, patch_candidates)?;
    if updates.is_empty() {
        return Err("document patch_spec.updates is empty".to_string());
    }

    let mut updated_paths = Vec::new();
    for update in &updates {
        let path = Path::new(&update.path);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).map_err(|err| {
                format!(
                    "create document parent '{}' failed: {}",
                    parent.display(),
                    err
                )
            })?;
        }
        fs::write(path, update.content.as_bytes())
            .map_err(|err| format!("write document '{}' failed: {}", update.path, err))?;
        updated_paths.push(update.path.clone());
    }

    if let Some(report_path) = report_path {
        if !updates.iter().any(|update| update.path == report_path) {
            let report_content = synthesize_report_markdown(
                patch_spec
                    .get("summary")
                    .and_then(Value::as_str)
                    .unwrap_or("Document patch applied."),
                &updated_paths,
            );
            let path = Path::new(report_path);
            if let Some(parent) = path.parent() {
                fs::create_dir_all(parent).map_err(|err| {
                    format!(
                        "create document report parent '{}' failed: {}",
                        parent.display(),
                        err
                    )
                })?;
            }
            fs::write(path, report_content.as_bytes()).map_err(|err| {
                format!("write document report '{}' failed: {}", report_path, err)
            })?;
            updated_paths.push(report_path.to_string());
        }
    }

    Ok([
        (
            "updated_paths".to_string(),
            Value::Array(updated_paths.iter().cloned().map(Value::String).collect()),
        ),
        ("patch_count".to_string(), json!(updated_paths.len())),
        (
            "summary".to_string(),
            Value::String(
                patch_spec
                    .get("summary")
                    .and_then(Value::as_str)
                    .unwrap_or("Document patch applied.")
                    .to_string(),
            ),
        ),
    ]
    .into_iter()
    .collect())
}

fn synthesize_report_markdown(summary: &str, updated_paths: &[String]) -> String {
    let mut lines = vec!["# Patch Summary".to_string(), String::new()];
    let trimmed = summary.trim();
    if !trimmed.is_empty() {
        lines.push(trimmed.to_string());
        lines.push(String::new());
    }
    lines.push("Updated files:".to_string());
    lines.extend(updated_paths.iter().map(|path| format!("- {}", path)));
    lines.join("\n")
}