kcode-k1-chat-codex-state 0.5.0

Codex turn state over current K1 Chat State
Documentation
use kcode_k1_chat_codex_codec::{BoxValue, Codec};
use kcode_k1_chat_codex_state::{
    ConversationState, DispatchedToolCall, RestartError, ResultView, Status, ToolResult,
    ToolResultStatus,
};
use kcode_k1_codex_adapter::{BoxCodec, ShimItem, ShimOutput, ToolCall};
use serde_json::{Value, json};

fn accept(state: &mut ConversationState, text: &str) {
    state
        .accept(
            "User Message".into(),
            text.into(),
            String::new(),
            String::new(),
        )
        .unwrap();
}

fn native(wrapper: &str) -> BoxValue {
    Codec.tool_call_box(&ToolCall {
        call_id: "provider-private".into(),
        name: "call_ktool".into(),
        arguments: Value::String(wrapper.into()),
    })
}

fn start(state: &mut ConversationState) -> u64 {
    state.begin().unwrap().unwrap().job
}

fn result(call: &DispatchedToolCall) -> ToolResult {
    ToolResult::new(
        call.call.call_id(),
        call.call_box_id,
        call.call.tool().into(),
        call.call.tool_version().into(),
        ToolResultStatus::Ok,
        json!({"complete": true}),
        ResultView::OneLine("complete".into()),
    )
    .unwrap()
}

fn complete(state: &mut ConversationState, job: u64, text: &str) {
    state
        .complete(
            job,
            ShimOutput {
                items: vec![ShimItem::Text(text.into())],
            },
        )
        .unwrap();
}

#[test]
fn stages_allocate_in_order_and_fail_transactionally() {
    let mut state = ConversationState::new();
    accept(&mut state, "ask");
    let job = start(&mut state);
    let before = state.boxes().to_vec();
    assert!(
        state
            .prepare_stage(job, "bad".into(), vec![native("{")])
            .is_err()
    );
    assert!(
        state
            .prepare_stage(job, "bad".into(), vec![BoxValue::History("x".into())])
            .is_err()
    );
    assert_eq!(state.boxes(), before);

    let calls = state
        .prepare_stage(
            job,
            "thinking".into(),
            vec![
                native(r#"{"name":"first","arguments":{"nested":[1,{"ok":true}]}}"#),
                native(r#"{"name":"second","arguments":{}}"#),
            ],
        )
        .unwrap();
    assert_eq!(calls[0].call.call_id().to_string(), "c1");
    assert_eq!(calls[1].call.call_id().to_string(), "c2");
    assert_eq!(
        calls[0].call.arguments(),
        &json!({"nested":[1,{"ok":true}]})
    );
}

#[test]
fn steer_is_finite_exact_once_and_leaves_later_arrivals() {
    let mut state = ConversationState::new();
    accept(&mut state, "ask");
    let job = start(&mut state);
    let call = state
        .prepare_stage(
            job,
            String::new(),
            vec![native(r#"{"name":"work","arguments":{}}"#)],
        )
        .unwrap()
        .remove(0);
    state.accept_tool_return(result(&call)).unwrap();
    let first = state.prepare_steer(job).unwrap().unwrap();
    assert_eq!(first.values().len(), 1);
    state
        .accept(
            "Future".into(),
            "later".into(),
            "future/v1".into(),
            "hidden".into(),
        )
        .unwrap();
    let contender = state.prepare_steer(job).unwrap().unwrap();
    state.commit_steer(contender).unwrap();
    assert!(state.commit_steer(first).is_err());
    let later = state.prepare_steer(job).unwrap().unwrap();
    assert_eq!(later.values().len(), 1);
    state.commit_steer(later).unwrap();
    complete(&mut state, job, "done");
    assert_eq!(state.status(), Status::Quiet);
}

#[test]
fn recovery_reprojects_history_and_continues_call_ids() {
    let mut state = ConversationState::new();
    accept(&mut state, "ask");
    let job = start(&mut state);
    state
        .prepare_stage(
            job,
            String::new(),
            vec![native(r#"{"name":"first","arguments":{}}"#)],
        )
        .unwrap();
    complete(&mut state, job, "answer");

    let mut recovered = ConversationState::recover(state.boxes().to_vec(), true).unwrap();
    let start = recovered.begin().unwrap().unwrap();
    assert!(!start.boxes.is_empty());
    let calls = recovered
        .prepare_stage(
            start.job,
            String::new(),
            vec![native(r#"{"name":"second","arguments":{}}"#)],
        )
        .unwrap();
    assert_eq!(calls[0].call.call_id().to_string(), "c2");
}

#[test]
fn failures_restart_only_before_a_call_wave_and_stale_jobs_are_safe() {
    let mut state = ConversationState::new();
    accept(&mut state, "ask");
    let job = start(&mut state);
    let before = state.boxes().to_vec();
    assert!(
        state
            .prepare_stage(job + 1, String::new(), Vec::new())
            .is_err()
    );
    assert_eq!(state.boxes(), before);
    state.fail(job, "before launch".into(), true);
    assert_eq!(
        state.status(),
        Status::Stalled {
            message: "before launch".into(),
            restartable: true
        }
    );
    state.restart().unwrap();

    let retry = start(&mut state);
    state
        .prepare_stage(
            retry,
            String::new(),
            vec![native(r#"{"name":"work","arguments":{}}"#)],
        )
        .unwrap();
    state.fail(retry, "after call".into(), true);
    assert_eq!(state.restart(), Err(RestartError::NotRestartable));
}

#[test]
fn completion_rejects_nonterminal_shapes_without_mutation() {
    let mut state = ConversationState::new();
    accept(&mut state, "ask");
    let job = start(&mut state);
    let before = state.boxes().to_vec();
    let output = ShimOutput {
        items: vec![ShimItem::Box(BoxValue::History("invalid".into()))],
    };
    assert!(state.complete(job, output).is_err());
    assert_eq!(state.boxes(), before);
    complete(&mut state, job, "answer");
}