trace-weft 0.3.4

Rust-first, local-first observability and debugging toolkit for LLM agents
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Mutex;

lazy_static::lazy_static! {
    static ref REPLAY_CONTEXT: Mutex<Option<ReplayConfig>> = Mutex::new(None);
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReplayConfig {
    #[serde(default)]
    pub mocked_spans: HashMap<String, Value>,
    #[serde(default)]
    pub mocked_span_ids: HashMap<String, Value>,
    #[serde(default)]
    pub block_side_effects: bool,
}

impl ReplayConfig {
    pub fn load_from_env() -> Option<Self> {
        if let Ok(path_str) = std::env::var("TRACE_WEFT_REPLAY_FILE") {
            let path = Path::new(&path_str);
            if path.exists()
                && let Ok(content) = std::fs::read_to_string(path)
            {
                if let Ok(config) = serde_json::from_str::<Self>(&content) {
                    tracing::info!("Loaded TraceWeft replay config from {}", path_str);
                    return Some(config);
                } else {
                    tracing::error!("Failed to parse TraceWeft replay config at {}", path_str);
                }
            }
        }
        None
    }
}

pub fn init_replay(config: ReplayConfig) {
    if let Ok(mut ctx) = REPLAY_CONTEXT.lock() {
        *ctx = Some(config);
    }
}

pub fn get_mocked_output(span_id: &str, span_name: &str) -> Option<Value> {
    if let Ok(ctx) = REPLAY_CONTEXT.lock()
        && let Some(config) = ctx.as_ref()
    {
        return config
            .mocked_span_ids
            .get(span_id)
            .or_else(|| config.mocked_spans.get(span_name))
            .cloned();
    }
    None
}