//! The trace of a small session, pinned as a snapshot (`SMH-SPEC-SPEC0001`, //! Sessions). //! //! Session, branch, entry, and message ids and entry timestamps differ every //! run; they are redacted to placeholders before snapshotting. Call ids are //! chosen by the test and stay literal. use serde_json::Value; use smith::id::ToolCallId; use smith::message::{Message, Role}; use smith_core::session::{COMPACTION_KIND, EntryContent, EntryKind, Session}; use smith_core::trace::build_trace; /// One JSON line per record, with every run-specific value replaced. fn redacted_trace(session: &Session) -> String { let branch = session.active_branch(); let mut placeholders = vec![ (session.id().to_string(), "".to_string()), (branch.id().to_string(), "".to_string()), ]; for (index, entry) in branch.entries().iter().enumerate() { placeholders.push((entry.id.to_string(), format!(""))); if let EntryKind::Message { id, .. } = entry.kind { placeholders.push((id.to_string(), format!(""))); } } let redact = |text: &str| { placeholders .iter() .fold(text.to_string(), |text, (id, placeholder)| { text.replace(id, placeholder) }) }; build_trace(session) .iter() .map(|record| { let mut line = serde_json::to_value(record).unwrap(); if line["timestamp_ms"] != 0 { line["timestamp_ms"] = Value::from(""); } redact(&line.to_string()) }) .collect::>() .join("\n") } #[test] fn trace_of_message_tool_round_trip_and_compaction() { let call = ToolCallId::from_u128(1); let failed_call = ToolCallId::from_u128(2); let mut session = Session::new(); for content in [ EntryContent::Message(Message::with_text(Role::User, "list files")), EntryContent::ToolCall { name: "ls".to_string(), input: serde_json::json!({"path": "."}), call_id: call, }, EntryContent::ToolResult { ok: true, output: "a.txt".to_string(), call_id: call, }, EntryContent::ToolCall { name: "read".to_string(), input: serde_json::json!({"path": "missing.txt"}), call_id: failed_call, }, EntryContent::ToolResult { ok: false, output: "not found".to_string(), call_id: failed_call, }, EntryContent::Message(Message::with_text(Role::Assistant, "a.txt")), EntryContent::Meta { kind: COMPACTION_KIND.to_string(), detail: serde_json::json!({}), }, EntryContent::Meta { kind: "branch".to_string(), detail: serde_json::json!({}), }, ] { session.append(&content).unwrap(); } insta::assert_snapshot!(redacted_trace(&session)); }