use super::*;
fn assistant_line(id: &str, input: u64, cache_read: u64, output: u64) -> String {
format!(
r#"{{"type":"assistant","message":{{"id":"{id}","role":"assistant","usage":{{"input_tokens":{input},"cache_creation_input_tokens":0,"cache_read_input_tokens":{cache_read},"output_tokens":{output}}}}}}}"#
)
}
fn write_transcript(dir: &std::path::Path, lines: &[String]) -> std::path::PathBuf {
let path = dir.join("session.jsonl");
std::fs::write(&path, format!("{}\n", lines.join("\n"))).expect("write transcript");
path
}
#[test]
fn fold_sums_one_message_once_per_id() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[
r#"{"type":"user","message":{"role":"user","content":"hi"}}"#.to_string(),
assistant_line("msg_a", 2, 100, 50),
assistant_line("msg_a", 2, 100, 50),
assistant_line("msg_a", 2, 100, 50),
assistant_line("msg_b", 4, 200, 70),
],
);
let total = fold_transcript(&path);
assert_eq!(total.messages, 2, "two distinct message ids");
assert_eq!(total.tokens_out, 120, "50 + 70, each counted once");
assert_eq!(total.tokens_in, 306, "(2+100) + (4+200)");
}
#[test]
fn fold_counts_cache_tokens_as_tokens_in() {
let dir = tempfile::tempdir().expect("temp dir");
let line = r#"{"type":"assistant","message":{"id":"m1","usage":{"input_tokens":2,"cache_creation_input_tokens":74685,"cache_read_input_tokens":27297,"output_tokens":288}}}"#;
let path = write_transcript(dir.path(), &[line.to_string()]);
let total = fold_transcript(&path);
assert_eq!(total.tokens_in, 101_984);
assert_eq!(total.tokens_out, 288);
}
#[test]
fn fold_of_a_missing_transcript_is_empty() {
let dir = tempfile::tempdir().expect("temp dir");
let total = fold_transcript(&dir.path().join("nothing-here.jsonl"));
assert_eq!(total, TranscriptUsage::default());
assert!(total.is_empty());
}
fn filler_line(bytes: usize) -> String {
let prefix = r#"{"type":"user","pad":""#;
let suffix = r#""}"#;
let pad = bytes.saturating_sub(prefix.len() + suffix.len());
format!("{prefix}{}{suffix}", "a".repeat(pad))
}
#[test]
fn fold_reads_only_the_tail_of_an_oversized_transcript() {
let dir = tempfile::tempdir().expect("temp dir");
let tail = [
assistant_line("t1", 10, 0, 5),
assistant_line("t2", 20, 0, 7),
];
let tail_bytes: u64 = tail.iter().map(|line| line.len() as u64 + 1).sum();
let path = write_transcript(
dir.path(),
&[
assistant_line("head", 1_000_000, 0, 1_000_000),
filler_line(4096),
tail[0].clone(),
tail[1].clone(),
],
);
let usage = fold_transcript_tail(&path, tail_bytes + 1);
assert_eq!(usage.messages, 2, "only the two lines inside the window");
assert_eq!(
usage.tokens_in, 30,
"the head's million is outside the window"
);
assert_eq!(usage.tokens_out, 12);
assert!(usage.truncated, "the cap stopped the fold short");
}
#[test]
fn fold_transcript_bounds_a_transcript_larger_than_the_cap() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[
assistant_line("head", 1_000_000, 0, 1_000_000),
filler_line(TRANSCRIPT_TAIL_BYTES as usize),
assistant_line("t1", 10, 0, 5),
assistant_line("t2", 20, 0, 7),
],
);
let usage = fold_transcript(&path);
assert_eq!(usage.messages, 2);
assert_eq!(usage.tokens_in, 30);
assert_eq!(usage.tokens_out, 12);
assert!(usage.truncated);
}
#[test]
fn fold_of_a_transcript_within_the_cap_is_not_truncated() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(dir.path(), &[assistant_line("m1", 10, 0, 5)]);
let usage = fold_transcript(&path);
assert!(!usage.truncated);
assert_eq!(usage.messages, 1);
}
#[test]
fn fold_skips_a_malformed_line_and_keeps_the_valid_total() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[
assistant_line("m1", 10, 0, 5),
r#"{"type":"assistant","message":{"usage":{"output_to"#.to_string(),
assistant_line("m2", 20, 0, 7),
],
);
let total = fold_transcript(&path);
assert_eq!(total.messages, 2);
assert_eq!(total.tokens_in, 30);
assert_eq!(total.tokens_out, 12);
}
#[test]
fn first_turn_reads_the_opening_assistant_turn() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[
r#"{"type":"user","message":{"role":"user","content":"hi"}}"#.to_string(),
assistant_line("msg_a", 3, 100, 50),
assistant_line("msg_b", 4, 900, 70),
],
);
assert_eq!(first_turn_context_tokens(&path), Some(103));
}
#[test]
fn first_turn_counts_cache_tokens() {
let dir = tempfile::tempdir().expect("temp dir");
let line = r#"{"type":"assistant","message":{"id":"m1","usage":{"input_tokens":4,"cache_creation_input_tokens":74685,"cache_read_input_tokens":27297,"output_tokens":288}}}"#;
let path = write_transcript(dir.path(), &[line.to_string()]);
assert_eq!(first_turn_context_tokens(&path), Some(101_986));
}
#[test]
fn first_turn_of_a_missing_transcript_is_none() {
let dir = tempfile::tempdir().expect("temp dir");
assert_eq!(
first_turn_context_tokens(&dir.path().join("absent.jsonl")),
None
);
}
#[test]
fn first_turn_skips_a_malformed_line() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[
r#"{"type":"assistant","message":{"usage":{"input_to"#.to_string(),
assistant_line("m2", 20, 5, 7),
],
);
assert_eq!(first_turn_context_tokens(&path), Some(25));
}
#[test]
fn first_turn_of_a_transcript_with_no_assistant_turn_is_none() {
let dir = tempfile::tempdir().expect("temp dir");
let path = write_transcript(
dir.path(),
&[r#"{"type":"user","message":{"role":"user","content":"hi"}}"#.to_string()],
);
assert_eq!(first_turn_context_tokens(&path), None);
}
#[test]
fn first_turn_stops_at_the_head_cap() {
let dir = tempfile::tempdir().expect("temp dir");
let filler = r#"{"type":"user","message":{"role":"user","content":"padding padding padding"}}"#;
let mut lines: Vec<String> = (0..40).map(|_| filler.to_string()).collect();
lines.push(assistant_line("late", 11, 0, 3));
let path = write_transcript(dir.path(), &lines);
assert_eq!(first_turn_context_tokens(&path), Some(11));
assert_eq!(first_turn_context_tokens_within(&path, 200), None);
}