deepstrike-sdk 0.2.65

DeepStrike Rust SDK — agent framework built on deepstrike-core
Documentation
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};

use deepstrike_core::runtime::kernel::wire::canonical_digest;

static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(1);

pub trait PayloadStore: Send + Sync {
    fn persist(&self, session_id: &str, payload_ref: &str, content: &str) -> std::io::Result<()>;
    fn load(&self, session_id: &str, payload_ref: &str) -> std::io::Result<Option<String>>;
}

pub struct FilePayloadStore {
    root: PathBuf,
}

impl FilePayloadStore {
    pub fn new(root: impl Into<PathBuf>) -> Self {
        Self { root: root.into() }
    }

    fn path(&self, session_id: &str, payload_ref: &str) -> PathBuf {
        let identity = format!("{session_id}\0{payload_ref}");
        let digest = canonical_digest(identity.as_bytes());
        self.root.join(format!(
            "{}.payload",
            digest.as_str().trim_start_matches("sha256:")
        ))
    }
}

impl PayloadStore for FilePayloadStore {
    fn persist(&self, session_id: &str, payload_ref: &str, content: &str) -> std::io::Result<()> {
        fs::create_dir_all(&self.root)?;
        let target = self.path(session_id, payload_ref);
        let temporary = temporary_path(&target);
        let result = (|| {
            let mut file = fs::OpenOptions::new()
                .create_new(true)
                .write(true)
                .open(&temporary)?;
            file.write_all(content.as_bytes())?;
            file.sync_all()?;
            drop(file);
            fs::rename(&temporary, target)
        })();
        if result.is_err() {
            let _ = fs::remove_file(temporary);
        }
        result
    }

    fn load(&self, session_id: &str, payload_ref: &str) -> std::io::Result<Option<String>> {
        match fs::read_to_string(self.path(session_id, payload_ref)) {
            Ok(content) => Ok(Some(content)),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(error) => Err(error),
        }
    }
}

fn temporary_path(target: &Path) -> PathBuf {
    target.with_extension(format!(
        "tmp-{}-{}",
        std::process::id(),
        NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
    ))
}

#[cfg(test)]
mod tests {
    use super::{FilePayloadStore, PayloadStore};
    use std::fs;

    #[test]
    fn opaque_locators_are_hashed_and_session_scoped() {
        let root = std::env::temp_dir().join(format!(
            "deepstrike-payload-test-{}-{}",
            std::process::id(),
            super::NEXT_TEMP_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
        ));
        let store = FilePayloadStore::new(&root);
        store
            .persist("session-a", "../../payload", "alpha")
            .expect("persist a");
        store
            .persist("session-b", "../../payload", "beta")
            .expect("persist b");

        assert_eq!(
            store.load("session-a", "../../payload").expect("load a"),
            Some("alpha".to_string())
        );
        assert_eq!(
            store.load("session-b", "../../payload").expect("load b"),
            Some("beta".to_string())
        );
        assert_eq!(
            store.load("session-c", "../../payload").expect("load c"),
            None
        );
        assert_eq!(fs::read_dir(&root).expect("list").count(), 2);
        fs::remove_dir_all(root).expect("cleanup");
    }
}