kcode-k1-chat-chatend 0.2.0

Append-only boxed Chatend state transitions for K1 chat
Documentation
use super::*;

fn action(n: u64) -> ActionId {
    ActionId::new([n as u8; 12], n)
}

fn fails<T>(result: Result<T, TransitionError>, error: TransitionError) -> bool {
    result.err() == Some(error)
}

fn action_box(value: &ChatBox, action_id: ActionId, returned: bool) -> bool {
    match value.content() {
        BoxContent::KtoolCall {
            action_id: found, ..
        } => !returned && *found == action_id,
        BoxContent::KtoolReturn {
            action_id: found, ..
        } => returned && *found == action_id,
        _ => false,
    }
}

#[test]
fn action_identity_is_exact_and_ordered() {
    let first = ActionId::new([3; 12], 7);
    let second = ActionId::new([3; 12], 8);
    assert_eq!(first.session(), [3; 12]);
    assert_eq!(first.sequence(), 7);
    assert!(first < second);
}

#[test]
fn canonical_boxes_and_round_shapes() {
    let mut chat = Chatend::new();
    chat.accept_system("s".into()).unwrap();
    chat.accept_user("u".into()).unwrap();
    chat.accept_attachment().unwrap();
    let frontier = chat.start_round().unwrap();
    assert!(frontier == Some(chat.boxes()[2].id()));
    assert!(matches!(
        chat.boxes()[3].content(),
        BoxContent::Kennedy {
            complete: false,
            ..
        }
    ));
    chat.append_kennedy_text("a").unwrap();
    chat.collect_provider_call("n".into(), "{}".into()).unwrap();
    chat.append_kennedy_text("b").unwrap();
    assert!(chat.complete_provider_output(&[action(1)]).unwrap().len() == 1);
    chat.complete_dispatch(vec![DispatchOutcome::Terminal(Ok("r".into()))])
        .unwrap();
    let stable = chat.boxes()[3].clone();
    assert!(
        chat.boxes()
            .iter()
            .zip(1..)
            .all(|(value, id)| value.id().get() == id)
    );
    assert!(
        matches!(chat.boxes()[0].content(), BoxContent::System(text) if text == "s")
            && matches!(chat.boxes()[1].content(), BoxContent::User(text) if text == "u")
            && matches!(chat.boxes()[2].content(), BoxContent::Attachment)
            && matches!(
                chat.boxes()[3].content(),
                BoxContent::Kennedy {
                    text,
                    complete: true
                } if text == "ab"
            )
            && action_box(&chat.boxes()[4], action(1), false)
            && action_box(&chat.boxes()[5], action(1), true)
    );
    chat.start_round().unwrap();
    chat.collect_provider_call("only".into(), "[]".into())
        .unwrap();
    chat.complete_provider_output(&[action(2)]).unwrap();
    assert!(matches!(
        chat.boxes()[6].content(),
        BoxContent::Kennedy {
            text,
            complete: true
        } if text.is_empty()
    ));
    chat.complete_dispatch(vec![DispatchOutcome::Pending])
        .unwrap();
    chat.start_round().unwrap();
    chat.append_kennedy_text("text").unwrap();
    assert!(chat.accept_system("late".into()).unwrap().is_none());
    assert!(chat.complete_provider_output(&[]).unwrap().is_empty());
    assert!(matches!(
        chat.boxes().last().unwrap().content(),
        BoxContent::System(text) if text == "late"
    ));
    assert!(chat.boxes()[3] == stable);
}

#[test]
fn barrier_and_async_acceptance_order() {
    let mut chat = Chatend::new();
    chat.start_round().unwrap();
    chat.collect_provider_call("prior".into(), "{}".into())
        .unwrap();
    chat.complete_provider_output(&[action(1)]).unwrap();
    chat.complete_dispatch(vec![DispatchOutcome::Pending])
        .unwrap();
    chat.start_round().unwrap();
    let held = chat.boxes().len();
    assert!(chat.accept_user("queued".into()).unwrap().is_none());
    assert!(
        chat.accept_async_return(action(1), Ok("prior".into()))
            .unwrap()
            .is_none()
    );
    assert!(chat.boxes().len() == held);
    chat.collect_provider_call("one".into(), "1".into())
        .unwrap();
    chat.collect_provider_call("two".into(), "2".into())
        .unwrap();
    chat.complete_provider_output(&[action(2), action(3)])
        .unwrap();
    let synchronous = vec![DispatchOutcome::Terminal(Ok("sync".into())); 2];
    chat.complete_dispatch(synchronous).unwrap();
    assert!(
        action_box(&chat.boxes()[5], action(2), true)
            && action_box(&chat.boxes()[6], action(3), true)
            && matches!(chat.boxes()[7].content(), BoxContent::User(text) if text == "queued")
            && action_box(&chat.boxes()[8], action(1), true)
    );
    chat.start_round().unwrap();
    chat.collect_provider_call("x".into(), "x".into()).unwrap();
    chat.collect_provider_call("y".into(), "y".into()).unwrap();
    chat.complete_provider_output(&[action(4), action(5)])
        .unwrap();
    chat.complete_dispatch(vec![DispatchOutcome::Pending; 2])
        .unwrap();
    let first = chat.boxes().len();
    chat.accept_async_return(action(5), Ok("five".into()))
        .unwrap();
    chat.accept_async_return(action(4), Ok("four".into()))
        .unwrap();
    assert!(
        action_box(&chat.boxes()[first], action(5), true)
            && action_box(&chat.boxes()[first + 1], action(4), true)
    );
}

#[test]
fn rejections_are_transactional() {
    let mut chat = Chatend::new();
    let orphan = chat.accept_async_return(action(9), Ok("x".into()));
    assert!(fails(orphan, TransitionError::UnknownAction));
    chat.start_round().unwrap();
    chat.collect_provider_call("a".into(), "a".into()).unwrap();
    chat.collect_provider_call("b".into(), "b".into()).unwrap();
    let before = chat.boxes().to_vec();
    assert!(fails(
        chat.complete_provider_output(&[action(1)]),
        TransitionError::ActionCount
    ));
    assert!(chat.boxes() == before.as_slice());
    let duplicate_actions = chat.complete_provider_output(&[action(1), action(1)]);
    assert!(fails(duplicate_actions, TransitionError::DuplicateAction));
    assert!(chat.boxes() == before.as_slice());
    chat.complete_provider_output(&[action(1), action(2)])
        .unwrap();
    let before_dispatch = chat.boxes().to_vec();
    assert!(fails(
        chat.complete_dispatch(vec![DispatchOutcome::Pending]),
        TransitionError::OutcomeCount
    ));
    assert!(chat.boxes() == before_dispatch.as_slice());
    chat.complete_dispatch(vec![DispatchOutcome::Pending; 2])
        .unwrap();
    assert!(
        chat.accept_async_return(action(1), Ok("done".into()))
            .unwrap()
            .is_some()
    );
    let duplicate_return = chat.accept_async_return(action(1), Ok("again".into()));
    assert!(fails(duplicate_return, TransitionError::DuplicateReturn));
}