liminal-cli 0.3.30

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! SRV-052 R1 and R2: one answer over the seat's inline bound is handed over
//! whole as a file, or refused by its size when it cannot be, and the seat
//! goes on either way.

use super::{Answers, Writer};
use serde_json::{Value, json};
use sha2::Digest as _;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};

#[derive(Clone, Default)]
struct Shared(Arc<Mutex<Vec<u8>>>);

impl Write for Shared {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.0
            .lock()
            .map_err(|_| io::Error::other("poisoned"))?
            .extend_from_slice(bytes);
        Ok(bytes.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl Shared {
    fn lines(&self) -> anyhow::Result<Vec<Value>> {
        let bytes = self
            .0
            .lock()
            .map_err(|_| anyhow::anyhow!("poisoned"))?
            .clone();
        bytes
            .split(|byte| *byte == b'\n')
            .filter(|line| !line.is_empty())
            .map(|line| Ok(serde_json::from_slice(line)?))
            .collect()
    }
}

fn writer(dir: std::path::PathBuf) -> (Writer<Shared>, Shared) {
    let output = Shared::default();
    let writer = Writer::new(
        output.clone(),
        512,
        Answers {
            bytes: 512,
            dir: Some(dir),
        },
        Arc::default(),
        Arc::default(),
        Arc::default(),
    );
    (writer, output)
}

fn answer(id: u64, text: &str) -> Value {
    json!({"jsonrpc":"2.0","id":id,"result":{"content":[{"type":"text","text":text}],"isError":false}})
}

#[test]
fn an_answer_that_cannot_be_handed_over_refuses_its_own_call_and_the_seat_goes_on()
-> anyhow::Result<()> {
    let root = tempfile::tempdir()?;
    // A directory under a regular file can never be made.
    let blocker = root.path().join("file");
    std::fs::write(&blocker, b"")?;
    let (writer, output) = writer(blocker.join("answers"));
    let big = answer(1, &"x".repeat(4096));
    let bytes = serde_json::to_vec(&big)?.len() + 1;
    writer.write(big, None, false)?;
    writer.write(answer(2, "small"), None, false)?;
    writer.check()?;
    let lines = output.lines()?;
    assert_eq!(lines.len(), 2, "{lines:?}");
    assert_eq!(lines[0]["id"], 1);
    assert_eq!(lines[0]["result"]["isError"], true);
    let words = lines[0]["result"]["content"][0]["text"]
        .as_str()
        .unwrap_or_default();
    assert!(
        words.contains(&format!(
            "the answer is {bytes} bytes, over this seat's inline bound of 512"
        )),
        "{words}"
    );
    assert!(words.contains("answer_bytes"), "{words}");
    assert_eq!(lines[1], answer(2, "small"));
    Ok(())
}

#[test]
fn a_large_answer_is_handed_over_whole_and_removed_when_the_seat_ends() -> anyhow::Result<()> {
    let root = tempfile::tempdir()?;
    let dir = root.path().join("answers");
    let (writer, output) = writer(dir.clone());
    let text = "y".repeat(10_000);
    let big = answer(7, &text);
    writer.write(big.clone(), None, false)?;
    let lines = output.lines()?;
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0]["id"], 7);
    let handed = &lines[0]["result"]["structuredContent"]["handed_over"];
    let path = std::path::PathBuf::from(handed["path"].as_str().unwrap_or_default());
    let body = std::fs::read(&path)?;
    assert_eq!(body, serde_json::to_vec(&big["result"])?);
    assert_eq!(handed["bytes"], body.len());
    assert_eq!(handed["sha256"], hex::encode(sha2::Sha256::digest(&body)));
    assert_eq!(handed["media_type"], "application/json");
    let preview = lines[0]["result"]["structuredContent"]["preview"]
        .as_str()
        .unwrap_or_default();
    assert_eq!(preview.chars().count(), 4096);
    assert!(path.starts_with(&dir));
    drop(writer);
    assert!(!path.exists(), "{} was left behind", path.display());
    Ok(())
}