use serde::Serialize;
use std::path::{Path, PathBuf};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use trusty_agents_common::compress::compress_tool_output_async_with_path;
pub(crate) async fn run_compress(tool: &str) -> anyhow::Result<()> {
init_stats_log_subscriber();
let mut input = String::new();
tokio::io::stdin().read_to_string(&mut input).await?;
let started = std::time::Instant::now();
let (compressed, path) = compress_tool_output_async_with_path(tool, &input).await;
let duration_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
log_compression_stats(tool, input.len(), compressed.len(), path.as_str());
append_compression_record(
&compression_log_path(),
tool,
input.len(),
compressed.len(),
path.as_str(),
duration_ms,
)
.await;
let mut stdout = tokio::io::stdout();
stdout.write_all(compressed.as_bytes()).await?;
stdout.flush().await?;
Ok(())
}
fn init_stats_log_subscriber() {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()),
)
.with_writer(std::io::stderr)
.try_init();
}
fn log_compression_stats(tool_name: &str, bytes_before: usize, bytes_after: usize, path: &str) {
let pct_reduction = if bytes_before > 0 {
(1.0 - bytes_after as f64 / bytes_before as f64) * 100.0
} else {
0.0
};
tracing::info!(
tool_name = %tool_name,
bytes_before,
bytes_after,
pct_reduction,
compression_path = %path,
"tool output compressed"
);
}
#[derive(Debug, Clone, Serialize)]
struct CompressionRecord {
ts: String,
session_id: Option<String>,
surface: &'static str,
surface_detail: String,
tokens_before: u32,
tokens_after: u32,
ratio: f64,
working_context_pct_after: Option<u8>,
overhead_pct_after: Option<u8>,
compaction_event: bool,
duration_ms: u64,
rounds: u32,
compression_path: String,
}
fn estimate_tokens_from_bytes(bytes: usize) -> u32 {
u32::try_from(bytes / 4).unwrap_or(u32::MAX)
}
fn compression_log_path() -> PathBuf {
match dirs::home_dir() {
Some(home) => home.join(".trusty-mpm").join("compression.jsonl"),
None => PathBuf::from(".trusty-mpm").join("compression.jsonl"),
}
}
async fn append_compression_record(
path: &Path,
tool_name: &str,
bytes_before: usize,
bytes_after: usize,
compression_path: &str,
duration_ms: u64,
) {
let tokens_before = estimate_tokens_from_bytes(bytes_before);
let tokens_after = estimate_tokens_from_bytes(bytes_after);
let ratio = if tokens_before > 0 {
f64::from(tokens_after) / f64::from(tokens_before)
} else {
0.0
};
let record = CompressionRecord {
ts: chrono::Utc::now().to_rfc3339(),
session_id: None,
surface: "tm-compress",
surface_detail: tool_name.to_string(),
tokens_before,
tokens_after,
ratio,
working_context_pct_after: None,
overhead_pct_after: None,
compaction_event: false,
duration_ms,
rounds: 1,
compression_path: compression_path.to_string(),
};
if let Some(parent) = path.parent() {
let _ = tokio::fs::create_dir_all(parent).await;
}
let line = match serde_json::to_string(&record) {
Ok(s) => format!("{s}\n"),
Err(e) => {
tracing::debug!(error = %e, "compression telemetry: serialize failed");
return;
}
};
match tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.await
{
Ok(mut f) => {
if let Err(e) = f.write_all(line.as_bytes()).await {
tracing::debug!(error = %e, path = %path.display(), "compression telemetry: write failed");
return;
}
if let Err(e) = f.flush().await {
tracing::debug!(error = %e, path = %path.display(), "compression telemetry: flush failed");
}
}
Err(e) => {
tracing::debug!(error = %e, path = %path.display(), "compression telemetry: open failed");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_compression_stats_pct_reduction_is_zero_for_empty_input() {
log_compression_stats("bash", 0, 0, "native_fallback");
}
#[test]
fn log_compression_stats_pct_reduction_can_be_negative_when_output_expands() {
log_compression_stats("bash", 10, 20, "native_fallback");
}
#[tokio::test]
async fn run_compress_shrinks_repetitive_cargo_test_output() {
let mut input = String::new();
for i in 0..50 {
input.push_str(&format!("test mod::t{i} ... ok\n"));
}
input.push_str("test result: ok. 50 passed; 0 failed\n");
let (compressed, _path) = compress_tool_output_async_with_path("cargo test", &input).await;
assert!(
compressed.len() < input.len(),
"expected compression to shrink repetitive passing-test output"
);
assert!(compressed.contains("test result"));
}
#[tokio::test]
async fn run_compress_passes_through_short_output_unchanged() {
let input = "ok\n";
let (compressed, _path) = compress_tool_output_async_with_path("bash", input).await;
assert_eq!(compressed, input);
}
#[test]
fn compression_record_serializes_to_valid_jsonl() {
let record = CompressionRecord {
ts: "2026-07-24T19:00:00Z".to_string(),
session_id: None,
surface: "tm-compress",
surface_detail: "cargo test".to_string(),
tokens_before: 100,
tokens_after: 25,
ratio: 0.25,
working_context_pct_after: None,
overhead_pct_after: None,
compaction_event: false,
duration_ms: 4,
rounds: 1,
compression_path: "native_fallback".to_string(),
};
let json = serde_json::to_string(&record).unwrap();
assert!(!json.contains('\n'), "JSONL invariant: single line");
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["surface"], "tm-compress");
assert_eq!(parsed["surface_detail"], "cargo test");
assert_eq!(parsed["ratio"], 0.25);
assert_eq!(parsed["duration_ms"], 4);
assert_eq!(parsed["rounds"], 1);
assert_eq!(parsed["compaction_event"], false);
assert_eq!(parsed["compression_path"], "native_fallback");
assert!(parsed["session_id"].is_null());
assert!(parsed["working_context_pct_after"].is_null());
assert!(parsed["overhead_pct_after"].is_null());
}
#[tokio::test]
async fn append_compression_record_creates_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("compression.jsonl");
append_compression_record(&path, "cargo test", 400, 100, "native_fallback", 7).await;
assert!(path.exists(), "compression.jsonl should be created");
let contents = tokio::fs::read_to_string(&path).await.unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 1, "exactly one line after one append");
let parsed: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
assert_eq!(parsed["surface"], "tm-compress");
assert_eq!(parsed["surface_detail"], "cargo test");
assert_eq!(parsed["tokens_before"], 100);
assert_eq!(parsed["tokens_after"], 25);
assert_eq!(parsed["ratio"], 0.25);
assert_eq!(parsed["duration_ms"], 7);
assert_eq!(parsed["rounds"], 1);
assert_eq!(parsed["compaction_event"], false);
assert_eq!(parsed["compression_path"], "native_fallback");
assert!(parsed["session_id"].is_null());
}
#[tokio::test]
async fn append_compression_record_appends() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nested").join("compression.jsonl");
append_compression_record(&path, "cargo test", 400, 100, "rtk_binary", 1).await;
append_compression_record(&path, "git diff", 200, 200, "native_fallback", 2).await;
let contents = tokio::fs::read_to_string(&path).await.unwrap();
let lines: Vec<&str> = contents.lines().collect();
assert_eq!(lines.len(), 2, "second append should not overwrite");
let p1: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
let p2: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
assert_eq!(p1["surface_detail"], "cargo test");
assert_eq!(p1["compression_path"], "rtk_binary");
assert_eq!(p2["surface_detail"], "git diff");
assert_eq!(
p2["ratio"], 1.0,
"unchanged-size compression must report ratio 1.0, not 0.0"
);
}
#[tokio::test]
async fn unwritable_log_dir_does_not_panic_or_block_the_append() {
let dir = tempfile::tempdir().unwrap();
let blocked = dir.path().join("not-a-dir");
tokio::fs::write(&blocked, b"i am a file, not a directory")
.await
.unwrap();
let path = blocked.join("compression.jsonl");
append_compression_record(&path, "cargo test", 400, 100, "native_fallback", 1).await;
assert!(
!path.exists(),
"the record must not have been written under an unwritable parent"
);
}
#[test]
fn estimate_tokens_from_bytes_divides_by_four() {
assert_eq!(estimate_tokens_from_bytes(400), 100);
assert_eq!(estimate_tokens_from_bytes(0), 0);
}
#[test]
fn compression_log_path_ends_with_trusty_mpm_compression_jsonl() {
let path = compression_log_path();
assert!(path.ends_with(".trusty-mpm/compression.jsonl"));
}
}