haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Bounded newline frames, validated before they reach the registry.
use crate::serve::storage::Failure;
use serde::Deserialize;
use serde_json::{Value, json};
use std::io::{self, BufRead, Write};

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub(super) struct Greeting {
    pub hello: Hello,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub(super) struct Hello {
    pub agent: String,
    pub pass: String,
    pub store: String,
    pub resumable: bool,
    pub harness: Harness,
}
#[derive(Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub(super) struct Harness {
    pub id: String,
    pub version: String,
    pub protocol: u64,
}

pub(super) fn read(reader: &mut impl BufRead, bound: usize) -> Result<Option<Value>, Failure> {
    let mut bytes = Vec::new();
    loop {
        let available = reader.fill_buf().map_err(|_| {
            Failure::new(
                "assistant_socket_read_failed",
                "the seat frame could not be read",
            )
        })?;
        if available.is_empty() {
            return if bytes.is_empty() {
                Ok(None)
            } else {
                Err(Failure::new(
                    "assistant_frame_invalid",
                    "frame ended without a newline",
                ))
            };
        }
        let count = available
            .iter()
            .position(|byte| *byte == b'\n')
            .map_or(available.len(), |index| index + 1);
        if count > bound.saturating_sub(bytes.len()) {
            return Err(super::assistant_state::bound(
                "frame_bytes",
                bound,
                bytes.len().saturating_add(count),
            ));
        }
        let ended = available[count - 1] == b'\n';
        bytes.extend_from_slice(&available[..count]);
        reader.consume(count);
        if ended {
            break;
        }
    }
    let value: Value = serde_json::from_slice(&bytes)
        .map_err(|_| Failure::new("assistant_frame_invalid", "frame is not a JSON object"))?;
    if !value.is_object() {
        return Err(Failure::new(
            "assistant_frame_invalid",
            "frame is not a JSON object",
        ));
    }
    Ok(Some(value))
}
pub(super) fn write(writer: &mut impl Write, value: &Value) -> io::Result<()> {
    serde_json::to_writer(&mut *writer, value)?;
    writer.write_all(b"\n")?;
    writer.flush()
}
pub(super) fn refuse(writer: &mut impl Write, error: &Failure) -> io::Result<()> {
    write(
        writer,
        &json!({"refused":{"code":error.code,"message":error.message}}),
    )
}
pub(super) fn identifier(value: &str) -> bool {
    !value.is_empty()
        && value.len() <= 256
        && !value
            .chars()
            .any(|c| c.is_control() || matches!(c, '/' | '\\'))
}
pub(super) fn person(value: &str) -> bool {
    identifier(value)
        && value
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
}
pub(super) fn item(value: &Value) -> Result<(), Failure> {
    let kind = value["kind"].as_str().unwrap_or_default();
    if !value["conversation"].as_str().is_some_and(identifier)
        || value["seq"].as_u64().is_none_or(|seq| seq == 0)
        || !value["durable"].is_boolean()
        || !matches!(
            kind,
            "request"
                | "prompt_accepted"
                | "prompt_queued"
                | "text"
                | "thought"
                | "tool_call"
                | "plan"
                | "available_commands"
                | "config_options"
                | "config_failed"
                | "permission_ask"
                | "permission_answered"
                | "state"
                | "turn_ended"
                | "turn_failed"
                | "sessions"
                | "session_opening"
                | "session_opened"
                | "session_failed"
                | "omitted"
                | "presence"
                | "ended"
        )
    {
        return Err(Failure::new(
            "assistant_item_invalid",
            "item needs a conversation, positive sequence, durable flag and declared kind",
        ));
    }
    Ok(())
}