laser-sdk 0.5.2

LaserData SDK over Apache Iggy: typed streaming, operational and lakehouse query, materialization destinations, key-value state, copy-on-write forks, and an optional agent runtime with the Agent Data Exchange Protocol (AGDX).
Documentation
use crate::harness;
use iggy::prelude::Identifier;
use laser_sdk::prelude::full::*;
use std::sync::LazyLock;

static SUPPORT_TURNS: LazyLock<Identifier> =
    LazyLock::new(|| Identifier::named("support.turns").expect("a valid identifier"));
static SUPPORT_REPLIES: LazyLock<Identifier> =
    LazyLock::new(|| Identifier::named("support.replies").expect("a valid identifier"));

#[tokio::test]
#[serial_test::serial(integration)]
async fn given_per_user_sessions_when_messaging_then_should_isolate_each_user() {
    let laser = harness::laser().await;
    let sessions = laser.sessions();
    let alice = sessions.create("alice");
    let bob = sessions.create("bob");

    for (session, text) in [(&alice, "alice message"), (&bob, "bob message")] {
        session
            .append(SessionTurnKind::Instruction, text.as_bytes().to_vec())
            .await
            .expect("the user turn should be appended");
    }

    let turns = harness::eventually(|| async {
        let turns = alice
            .context()
            .await
            .expect("assembling alice's context should succeed");
        (!turns.is_empty()).then_some(turns)
    })
    .await;

    assert_eq!(turns.len(), 1);
    assert_eq!(turns[0].text(), "alice message");
    assert_eq!(
        sessions.create("alice").conversation(),
        alice.conversation()
    );
    assert_ne!(alice.conversation(), bob.conversation());
}

#[tokio::test]
#[serial_test::serial(integration)]
async fn given_a_session_when_appending_typed_turns_then_context_should_read_them_back_with_kinds()
{
    let laser = harness::laser().await;
    let sessions = laser.sessions();
    let session = sessions.create("agent-42");
    let script = [
        (SessionTurnKind::Instruction, "summarize the ticket"),
        (SessionTurnKind::ToolCall, "search(ticket=42)"),
        (SessionTurnKind::ToolResult, "3 comments found"),
        (SessionTurnKind::ModelResponse, "it looks like a login bug"),
        (SessionTurnKind::HumanInput, "approved"),
        (SessionTurnKind::Response, "it is a login bug"),
    ];

    for (kind, text) in script {
        session
            .append(kind, text.as_bytes().to_vec())
            .await
            .expect("appending the turn should succeed");
    }

    let turns = harness::eventually(|| async {
        let turns = session
            .context()
            .await
            .expect("assembling the session's context should succeed");
        (turns.len() == script.len()).then_some(turns)
    })
    .await;
    let read_back: Vec<(SessionTurnKind, String)> =
        turns.iter().map(|turn| (turn.kind, turn.text())).collect();
    let expected: Vec<(SessionTurnKind, String)> = script
        .iter()
        .map(|(kind, text)| (*kind, (*text).to_owned()))
        .collect();
    assert_eq!(read_back, expected);

    assert_eq!(
        sessions.open(session.conversation()).conversation(),
        session.conversation()
    );
    assert_ne!(
        sessions.start().conversation(),
        sessions.start().conversation()
    );
}

#[tokio::test]
#[serial_test::serial(integration)]
async fn given_a_checkpoint_when_more_turns_are_appended_then_state_at_and_replay_should_split_history_there()
 {
    let laser = harness::laser().await;
    let session = laser.sessions().start();

    session
        .append(SessionTurnKind::Instruction, b"first".to_vec())
        .await
        .expect("appending the first turn should succeed");
    session
        .append(SessionTurnKind::ModelResponse, b"second".to_vec())
        .await
        .expect("appending the second turn should succeed");

    let checkpoint = harness::eventually(|| async {
        let checkpoint = session
            .checkpoint()
            .await
            .expect("reading a checkpoint should succeed");
        let seen = session
            .state_at(checkpoint.clone(), 0usize, |count, _| count + 1)
            .await
            .expect("folding up to the checkpoint should succeed");
        (seen == 2).then_some(checkpoint)
    })
    .await;

    session
        .append(SessionTurnKind::ToolResult, b"third".to_vec())
        .await
        .expect("appending the third turn should succeed");
    let after = harness::eventually(|| async {
        let turns = session
            .turns_since(checkpoint.clone())
            .await
            .expect("replay should read forward from the checkpoint");
        (turns.len() == 1).then_some(turns)
    })
    .await;
    assert_eq!(after[0].kind, SessionTurnKind::ToolResult);
    assert_eq!(after[0].text(), "third");

    let before: Vec<String> = session
        .turns_at(checkpoint.clone())
        .await
        .expect("turns up to the checkpoint should read")
        .iter()
        .map(SessionTurn::text)
        .collect();
    assert_eq!(before, vec!["first".to_owned(), "second".to_owned()]);

    let persisted = serde_json::to_string(&checkpoint).expect("a checkpoint serializes");
    let restored: Checkpoint = serde_json::from_str(&persisted).expect("a checkpoint deserializes");
    let replayed = session
        .replay(restored, Vec::new(), |mut acc: Vec<String>, turn| {
            acc.push(turn.text());
            acc
        })
        .await
        .expect("replay from a restored checkpoint should succeed");
    assert_eq!(replayed, vec!["third".to_owned()]);
}

#[tokio::test]
#[serial_test::serial(integration)]
async fn given_a_custom_layout_when_turns_ride_their_own_stream_and_topic_then_should_read_and_checkpoint_there()
 {
    let laser = harness::laser().await;
    let stream = format!(
        "{}-support",
        laser.default_stream().expect("a default stream")
    );
    laser
        .stream(&stream)
        .ensure()
        .await
        .expect("the support stream should be created");
    for topic in ["support.turns", "support.replies"] {
        laser
            .stream(&stream)
            .topic(topic)
            .ensure(2)
            .await
            .expect("the support topic should be created");
    }
    let config = SessionConfig::new()
        .stream(stream.clone())
        .topic(
            SessionTurnKind::Instruction,
            AgentTopic::Custom(&SUPPORT_TURNS),
        )
        .topic(
            SessionTurnKind::Response,
            AgentTopic::Custom(&SUPPORT_REPLIES),
        )
        .memory_namespace("support.sessions")
        .context_turns(10);
    let session = laser
        .sessions_with(config)
        .expect("a layout with one topic per kind is valid")
        .create("ticket-7");

    session
        .append(SessionTurnKind::Instruction, b"where is my order".to_vec())
        .await
        .expect("appending on the custom topic should succeed");
    let turns = harness::eventually(|| async {
        let turns = session
            .context()
            .await
            .expect("assembling the custom-layout context should succeed");
        (turns.len() == 1).then_some(turns)
    })
    .await;
    assert_eq!(turns[0].kind, SessionTurnKind::Instruction);
    assert_eq!(turns[0].message.topic, "support.turns");

    let untouched = laser
        .sessions()
        .create("ticket-7")
        .context()
        .await
        .expect("the default layout should still read");
    assert!(untouched.is_empty());

    let checkpoint = harness::eventually(|| async {
        let checkpoint = session
            .checkpoint()
            .await
            .expect("a checkpoint over a custom topic should read");
        (checkpoint.topic_offsets("support.turns").is_some()
            && session
                .turns_at(checkpoint.clone())
                .await
                .expect("turns up to the checkpoint should read")
                .len()
                == 1)
            .then_some(checkpoint)
    })
    .await;
    session
        .append(SessionTurnKind::Response, b"shipped yesterday".to_vec())
        .await
        .expect("appending the response should succeed");
    let since = harness::eventually(|| async {
        let turns = session
            .turns_since(checkpoint.clone())
            .await
            .expect("turns after the checkpoint should read");
        (turns.len() == 1).then_some(turns)
    })
    .await;
    assert_eq!(since[0].kind, SessionTurnKind::Response);
}