Luigit
repositories / smith

smith

There are many coding harnesses - but this one is fast

owned by admin

smith-core/tests/trace_snapshot.rs

Raw
//! 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(), "<session>".to_string()),
        (branch.id().to_string(), "<branch>".to_string()),
    ];
    for (index, entry) in branch.entries().iter().enumerate() {
        placeholders.push((entry.id.to_string(), format!("<entry-{index}>")));
        if let EntryKind::Message { id, .. } = entry.kind {
            placeholders.push((id.to_string(), format!("<message-{index}>")));
        }
    }
    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("<timestamp>");
            }
            redact(&line.to_string())
        })
        .collect::<Vec<_>>()
        .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));
}