supercode-core 0.2.1

A lightweight, fully-customizable AI coding agent SDK in Rust. Talks to any model via OpenRouter or any OpenAI-compatible endpoint.
Documentation
//! Acceptance test closing a coverage gap in TR-2 (`.volter/tracker/markdown/TR-2.md`)
//! flagged by adversarial review: every existing `DuplicateOutput` test
//! (`reduce_dedup.rs`) mints AND inverts against the SAME in-memory `Session`
//! (`project`/`project_messages` called directly on a hand-built message
//! vector). None of them drive `DuplicateOutput` through the path the
//! control guarantor actually re-runs: live `Agent` -> recorder(disk
//! sidecar) -> **reload from disk** -> offline `verify_log`/`invert`.
//!
//! THE GAP: TR-12's regression (`tr12_cap_supersession.rs`) proved that a
//! hash minted from an in-memory copy of `history` can silently diverge from
//! what a reloaded sidecar recomputes, once `cap_tool_output` mutates the
//! bytes behind the mint site. `DuplicateOutput`'s mint site
//! (`reduce.rs`'s TR-2 pass, run from `Agent::build_request_messages` over
//! `self.history[1..]`) sits in exactly the same position in the pipeline —
//! nothing in the existing TR-2 suite would have caught an equivalent
//! divergence for `DuplicateOutput` specifically, because none of those
//! tests ever touch a recorder, a sidecar file, or a disk reload. This test
//! fills that gap, reusing the exact harness idiom
//! `tr12_cap_supersession.rs` established (`Agent::with_parts` + a scripted
//! `Provider`, `SidecarWriter`/`SessionStore`, offline reload via
//! `Session::from_sidecar_str`, `verify_log` + `invert`).
//!
//! dev/01: a live agent calls a tool that returns the SAME distinctive
//! \>256B payload five times across turns (default
//! `protect_last_n_tool_results = 3` leaves the two OLDEST occurrences
//! eligible, so the second becomes a `DuplicateOutput` of the first). The
//! sidecar and reduction log are persisted and then reloaded from disk via
//! `SessionStore` (the exact primitive `cli/main.rs`'s
//! `show-reductions`/`convert`/`inspect` use) — never the live
//! `Agent`/in-memory `Session`. `verify_log` and `invert` both run OFFLINE
//! against that reloaded state and must pass clean, with `invert` restoring
//! the full original payload byte-exact for the duplicate.
//!
//! dev/04 (bonus, disk-path variant of `reduce_dedup.rs`'s
//! `duplicate_still_resolves_after_the_canonical_is_later_truncated_by_a7`):
//! after the duplicate is established, the policy is tightened (much lower
//! A7 trigger) and one more turn runs, causing the CANONICAL occurrence
//! (never itself a dedup target) to be independently truncated by A7. The
//! already-established `DuplicateOutput` reduction must reproduce verbatim
//! (prefix stability) and — reloaded from disk again — must still invert to
//! the full original bytes, independent of the canonical's now-reduced
//! state.

use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};

use async_trait::async_trait;
use supercode::reduce::{invert, project_messages, verify_log, ReductionKind, ReductionPolicy};
use supercode::session::Session;
use supercode::sidecar::SidecarWriter;
use supercode::store::SessionStore;
use supercode::{
    Agent, ChatMessage, ChatRequest, Config, FunctionCall, Provider, Role, ToolCall, Usage,
};

fn temp_dir(tag: &str) -> PathBuf {
    static N: AtomicUsize = AtomicUsize::new(0);
    let dir = std::env::temp_dir().join(format!(
        "supercode-tr2-guarantor-{tag}-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

/// A tool ("dup_tool") that always returns the SAME fixed payload,
/// regardless of how many times it's called — the content-hash dedup
/// candidate.
struct DupTool(String);
#[async_trait]
impl supercode::tools::Tool for DupTool {
    fn name(&self) -> &str {
        "dup_tool"
    }
    fn description(&self) -> &str {
        "x"
    }
    fn parameters(&self) -> serde_json::Value {
        serde_json::json!({"type": "object"})
    }
    async fn execute(
        &self,
        _a: serde_json::Value,
        _c: &supercode::tools::ToolContext,
    ) -> supercode::Result<String> {
        Ok(self.0.clone())
    }
}

fn tool_call_msg(id: &str) -> ChatMessage {
    ChatMessage {
        role: Role::Assistant,
        content: None,
        content_parts: None,
        tool_calls: Some(vec![ToolCall {
            id: id.to_string(),
            kind: "function".to_string(),
            function: FunctionCall {
                name: "dup_tool".to_string(),
                arguments: "{}".to_string(),
            },
        }]),
        tool_call_id: None,
        name: None,
        metadata: Default::default(),
    }
}

/// Calls `dup_tool` on the first `n_tool_calls` model turns (all within the
/// SAME `Agent::send`, since `run_loop` re-invokes the provider immediately
/// after executing each tool call); every turn after that is a plain text
/// reply with no tool calls, which is what lets a single `send` (or a
/// second, later one) end the loop and return.
struct RepeatedToolThenPlain {
    calls: AtomicUsize,
    n_tool_calls: usize,
}
#[async_trait]
impl Provider for RepeatedToolThenPlain {
    async fn complete(
        &self,
        _req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode::Result<(ChatMessage, Usage)> {
        let n = self.calls.fetch_add(1, Ordering::SeqCst);
        if n < self.n_tool_calls {
            Ok((tool_call_msg(&format!("c{n}")), Usage::default()))
        } else {
            Ok((
                ChatMessage::assistant(format!("done {n}")),
                Usage::default(),
            ))
        }
    }
}

/// dev/01 + dev/04 (guarantor-path regression, TR-2 coverage-gap closure): a
/// live agent run with a recorder + `ReductionPolicy` both active produces a
/// `DuplicateOutput` reduction; `verify_log` AND `invert` then run OFFLINE
/// against the sidecar and reduction log reloaded from disk (never the live
/// `Agent`/in-memory `Session`). Both must pass clean, and `invert` must
/// restore the full original bytes byte-exact for the duplicate. The policy
/// is then tightened and one more turn runs so the canonical occurrence is
/// independently truncated by A7 (dev/04) — the duplicate must still
/// resolve byte-exact from a fresh disk reload afterward.
#[tokio::test]
async fn duplicate_output_survives_live_agent_disk_reload_offline_verify_and_invert() {
    let dir = temp_dir("dev01-dev04");
    let store_dir = dir.join("store");
    let store = SessionStore::open(&store_dir).unwrap();
    let name = "tr2-dev01";
    let sidecar_path = store.sidecar_path(name);

    // A distinctive payload comfortably above `duplicate_output_min_bytes`
    // (256B default) but below the default A7 trigger (8,192B), so the
    // FIRST send's dedup pass fires cleanly with nothing else in play.
    let mut original = "D".repeat(4_000);
    original.push_str("TR2-DEDUP-NEEDLE");
    let pad = 5_000 - original.len();
    original.push_str(&"e".repeat(pad));
    assert_eq!(original.len(), 5_000);
    assert!(original.len() > ReductionPolicy::default().duplicate_output_min_bytes);
    assert!(original.len() < ReductionPolicy::default().tool_output_trigger_bytes);

    let config = Config::builder().cwd(dir.clone()).build();
    let mut reg = supercode::tools::ToolRegistry::new();
    reg.register(DupTool(original.clone()));
    // Five identical `dup_tool` calls: with the default
    // `protect_last_n_tool_results = 3`, the two oldest (occurrences 1 and
    // 2) are the only ones ever eligible; occurrence 1 becomes canonical,
    // occurrence 2 becomes `DuplicateOutput`. Occurrences 3-5 stay in the
    // protected tail forever (positional, not turn-count based), so no
    // further dedup candidates ever appear once the tool-call count stops
    // growing.
    let mut agent = Agent::with_parts(
        config,
        Box::new(RepeatedToolThenPlain {
            calls: AtomicUsize::new(0),
            n_tool_calls: 5,
        }),
        reg,
    );

    let empty_session = Session::from_claude_code_str("").unwrap();
    let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
    agent.set_recorder(writer);
    let policy1 = ReductionPolicy::default();
    agent.set_reduction_policy(policy1.clone());

    // A single `send` drives all 5 tool calls (each is answered immediately
    // by the next provider turn, inside `run_loop`) and returns once the
    // provider finally replies with plain text.
    let reply = agent.send("investigate").await.unwrap();
    assert!(reply.starts_with("done"), "unexpected reply: {reply}");

    let log1 = agent.reduction_log().clone();
    let dups: Vec<_> = log1
        .reductions
        .iter()
        .filter(|r| matches!(r.kind, ReductionKind::DuplicateOutput { .. }))
        .collect();
    assert_eq!(
        dups.len(),
        1,
        "exactly one of the five identical calls must be eligible to dedup \
         under the default protected tail: {:?}",
        log1.reductions
    );
    let dup_id = dups[0].id.clone();
    let dup_idx = dups[0].ptr.addr.index;
    let canonical_idx = match dups[0].kind {
        ReductionKind::DuplicateOutput { canonical, .. } => canonical.index,
        _ => unreachable!(),
    };

    // Persist the reduction log the way the CLI does (`sessions
    // show-reductions`/`convert`/`inspect` all call `store.save_reduction_log`
    // via the resume/chat path).
    store.save_reduction_log(name, &log1).unwrap();

    // --- OFFLINE from here: fresh reads from disk, no live Agent/Session ---
    {
        let sidecar_jsonl = store
            .load_sidecar(name)
            .unwrap()
            .expect("sidecar must exist on disk");
        let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
        let log_reloaded = store
            .load_reduction_log(name)
            .unwrap()
            .expect("reduction log must exist on disk");

        // The exact primitive `cli/main.rs`'s `show-reductions`/`convert`/
        // `inspect` all call before doing anything else with a reduced
        // session.
        verify_log(&log_reloaded, &sidecar)
            .expect("verify_log must pass clean against the reloaded-from-disk sidecar");

        let (final_view, reprojected_log) =
            project_messages(&sidecar.messages, &policy1, &log_reloaded);
        assert_eq!(
            reprojected_log, log_reloaded,
            "re-projecting from the reloaded sidecar with its own log must not invent new reductions"
        );

        let inverted = invert(&final_view, &log_reloaded, &sidecar)
            .expect("invert must pass clean against the reloaded-from-disk sidecar");
        assert_eq!(inverted.len(), sidecar.messages.len());
        for (a, b) in inverted.iter().zip(&sidecar.messages) {
            assert_eq!(a.role, b.role);
            assert_eq!(a.content, b.content);
        }

        // Byte-exact restore of the duplicate AND the canonical, both from
        // the disk-reloaded sidecar.
        assert_eq!(
            inverted[dup_idx].content.as_deref(),
            Some(original.as_str()),
            "invert must restore the duplicate occurrence byte-exact from disk"
        );
        assert_eq!(
            inverted[canonical_idx].content.as_deref(),
            Some(original.as_str()),
            "invert must restore the canonical occurrence byte-exact from disk"
        );
    }

    // --- dev/04: tighten the policy so the canonical (never itself a dedup
    // target) is independently truncated by A7 on a later turn; the
    // duplicate must survive prefix-stable and still resolve from disk. ---
    let policy2 = ReductionPolicy {
        tool_output_trigger_bytes: 100,
        tool_output_keep_bytes: 32,
        // Unchanged from policy1 (default 3): occurrences 3-5 stay
        // protected, so this pass introduces no NEW dedup candidates —
        // only the canonical's own truncation.
        protect_last_n_tool_results: 3,
        ..ReductionPolicy::default()
    };
    agent.set_reduction_policy(policy2.clone());

    // One more turn (provider replies plain text immediately, n=5 already
    // consumed the tool-call budget) forces a fresh `build_request_messages`
    // call under the tightened policy, minting the canonical's truncation.
    let reply2 = agent.send("one more thing").await.unwrap();
    assert!(reply2.starts_with("done"), "unexpected reply: {reply2}");

    let log2 = agent.reduction_log().clone();
    let canonical_reduction = log2
        .reductions
        .iter()
        .find(|r| r.ptr.addr.index == canonical_idx)
        .expect("the canonical must now have its own reduction");
    assert!(
        matches!(
            canonical_reduction.kind,
            ReductionKind::ToolOutputTruncated { .. }
        ),
        "the canonical must now be independently truncated by A7: {:?}",
        canonical_reduction.kind
    );
    let dup2 = log2
        .reductions
        .iter()
        .find(|r| r.id == dup_id)
        .expect("the duplicate reduction must survive the canonical's truncation");
    assert_eq!(
        dup2, dups[0],
        "the duplicate reduction must reproduce verbatim (prefix stability)"
    );

    store.save_reduction_log(name, &log2).unwrap();

    // --- OFFLINE again: fresh disk reload after the canonical's truncation ---
    let sidecar_jsonl = store
        .load_sidecar(name)
        .unwrap()
        .expect("sidecar must exist on disk");
    let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
    let log_reloaded = store
        .load_reduction_log(name)
        .unwrap()
        .expect("reduction log must exist on disk");

    verify_log(&log_reloaded, &sidecar).expect(
        "verify_log must pass clean against the reloaded-from-disk sidecar \
         even after the canonical's truncation",
    );

    let (final_view, _reprojected_log) =
        project_messages(&sidecar.messages, &policy2, &log_reloaded);
    let inverted = invert(&final_view, &log_reloaded, &sidecar)
        .expect("invert must pass clean against the reloaded-from-disk sidecar");

    // The duplicate still inverts to the full original bytes, independent of
    // the canonical having just been truncated by A7.
    assert_eq!(
        inverted[dup_idx].content.as_deref(),
        Some(original.as_str()),
        "invert must restore the duplicate byte-exact from disk even after \
         the canonical is A7-truncated"
    );
    // The canonical also still inverts byte-exact (A7 truncation is
    // reversible too).
    assert_eq!(
        inverted[canonical_idx].content.as_deref(),
        Some(original.as_str()),
        "invert must restore the now-truncated canonical byte-exact from disk"
    );

    std::fs::remove_dir_all(&dir).ok();
}