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");
}