mod interop_common;
use std::path::{Path, PathBuf};
use supercode::audit::{audit_dir, Corpus};
use supercode::session::{Session, SessionFormat};
use supercode::Role;
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
const REAL_CORPUS_FILES: [&str; 5] = [
"pi_real_corpus_plain_text.jsonl",
"pi_real_corpus_thinking.jsonl",
"pi_real_corpus_tool_call.jsonl",
"pi_real_corpus_error_retry.jsonl",
"pi_real_corpus_aborted.jsonl",
];
fn strip_native_header(native: &str) -> &str {
let idx = native
.find('\n')
.expect("native output must have a header line");
&native[idx + 1..]
}
#[test]
fn pi_real_corpus_five_sessions_load_with_expected_message_counts() {
let expected: [(&str, usize); 5] = [
("pi_real_corpus_plain_text.jsonl", 6),
("pi_real_corpus_thinking.jsonl", 4),
("pi_real_corpus_tool_call.jsonl", 8),
("pi_real_corpus_error_retry.jsonl", 4),
("pi_real_corpus_aborted.jsonl", 6),
];
assert_eq!(
expected.len(),
REAL_CORPUS_FILES.len(),
"keep the summary table and the file list in sync"
);
for (name, expected_count) in expected {
let session = Session::from_pi(fixture(name)).expect("real pi fixture must load");
assert_eq!(
session.messages.len(),
expected_count,
"{name}: unexpected message count: {:?}",
session.messages
);
assert_eq!(
session.meta.lineage.get("pi_version").map(String::as_str),
Some("3")
);
println!(
"{name}: {} messages, {} tool calls, cwd={:?} — REAL pi agent-loop output",
session.messages.len(),
session
.messages
.iter()
.map(|m| m.tool_calls().len())
.sum::<usize>(),
session.meta.cwd
);
}
}
#[test]
fn pi_real_corpus_directory_audit_has_zero_gaps_over_genuine_output() {
let tmp = std::env::temp_dir().join(format!("sc-pi-real-corpus-audit-{}", std::process::id()));
std::fs::create_dir_all(&tmp).unwrap();
for name in REAL_CORPUS_FILES {
std::fs::copy(fixture(name), tmp.join(name)).unwrap();
}
let report = audit_dir(&tmp, Corpus::Pi, None);
assert_eq!(report.files, REAL_CORPUS_FILES.len() as u64);
assert_eq!(report.parse_errors, 0);
assert!(
report.records.keys().all(|k| !k.starts_with("<line>/")
&& !k.contains("UnknownRole")
&& !k.contains("UnknownImageShape")),
"5 genuine pi-agent-loop sessions must hit zero unknown-record/role/image-shape \
buckets: {:?}",
report.records.keys().collect::<Vec<_>>()
);
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn pi_real_corpus_thinking_session_preserves_thinking_metadata() {
let session = Session::from_pi(fixture("pi_real_corpus_thinking.jsonl")).unwrap();
let thinkers: Vec<&supercode::ChatMessage> = session
.messages
.iter()
.filter(|m| m.role == Role::Assistant && m.metadata.contains_key("thinking"))
.collect();
assert_eq!(
thinkers.len(),
2,
"both assistant turns in this REAL pi session carried a genuine thinking block: {:?}",
session.messages
);
assert!(thinkers[0]
.metadata
.get("thinking")
.unwrap()
.contains("Quicksort is fast on average"));
assert!(thinkers[0]
.content
.as_deref()
.unwrap_or("")
.contains("Timsort"));
}
#[test]
fn pi_real_corpus_tool_call_session_ran_real_tools_not_scripted_results() {
let session = Session::from_pi(fixture("pi_real_corpus_tool_call.jsonl")).unwrap();
let write_call = session
.messages
.iter()
.flat_map(|m| m.tool_calls())
.find(|c| c.function.name == "write")
.expect("real write toolCall");
assert_eq!(
write_call.function.parsed_arguments().unwrap(),
serde_json::json!({"path": "hello.txt", "content": "hi"})
);
let write_result = session
.messages
.iter()
.find(|m| m.role == Role::Tool && m.tool_call_id.as_deref() == Some(write_call.id.as_str()))
.expect("paired write toolResult");
assert_eq!(
write_result.content.as_deref(),
Some("Successfully wrote 2 bytes to hello.txt"),
"this string came from pi's REAL `write` tool executing against a real scratch cwd, \
not a faux-scripted tool result"
);
let bash_call = session
.messages
.iter()
.flat_map(|m| m.tool_calls())
.find(|c| c.function.name == "bash")
.expect("real bash toolCall");
assert_eq!(
bash_call.function.parsed_arguments().unwrap(),
serde_json::json!({"command": "cat hello.txt"})
);
let bash_result = session
.messages
.iter()
.find(|m| m.role == Role::Tool && m.tool_call_id.as_deref() == Some(bash_call.id.as_str()))
.expect("paired bash toolResult");
assert_eq!(
bash_result.content.as_deref(),
Some("hi"),
"genuine stdout from a real `cat hello.txt` run against the file the `write` tool \
really created — never scripted as a faux tool result"
);
}
#[test]
fn pi_real_corpus_error_retry_session_preserves_error_then_recovery() {
let session = Session::from_pi(fixture("pi_real_corpus_error_retry.jsonl")).unwrap();
assert!(
session.raw_verbatim().contains("overloaded_error"),
"the failed attempt must still be preserved verbatim in T1 `raw`, even though it \
correctly does not become a canonical (replayable) message"
);
assert!(
!session
.messages
.iter()
.any(|m| m.metadata.get("pi_stop_reason").map(String::as_str) == Some("error")),
"an empty-content error turn has nothing to replay and is consistently excluded from \
canonical messages, exactly like every other format's loader"
);
let recovered = session
.messages
.iter()
.find(|m| {
m.role == Role::Assistant
&& m.content.as_deref() == Some("Sorry about that -- recovered. The answer is 42.")
})
.expect("the retried, successful continuation must survive as a canonical message");
assert_eq!(
recovered.metadata.get("pi_stop_reason").map(String::as_str),
Some("stop")
);
}
#[test]
fn pi_real_corpus_aborted_session_preserves_aborted_turn_and_recovers() {
let session = Session::from_pi(fixture("pi_real_corpus_aborted.jsonl")).unwrap();
let aborted = session
.messages
.iter()
.find(|m| m.metadata.get("pi_stop_reason").map(String::as_str) == Some("aborted"))
.expect("the genuinely interrupted turn must survive as its own message");
assert_eq!(
aborted.metadata.get("pi_error_message").map(String::as_str),
Some("Request was aborted"),
"this literal string comes from pi's own faux.ts `createAbortedMessage`, not fabricated"
);
assert!(aborted.content.as_deref().unwrap_or("").len() < 20_000);
let recovered_after = session.messages.iter().any(|m| {
m.role == Role::Assistant && m.content.as_deref() == Some("Okay, keeping it short instead.")
});
assert!(
recovered_after,
"a genuine turn after the abort proves the file stays usable/continuable"
);
}
#[test]
fn pi_real_corpus_native_round_trip_is_byte_lossless_for_all_five() {
for name in REAL_CORPUS_FILES {
let path = fixture(name);
let original = std::fs::read(&path).unwrap();
let session = Session::from_pi(&path).unwrap();
let native = session.to_native_jsonl();
let body = strip_native_header(&native);
assert_eq!(
body.as_bytes(),
original.as_slice(),
"{name}: native round-trip must reproduce genuine pi-agent-loop bytes exactly"
);
assert_eq!(session.raw_verbatim().as_bytes(), original.as_slice());
}
}
#[test]
fn pi_real_corpus_thinking_survives_pi_to_claude_code_export() {
let session = Session::from_pi(fixture("pi_real_corpus_thinking.jsonl")).unwrap();
let claude_jsonl = session.to_jsonl(SessionFormat::ClaudeCode).unwrap();
let mut thinking_blocks_seen = 0usize;
let mut text_blocks_seen = 0usize;
for line in claude_jsonl.lines() {
if line.trim().is_empty() {
continue;
}
let v: serde_json::Value = serde_json::from_str(line).expect("valid JSON per line");
if v.get("type").and_then(serde_json::Value::as_str) != Some("assistant") {
continue;
}
let content = v["message"]["content"]
.as_array()
.cloned()
.expect("assistant content must be a block array");
let block_types: Vec<&str> = content
.iter()
.filter_map(|b| b.get("type").and_then(serde_json::Value::as_str))
.collect();
assert_eq!(
block_types.first(),
Some(&"thinking"),
"thinking must be re-emitted, and re-emitted FIRST (matching pi's/Claude's own \
ordering), not dropped because the turn also has text: {block_types:?}"
);
assert!(
block_types.contains(&"text"),
"the fix must not have discarded the text block while restoring thinking: {block_types:?}"
);
thinking_blocks_seen += 1;
text_blocks_seen += 1;
let thinking_text = content[0]["thinking"].as_str().unwrap_or_default();
assert!(
!thinking_text.is_empty(),
"the re-emitted thinking block must carry the real captured text, not a stub"
);
}
assert_eq!(
thinking_blocks_seen, 2,
"both real thinking+text turns in this genuine pi session must survive export, not just \
a reasoning-only edge case"
);
assert_eq!(text_blocks_seen, 2);
}
#[test]
fn pi_real_corpus_all_five_convert_to_every_target_format_and_stay_replayable() {
for name in REAL_CORPUS_FILES {
let session = Session::from_pi(fixture(name)).unwrap();
let source_user_texts: Vec<String> = session
.messages
.iter()
.filter(|m| m.role == Role::User)
.filter_map(|m| m.content.clone())
.collect();
assert!(
!source_user_texts.is_empty(),
"{name}: fixture must have user turns to check replay"
);
for target in [
SessionFormat::ClaudeCode,
SessionFormat::Codex,
SessionFormat::OpenCode,
SessionFormat::Pi,
] {
let out = session
.to_jsonl(target)
.unwrap_or_else(|e| panic!("{name} -> {target:?} must exit cleanly: {e}"));
assert!(
!out.trim().is_empty(),
"{name} -> {target:?}: output must be non-empty"
);
let reloaded = Session::load_str(&out, target).unwrap_or_else(|e| {
panic!("{name} -> {target:?}: writer output must itself reload cleanly: {e}")
});
for text in &source_user_texts {
let found = reloaded.messages.iter().any(|m| {
m.content
.as_deref()
.map(|c| c.contains(text.as_str()))
.unwrap_or(false)
});
assert!(
found,
"{name} -> {target:?}: source user text {text:?} must still be present and \
replayable after round-tripping through the writer + that format's own loader"
);
}
}
}
}