use super::utils::lossy_lines;
use super::{workspace_label_from_key, UnifiedMessage};
use crate::TokenBreakdown;
use serde_json::Value;
use std::collections::HashSet;
use std::io::Read;
use std::path::Path;
use tracing::warn;
const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD];
const ZSTD_CHUNK_BYTES: usize = 128 * 1024;
const MAX_TRANSCRIPT_FILE_BYTES: usize = 64 * 1024 * 1024;
const MAX_DECODED_TRANSCRIPT_BYTES: usize = 64 * 1024 * 1024;
fn read_session_bytes(path: &Path) -> Vec<u8> {
match read_session_bytes_bounded(
path,
MAX_TRANSCRIPT_FILE_BYTES,
MAX_DECODED_TRANSCRIPT_BYTES,
) {
Ok(bytes) => bytes,
Err(err) => {
warn!(
path = %path.display(),
error = %err,
"Skipping DSH transcript"
);
Vec::new()
}
}
}
fn read_session_bytes_bounded(
path: &Path,
max_file_bytes: usize,
max_decoded_bytes: usize,
) -> Result<Vec<u8>, String> {
let Ok(file) = std::fs::File::open(path) else {
return Ok(Vec::new());
};
let mut raw = Vec::new();
if let Err(err) = file.take(max_file_bytes as u64 + 1).read_to_end(&mut raw) {
return Err(format!("failed to read transcript: {err}"));
}
if raw.len() > max_file_bytes {
return Err(format!(
"transcript exceeds the {max_file_bytes} byte read ceiling (aborted at {} bytes)",
raw.len()
));
}
if raw.len() < ZSTD_MAGIC.len() || raw[..ZSTD_MAGIC.len()] != ZSTD_MAGIC {
if raw.len() > max_decoded_bytes {
return Err(format!(
"transcript exceeds the {max_decoded_bytes} byte decoded ceiling (aborted at {} bytes)",
raw.len()
));
}
return Ok(raw);
}
let Ok(mut decoder) = zstd::stream::read::Decoder::new(raw.as_slice()) else {
return Ok(Vec::new());
};
let mut decoded = Vec::new();
let mut chunk = vec![0u8; ZSTD_CHUNK_BYTES];
let mut over_ceiling = false;
loop {
let headroom = (max_decoded_bytes - decoded.len()).saturating_add(1);
let want = headroom.min(chunk.len());
let read = match decoder.read(&mut chunk[..want]) {
Ok(0) => break,
Ok(read) => read,
Err(_) => break,
};
if decoded.len().saturating_add(read) > max_decoded_bytes {
over_ceiling = true;
break;
}
decoded.extend_from_slice(&chunk[..read]);
}
if over_ceiling {
return Err(format!(
"transcript decodes past the {max_decoded_bytes} byte ceiling (aborted at {} bytes)",
decoded.len()
));
}
Ok(decoded)
}
pub fn parse_dsh_file(path: &Path) -> Vec<UnifiedMessage> {
let decoded = read_session_bytes(path);
if decoded.is_empty() {
return Vec::new();
}
let session_id_from_path = path
.parent()
.and_then(Path::file_name)
.and_then(|name| name.to_str())
.filter(|name| !name.trim().is_empty())
.unwrap_or("unknown")
.to_string();
let mut session_id: Option<String> = None;
let mut workspace_key: Option<String> = None;
let mut seed_length: i64 = 0;
let mut fallback_provider: Option<String> = None;
let mut fallback_model: Option<String> = None;
let mut messages = Vec::new();
let mut seen = HashSet::new();
let mut turn_started: HashSet<i64> = HashSet::new();
let mut pending_user_turn = false;
for line in lossy_lines(decoded.as_slice()) {
let Ok(value) = serde_json::from_str::<Value>(&line) else {
continue;
};
let Some(event_type) = value.get("type").and_then(Value::as_str) else {
continue;
};
match event_type {
"session" => {
session_id = value.get("id").and_then(Value::as_str).map(str::to_string);
workspace_key = value.get("cwd").and_then(Value::as_str).map(str::to_string);
seed_length = value
.get("seedLength")
.and_then(Value::as_i64)
.filter(|length| *length > 0)
.unwrap_or(0);
}
"request/header" => {
let config = value.pointer("/data/header/config");
fallback_provider = config
.and_then(|c| c.get("provider"))
.and_then(Value::as_str)
.map(str::to_string);
fallback_model = config
.and_then(|c| c.get("model"))
.and_then(non_empty_string)
.map(str::to_string);
}
"user/message" => {
pending_user_turn = true;
}
"assistant/message" | "compaction/summary" => {
let is_summary = event_type == "compaction/summary";
if seed_length > 0
&& value
.get("seq")
.and_then(Value::as_i64)
.is_some_and(|seq| seq < seed_length)
{
continue;
}
let Some(usage) = value.pointer("/data/usage") else {
continue;
};
let tokens = tokens_from_usage(usage);
if tokens.total() == 0 {
continue;
}
let Some(timestamp) = value.get("time").and_then(Value::as_i64) else {
continue;
};
if timestamp <= 0 {
continue;
}
let source = value.pointer("/data/message/source");
let model_id = served_model(source)
.or(fallback_model.as_deref())
.unwrap_or("unknown")
.to_string();
let provider_id = source
.and_then(|s| s.get("provider"))
.and_then(Value::as_str)
.or(fallback_provider.as_deref())
.unwrap_or("unknown")
.to_string();
let sid = session_id
.clone()
.unwrap_or_else(|| session_id_from_path.clone());
let is_turn_start = if is_summary {
false
} else {
let turn = value.pointer("/data/turn").and_then(Value::as_i64);
match turn {
Some(turn) => turn_started.insert(turn),
None => std::mem::take(&mut pending_user_turn),
}
};
let identity = value
.pointer("/data/message/id")
.and_then(Value::as_str)
.map(str::trim)
.filter(|id| !id.is_empty())
.map(|id| format!("msg:{id}"))
.or_else(|| {
if !is_summary {
return None;
}
value
.pointer("/data/compactionId")
.and_then(Value::as_str)
.map(str::trim)
.filter(|id| !id.is_empty())
.map(|id| format!("cmp:{id}"))
})
.or_else(|| {
value
.get("seq")
.and_then(Value::as_i64)
.map(|seq| format!("seq:{seq}"))
})
.unwrap_or_else(|| format!("sid:{sid}"));
let kind = if is_summary { "summary:" } else { "" };
let dedup_key = format!(
"dsh:{kind}{identity}:{timestamp}:{provider_id}:{model_id}:{}:{}:{}:{}:{}",
tokens.input,
tokens.output,
tokens.cache_read,
tokens.cache_write,
tokens.reasoning
);
if !seen.insert(dedup_key.clone()) {
continue;
}
let mut message = UnifiedMessage::new_with_dedup(
"dsh",
model_id,
provider_id,
&sid,
timestamp,
tokens,
0.0,
Some(dedup_key),
);
message.is_turn_start = is_turn_start;
if let Some(cwd) = &workspace_key {
if let Some(key) = super::normalize_workspace_key(cwd) {
let label = workspace_label_from_key(&key);
message.set_workspace(Some(key), label);
}
}
messages.push(message);
}
_ => {}
}
}
messages
}
fn served_model(source: Option<&Value>) -> Option<&str> {
source
.and_then(|value| value.pointer("/replayState/response/responseModel"))
.and_then(non_empty_string)
.or_else(|| {
source
.and_then(|value| value.get("model"))
.and_then(non_empty_string)
})
}
fn non_empty_string(value: &Value) -> Option<&str> {
value
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
}
fn tokens_from_usage(usage: &Value) -> TokenBreakdown {
let output = int_field(usage, "outputTokens").max(0);
let reasoning = int_field(usage, "reasoningTokens").max(0);
TokenBreakdown {
input: int_field(usage, "inputTokens"),
output: output.saturating_sub(reasoning),
cache_read: int_field(usage, "cacheReadTokens"),
cache_write: int_field(usage, "cacheWriteTokens"),
reasoning,
}
}
fn int_field(value: &Value, key: &str) -> i64 {
value.get(key).and_then(Value::as_i64).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
fn write_zstd_session(lines: &[&str]) -> tempfile::NamedTempFile {
let payload = lines.join("\n");
let compressed = zstd::encode_all(payload.as_bytes(), 3).unwrap();
let mut file = tempfile::NamedTempFile::new().unwrap();
std::io::Write::write_all(&mut file, &compressed).unwrap();
file
}
const TEST_FILE_CAP: usize = 64 * 1024;
const TEST_DECODED_CAP: usize = 1024 * 1024;
fn write_high_expansion_zstd(decoded_mib: usize) -> (tempfile::NamedTempFile, usize) {
let frame = zstd::encode_all(vec![b'a'; 1024 * 1024].as_slice(), 3).unwrap();
let mut payload = Vec::with_capacity(frame.len() * decoded_mib);
for _ in 0..decoded_mib {
payload.extend_from_slice(&frame);
}
let mut file = tempfile::NamedTempFile::new().unwrap();
std::io::Write::write_all(&mut file, &payload).unwrap();
let on_disk = payload.len();
(file, on_disk)
}
fn aborted_at(diagnostic: &str) -> usize {
diagnostic
.split("aborted at ")
.nth(1)
.and_then(|rest| rest.split(' ').next())
.and_then(|count| count.parse().ok())
.unwrap_or_else(|| panic!("the diagnostic must report where it stopped: {diagnostic}"))
}
#[test]
fn an_oversized_uncompressed_transcript_is_skipped_at_the_read_ceiling() {
let dir = tempfile::tempdir().unwrap();
let session_dir = dir.path().join("session-oversized");
std::fs::create_dir_all(&session_dir).unwrap();
let path = session_dir.join("session.jsonl");
let mut body = String::from(concat!(
r#"{"type":"session","id":"session-oversized","createdAt":1,"cwd":"/work"}"#,
"\n"
));
while body.len() < TEST_FILE_CAP * 4 {
body.push_str(r#"{"type":"user/message","time":1,"data":{"pad":""#);
body.push_str(&"x".repeat(512));
body.push_str("\"}}\n");
}
std::fs::write(&path, &body).unwrap();
let err = read_session_bytes_bounded(&path, TEST_FILE_CAP, TEST_DECODED_CAP)
.expect_err("a transcript past the read ceiling must not be buffered");
assert!(
err.contains(&TEST_FILE_CAP.to_string()),
"the diagnostic must name the limit: {err}"
);
assert_eq!(aborted_at(&err), TEST_FILE_CAP + 1);
assert!(
body.len() > TEST_FILE_CAP * 2,
"the payload must be well past the ceiling to be evidence"
);
}
#[test]
fn a_high_expansion_zstd_transcript_stops_at_the_decoded_ceiling() {
const DECODED_MIB: usize = 64;
let (file, on_disk) = write_high_expansion_zstd(DECODED_MIB);
assert!(
on_disk < TEST_FILE_CAP,
"the payload must sit under the {TEST_FILE_CAP} byte read ceiling for this to test \
the decoded one, but it is {on_disk} bytes"
);
let err = read_session_bytes_bounded(file.path(), TEST_FILE_CAP, TEST_DECODED_CAP)
.expect_err("a payload that expands past the ceiling must not be decoded whole");
let stopped = aborted_at(&err);
assert!(
stopped <= TEST_DECODED_CAP,
"the buffer must never pass the {TEST_DECODED_CAP} byte ceiling, but it held {stopped}"
);
assert!(
stopped + ZSTD_CHUNK_BYTES >= TEST_DECODED_CAP,
"decoding must run up to the ceiling rather than give up early, but it stopped at \
{stopped}"
);
assert!(
stopped < DECODED_MIB * 1024 * 1024 / 8,
"the decode must stop near the ceiling rather than buffer all {DECODED_MIB} MiB"
);
assert!(parse_dsh_file(file.path()).is_empty());
}
#[test]
fn the_production_decoded_ceiling_is_wired_into_the_parser() {
let (file, on_disk) =
write_high_expansion_zstd(MAX_DECODED_TRANSCRIPT_BYTES / (1024 * 1024) + 32);
assert!(
on_disk < MAX_TRANSCRIPT_FILE_BYTES,
"the payload must pass the read ceiling so the decoded one is what refuses it"
);
assert!(read_session_bytes(file.path()).is_empty());
assert!(parse_dsh_file(file.path()).is_empty());
}
#[test]
fn a_normal_transcript_is_read_byte_for_byte_under_the_ceilings() {
let rows = [
r#"{"type":"session","id":"session-normal","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"id":"m-1","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
];
let expected = rows.join("\n");
let compressed = write_zstd_session(&rows);
assert_eq!(read_session_bytes(compressed.path()), expected.as_bytes());
let dir = tempfile::tempdir().unwrap();
let plain = dir.path().join("session.jsonl");
std::fs::write(&plain, &expected).unwrap();
assert_eq!(read_session_bytes(&plain), expected.as_bytes());
let messages = parse_dsh_file(compressed.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].session_id, "session-normal");
assert_eq!(messages[0].tokens.input, 10);
assert_eq!(messages[0].tokens.output, 20);
}
#[test]
fn parses_assistant_messages_with_usage() {
let file = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"session-abc","createdAt":1786669406484,"cwd":"E:\\repo\\proj","delegationDepth":0,"agentPreset":"cordis"}"#,
r#"{"type":"turn/start","seq":4,"time":1786669450000,"data":{"turn":1}}"#,
r#"{"type":"user/message","seq":7,"time":1786669450001,"data":{"turn":1}}"#,
r#"{"type":"assistant/message","seq":301,"time":1786669454772,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[],"source":{"kind":"model","provider":"irix","model":"deepseek-v4-flash"}},"usage":{"inputTokens":130,"outputTokens":159,"cacheReadTokens":13824}}}"#,
r#"{"type":"assistant/message","seq":414,"time":1786669459063,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[],"source":{"kind":"model","provider":"irix","model":"deepseek-v4-flash"}},"usage":{"inputTokens":130,"outputTokens":159,"cacheReadTokens":13824}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 2);
let first = &messages[0];
assert_eq!(first.client, "dsh");
assert_eq!(first.model_id, "deepseek-v4-flash");
assert_eq!(first.provider_id, "irix");
assert_eq!(first.session_id, "session-abc");
assert_eq!(first.timestamp, 1786669454772);
assert_eq!(first.tokens.input, 130);
assert_eq!(first.tokens.output, 159);
assert_eq!(first.tokens.cache_read, 13824);
assert_eq!(first.tokens.cache_write, 0);
assert_eq!(first.tokens.reasoning, 0);
assert_eq!(first.cost, 0.0);
assert!(first.is_turn_start);
assert_eq!(first.workspace_key.as_deref(), Some("E:/repo/proj"));
assert_eq!(first.workspace_label.as_deref(), Some("proj"));
assert_eq!(
first.dedup_key.as_deref(),
Some("dsh:seq:301:1786669454772:irix:deepseek-v4-flash:130:159:13824:0:0")
);
assert!(!messages[1].is_turn_start);
}
#[test]
fn attributes_usage_to_the_model_reported_by_the_provider() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-served","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","seq":42,"time":1787122684043,"data":{"turn":1,"message":{"id":"m-served","source":{"kind":"model","provider":"zai-coding-cn","model":"glm-5.2","replayState":{"response":{"kind":"pi-ai","version":2,"api":"openai-completions","provider":"zai-coding-cn","model":"glm-5.2","responseModel":"glm-5.3","stopReason":"toolUse"}}}},"usage":{"inputTokens":8425,"outputTokens":207,"cacheReadTokens":576}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].model_id, "glm-5.3");
assert_eq!(messages[0].provider_id, "zai-coding-cn");
assert_eq!(
messages[0].dedup_key.as_deref(),
Some("dsh:msg:m-served:1787122684043:zai-coding-cn:glm-5.3:8425:207:576:0:0")
);
}
#[test]
fn resolves_a_floating_request_alias_to_its_concrete_served_model() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-alias","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1787122684043,"data":{"turn":1,"message":{"id":"m-alias","source":{"kind":"model","provider":"openrouter","model":"~x-ai/grok-latest","replayState":{"response":{"kind":"pi-ai","version":2,"api":"openai-completions","provider":"openrouter","model":"~x-ai/grok-latest","responseModel":"x-ai/grok-4.6","stopReason":"stop"}}}},"usage":{"inputTokens":100,"outputTokens":20}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].model_id, "x-ai/grok-4.6");
assert_eq!(messages[0].provider_id, "openrouter");
}
#[test]
fn served_model_uses_only_non_empty_string_response_values() {
for (source, expected) in [
(
serde_json::json!({
"model": " configured ",
"replayState": { "response": { "responseModel": " served " } }
}),
Some("served"),
),
(
serde_json::json!({
"model": " configured ",
"replayState": { "response": { "responseModel": " " } }
}),
Some("configured"),
),
(
serde_json::json!({
"model": " configured ",
"replayState": { "response": { "responseModel": 123 } }
}),
Some("configured"),
),
(serde_json::json!({ "model": " " }), None),
] {
assert_eq!(served_model(Some(&source)), expected);
}
assert_eq!(served_model(None), None);
}
#[test]
fn counts_compaction_summary_usage() {
let file = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"session-abc","createdAt":1786669406484,"cwd":"/work"}"#,
r#"{"type":"user/message","seq":7,"time":1786669450001,"data":{"turn":1}}"#,
r#"{"type":"assistant/message","seq":301,"time":1786669454772,"data":{"turn":1,"step":1,"message":{"source":{"provider":"minimax-cn","model":"MiniMax-M3"}},"usage":{"inputTokens":130,"outputTokens":159,"cacheReadTokens":13824}}}"#,
r#"{"type":"compaction/summary","seq":402,"time":1786669470000,"data":{"message":{"source":{"provider":"minimax-cn","model":"MiniMax-M3"}},"usage":{"inputTokens":536,"outputTokens":2436,"cacheReadTokens":41472}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 2);
let summary = &messages[1];
assert_eq!(summary.model_id, "MiniMax-M3");
assert_eq!(summary.provider_id, "minimax-cn");
assert_eq!(summary.session_id, "session-abc");
assert_eq!(summary.timestamp, 1786669470000);
assert_eq!(summary.tokens.input, 536);
assert_eq!(summary.tokens.output, 2436);
assert_eq!(summary.tokens.cache_read, 41472);
assert_eq!(summary.tokens.total(), 44_444);
assert_eq!(summary.workspace_key.as_deref(), Some("/work"));
assert!(messages[0].is_turn_start);
assert!(!summary.is_turn_start);
}
#[test]
fn compaction_summary_uses_the_concrete_served_model() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-summary-model","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"compaction/summary","seq":8,"time":1786669450002,"data":{"message":{"source":{"provider":"openrouter","model":"~x-ai/grok-latest","replayState":{"response":{"responseModel":"x-ai/grok-4.6"}}}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].model_id, "x-ai/grok-4.6");
assert!(messages[0]
.dedup_key
.as_deref()
.is_some_and(|key| key.contains(":openrouter:x-ai/grok-4.6:")));
}
#[test]
fn compaction_summary_does_not_steal_the_turn_start() {
let file = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"session-abc","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"user/message","seq":7,"time":1786669450001,"data":{"turn":2}}"#,
r#"{"type":"compaction/summary","seq":8,"time":1786669450002,"data":{"turn":2,"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
r#"{"type":"assistant/message","seq":9,"time":1786669450003,"data":{"turn":2,"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":30,"outputTokens":40}}}"#,
r#"{"type":"user/message","seq":10,"time":1786669450004,"data":{}}"#,
r#"{"type":"compaction/summary","seq":11,"time":1786669450005,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":50,"outputTokens":60}}}"#,
r#"{"type":"assistant/message","seq":12,"time":1786669450006,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":70,"outputTokens":80}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 4);
let turn_starts: Vec<bool> = messages.iter().map(|m| m.is_turn_start).collect();
assert_eq!(turn_starts, vec![false, true, false, true]);
}
#[test]
fn compaction_summary_without_usage_is_skipped() {
let file = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"session-abc","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"compaction/summary","seq":8,"time":1786669450002,"data":{"shadowedTokenCount":19962}}"#,
r#"{"type":"compaction/summary","seq":9,"time":1786669450003,"data":{"usage":{"inputTokens":0,"outputTokens":0}}}"#,
]);
assert!(parse_dsh_file(file.path()).is_empty());
}
#[test]
fn compaction_summary_inside_the_forked_seed_is_not_recounted() {
let file = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"child","createdAt":1,"cwd":"/work","seedLength":10}"#,
r#"{"type":"compaction/summary","seq":4,"time":1786669450002,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
r#"{"type":"compaction/summary","seq":11,"time":1786669450003,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":30,"outputTokens":40}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].tokens.input, 30);
}
#[test]
fn supports_cache_write_and_reasoning_buckets() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-xyz","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":10,"outputTokens":60,"cacheReadTokens":30,"cacheWriteTokens":40,"reasoningTokens":50}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
let msg = &messages[0];
assert_eq!(msg.tokens.input, 10);
assert_eq!(msg.tokens.output, 10);
assert_eq!(msg.tokens.cache_read, 30);
assert_eq!(msg.tokens.cache_write, 40);
assert_eq!(msg.tokens.reasoning, 50);
assert_eq!(msg.model_id, "deepseek-reasoner");
assert_eq!(msg.provider_id, "deepseek");
}
#[test]
fn reasoning_tokens_do_not_inflate_the_additive_output_bucket() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-reasoning","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"message":{"id":"m-1","source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":2885,"outputTokens":25,"cacheReadTokens":0,"reasoningTokens":23}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
let tokens = &messages[0].tokens;
assert_eq!(tokens.output, 2);
assert_eq!(tokens.reasoning, 23);
assert_eq!(tokens.total(), 2885 + 25);
}
#[test]
fn reasoning_equal_to_output_leaves_a_non_zero_message() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-all-reasoning","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":0,"outputTokens":31,"reasoningTokens":31}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].tokens.output, 0);
assert_eq!(messages[0].tokens.reasoning, 31);
assert_eq!(messages[0].tokens.total(), 31);
}
#[test]
fn falls_back_to_request_header_routing_and_folder_session_id() {
let file = write_zstd_session(&[
r#"{"type":"request/header","seq":11,"time":1786669450062,"data":{"header":{"config":{"provider":"irix","model":"deepseek-v4-flash"}}}}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"role":"assistant","content":[]},"usage":{"inputTokens":5,"outputTokens":7}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
let msg = &messages[0];
let folder = file
.path()
.parent()
.and_then(Path::file_name)
.and_then(|n| n.to_str())
.unwrap();
assert_eq!(msg.session_id, folder);
assert_eq!(msg.model_id, "deepseek-v4-flash");
assert_eq!(msg.provider_id, "irix");
}
#[test]
fn skips_zero_usage_and_missing_timestamp() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-zero","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":0,"outputTokens":0}}}"#,
r#"{"type":"assistant/message","data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":1,"outputTokens":1}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert!(messages.is_empty());
}
#[test]
fn dedups_identical_replayed_rows_within_a_file() {
let line = r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#;
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-dedup","createdAt":1,"cwd":"/work"}"#,
line,
line,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
}
#[test]
fn a_repeated_message_id_still_separates_calls_that_differ() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-placeholder","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"id":"{{sessionId}}","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":20,"outputTokens":8}}}"#,
r#"{"type":"assistant/message","time":1786669455000,"data":{"turn":1,"message":{"id":"{{sessionId}}","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":28,"outputTokens":2}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 2);
assert_ne!(messages[0].dedup_key, messages[1].dedup_key);
}
#[test]
fn skips_the_seeded_prefix_a_fork_inherits_from_its_parent() {
let parent = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"96cf59c9-b347-48b9-b234-a5200913ad05","createdAt":1783352134832,"cwd":"/work","delegationDepth":0}"#,
r#"{"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"message":{"id":"7ac2e3d7-d558-4b24-b71e-40fc2f42216d","source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":2885,"outputTokens":25,"cacheReadTokens":0,"reasoningTokens":23}}}"#,
]);
let child = write_zstd_session(&[
r#"{"type":"session","version":0,"id":"ada8966c-9fa3-441b-8721-37ff1e795e6a","createdAt":1783352137161,"cwd":"/work","parentSession":"96cf59c9-b347-48b9-b234-a5200913ad05","seedLength":42,"origin":"subagent","delegationDepth":1}"#,
r#"{"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"message":{"id":"7ac2e3d7-d558-4b24-b71e-40fc2f42216d","source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":2885,"outputTokens":25,"cacheReadTokens":0,"reasoningTokens":23}}}"#,
r#"{"type":"assistant/message","seq":96,"time":1786358035361,"data":{"turn":2,"message":{"id":"cdc56e00-c648-4669-92b2-7299e41cb743","source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":97,"outputTokens":39,"cacheReadTokens":2816,"reasoningTokens":34}}}"#,
]);
let parent_messages = parse_dsh_file(parent.path());
let child_messages = parse_dsh_file(child.path());
assert_eq!(parent_messages.len(), 1);
assert_eq!(child_messages.len(), 1);
assert_eq!(child_messages[0].timestamp, 1786358035361);
assert_eq!(child_messages[0].tokens.input, 97);
}
#[test]
fn a_copied_summary_shares_its_key_across_a_fork_that_lost_seedlength() {
let row = r#"{"type":"compaction/summary","seq":4,"time":1786669450002,"data":{"compactionId":"1ad33c8f-5255-4158-b607-7555f3c26cd0","message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#;
let parent = write_zstd_session(&[
r#"{"type":"session","id":"96cf59c9-b347-48b9-b234-a5200913ad05","createdAt":1,"cwd":"/work"}"#,
row,
]);
let child = write_zstd_session(&[
r#"{"type":"session","id":"ada8966c-9fa3-441b-8721-37ff1e795e6a","createdAt":2,"cwd":"/work","parentSession":"96cf59c9-b347-48b9-b234-a5200913ad05"}"#,
row,
]);
let parent_messages = parse_dsh_file(parent.path());
let child_messages = parse_dsh_file(child.path());
assert_eq!(parent_messages.len(), 1);
assert_eq!(child_messages.len(), 1);
assert_ne!(
parent_messages[0].session_id, child_messages[0].session_id,
"the two files must be distinct sessions for this to test anything"
);
assert_eq!(
parent_messages[0].dedup_key.as_deref(),
Some(
"dsh:summary:cmp:1ad33c8f-5255-4158-b607-7555f3c26cd0:1786669450002:p:m:10:20:0:0:0"
)
);
assert_eq!(
parent_messages[0].dedup_key, child_messages[0].dedup_key,
"a copied summary must collapse across the fork, or its cost is counted twice"
);
}
#[test]
fn distinct_compaction_ids_keep_otherwise_identical_summaries() {
let first = write_zstd_session(&[
r#"{"type":"session","id":"session-a","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"compaction/summary","seq":4,"time":1786669450002,"data":{"compactionId":"compact-a","message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20,"cacheReadTokens":30}}}"#,
]);
let second = write_zstd_session(&[
r#"{"type":"session","id":"session-b","createdAt":2,"cwd":"/work"}"#,
r#"{"type":"compaction/summary","seq":4,"time":1786669450002,"data":{"compactionId":"compact-b","message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20,"cacheReadTokens":30}}}"#,
]);
let first_messages = parse_dsh_file(first.path());
let second_messages = parse_dsh_file(second.path());
assert_eq!(first_messages.len(), 1);
assert_eq!(second_messages.len(), 1);
assert_eq!(
first_messages[0].dedup_key.as_deref(),
Some("dsh:summary:cmp:compact-a:1786669450002:p:m:10:20:30:0:0")
);
assert_eq!(
second_messages[0].dedup_key.as_deref(),
Some("dsh:summary:cmp:compact-b:1786669450002:p:m:10:20:30:0:0")
);
assert_ne!(
first_messages[0].dedup_key, second_messages[0].dedup_key,
"globally distinct summarize calls must both survive lane dedup"
);
}
#[test]
fn summary_without_compaction_id_keeps_seq_fallback() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"legacy-summary","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"compaction/summary","seq":9,"time":1786669450002,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(
messages[0].dedup_key.as_deref(),
Some("dsh:summary:seq:9:1786669450002:p:m:10:20:0:0:0")
);
}
#[test]
fn seeded_rows_share_the_parent_dedup_key_across_files() {
let row = r#"{"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"message":{"id":"7ac2e3d7-d558-4b24-b71e-40fc2f42216d","source":{"provider":"deepseek","model":"deepseek-reasoner"}},"usage":{"inputTokens":2885,"outputTokens":25,"reasoningTokens":23}}}"#;
let parent = write_zstd_session(&[
r#"{"type":"session","id":"96cf59c9-b347-48b9-b234-a5200913ad05","createdAt":1,"cwd":"/work"}"#,
row,
]);
let child = write_zstd_session(&[
r#"{"type":"session","id":"ada8966c-9fa3-441b-8721-37ff1e795e6a","createdAt":2,"cwd":"/work","parentSession":"96cf59c9-b347-48b9-b234-a5200913ad05"}"#,
row,
]);
let parent_messages = parse_dsh_file(parent.path());
let child_messages = parse_dsh_file(child.path());
assert_eq!(parent_messages.len(), 1);
assert_eq!(child_messages.len(), 1);
assert_ne!(parent_messages[0].session_id, child_messages[0].session_id);
assert_eq!(
parent_messages[0].dedup_key.as_deref(),
Some(
"dsh:msg:7ac2e3d7-d558-4b24-b71e-40fc2f42216d:1785730448979:deepseek:deepseek-reasoner:2885:2:0:0:23"
)
);
assert_eq!(parent_messages[0].dedup_key, child_messages[0].dedup_key);
}
#[test]
fn recovers_the_decodable_prefix_of_a_torn_trailing_frame() {
let header = zstd::encode_all(
concat!(
r#"{"type":"session","id":"session-torn","createdAt":1,"cwd":"/work"}"#,
"
"
)
.as_bytes(),
3,
)
.unwrap();
let committed = zstd::encode_all(
concat!(
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"id":"m-committed","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
"
"
)
.as_bytes(),
3,
)
.unwrap();
let torn = zstd::encode_all(
concat!(
r#"{"type":"assistant/message","time":1786669455000,"data":{"turn":2,"message":{"id":"m-torn","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":11,"outputTokens":21}}}"#,
"
"
)
.as_bytes(),
3,
)
.unwrap();
let mut payload = header;
payload.extend_from_slice(&committed);
payload.extend_from_slice(&torn[..torn.len() / 2]);
assert!(zstd::stream::decode_all(payload.as_slice()).is_err());
let mut file = tempfile::NamedTempFile::new().unwrap();
std::io::Write::write_all(&mut file, &payload).unwrap();
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].session_id, "session-torn");
assert_eq!(messages[0].tokens.input, 10);
assert_eq!(messages[0].tokens.output, 20);
}
#[test]
fn parses_the_uncompressed_session_jsonl_spelling() {
let dir = tempfile::tempdir().unwrap();
let session_dir = dir.path().join("session-plain");
std::fs::create_dir_all(&session_dir).unwrap();
let path = session_dir.join("session.jsonl");
std::fs::write(
&path,
concat!(
r#"{"type":"session","id":"session-plain","createdAt":1,"cwd":"/work"}"#,
"
",
r#"{"type":"assistant/message","time":1786669454772,"data":{"turn":1,"message":{"id":"m-plain","source":{"provider":"p","model":"m"}},"usage":{"inputTokens":12,"outputTokens":34,"reasoningTokens":4}}}"#,
"
"
),
)
.unwrap();
let messages = parse_dsh_file(&path);
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].session_id, "session-plain");
assert_eq!(messages[0].tokens.input, 12);
assert_eq!(messages[0].tokens.output, 30);
assert_eq!(messages[0].tokens.reasoning, 4);
}
#[test]
fn missing_or_corrupt_files_yield_no_messages() {
assert!(parse_dsh_file(Path::new("/nonexistent/dsh/session.jsonl.zstd")).is_empty());
let mut file = tempfile::NamedTempFile::new().unwrap();
std::io::Write::write_all(&mut file, b"this is not zstd").unwrap();
assert!(parse_dsh_file(file.path()).is_empty());
}
#[test]
fn marks_turn_start_when_no_turn_numbers_are_present() {
let file = write_zstd_session(&[
r#"{"type":"session","id":"session-noturn","createdAt":1,"cwd":"/work"}"#,
r#"{"type":"user/message","time":1,"data":{}}"#,
r#"{"type":"assistant/message","time":1786669454772,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":10,"outputTokens":20}}}"#,
r#"{"type":"assistant/message","time":1786669455000,"data":{"message":{"source":{"provider":"p","model":"m"}},"usage":{"inputTokens":11,"outputTokens":21}}}"#,
]);
let messages = parse_dsh_file(file.path());
assert_eq!(messages.len(), 2);
assert!(messages[0].is_turn_start);
assert!(!messages[1].is_turn_start);
}
}