awaken-stores 0.6.0

Storage backends (memory, file, PostgreSQL, SQLite mailbox) for Awaken agent state
Documentation
use awaken_server_contract::contract::message::Message;
use awaken_server_contract::contract::storage::{RunRecord, StorageError};
use awaken_server_contract::thread::Thread;
use bytes::Bytes;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointEntry {
    pub thread_id: String,
    pub run: RunRecord,
    pub messages: Vec<Message>,
    #[serde(default)]
    pub projected_thread: Option<Thread>,
    pub thread_seq: u64,
    pub written_at: u64,
}

pub fn encode(entry: &CheckpointEntry) -> Result<Bytes, StorageError> {
    serde_json::to_vec(entry)
        .map(Bytes::from)
        .map_err(|e| StorageError::Serialization(e.to_string()))
}

pub fn decode(bytes: &[u8]) -> Result<CheckpointEntry, StorageError> {
    serde_json::from_slice(bytes).map_err(|e| StorageError::Serialization(e.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use awaken_server_contract::contract::lifecycle::RunStatus;

    fn sample() -> CheckpointEntry {
        CheckpointEntry {
            thread_id: "t1".to_string(),
            run: RunRecord {
                run_id: "r1".to_string(),
                thread_id: "t1".to_string(),
                agent_id: "agent".to_string(),
                parent_run_id: None,
                resolution_id: None,
                activation: None,
                request: None,
                input: None,
                output: None,
                status: RunStatus::Done,
                termination_reason: None,
                final_output: None,
                error_payload: None,
                dispatch_id: None,
                session_id: None,
                transport_request_id: None,
                waiting: None,
                outcome: None,
                created_at: 1,
                started_at: None,
                finished_at: None,
                updated_at: 1,
                steps: 0,
                input_tokens: 0,
                output_tokens: 0,
                state: None,
            },
            messages: vec![Message::user("hi")],
            projected_thread: Some(Thread::with_id("t1")),
            thread_seq: 1,
            written_at: 1000,
        }
    }

    #[test]
    fn roundtrip_preserves_fields() {
        let entry = sample();
        let bytes = encode(&entry).unwrap();
        let decoded = decode(&bytes).unwrap();
        assert_eq!(decoded.thread_id, "t1");
        assert_eq!(decoded.thread_seq, 1);
        assert_eq!(decoded.messages.len(), 1);
        assert_eq!(
            decoded
                .projected_thread
                .as_ref()
                .map(|thread| thread.id.as_str()),
            Some("t1")
        );
    }
}