haematite-cli 0.21.12

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use super::{
    config::{Place, Seat},
    tools::Identity,
};
use crate::{
    client::{
        Client, Ended, Reply,
        network::{Address, Remote, Trust},
    },
    mcp::adapter::{Sender, ServiceRefusal},
    serve::{cli::Config, protocol::Operation},
};
use serde::Deserialize;
use serde_json::{Value, json, value::RawValue};
use std::time::Duration;

pub(super) struct Native {
    /// The page asked for: the serve config's or the seat file's, else the
    /// store's own, learnt from its hello at the first [`Native::page`], on
    /// the recorder's thread rather than the seat's runtime.
    page: Option<u32>,
    address: Address,
    timeout: Duration,
    issuer: String,
    agent: String,
    proof: String,
    bound: usize,
}
#[derive(Deserialize)]
struct Wire {
    method: String,
    params: Option<Box<RawValue>>,
}
impl Native {
    pub fn new(seat: &Seat, identity: &Identity) -> anyhow::Result<Self> {
        let (page, address, timeout) = match &seat.place {
            Place::Local { serve_config, .. } => {
                let config: Config = serde_json::from_slice(&std::fs::read(serve_config)?)
                    .map_err(|_| anyhow::anyhow!("assistant_serve_config_invalid"))?;
                let endpoint = config.endpoint(false)?;
                anyhow::ensure!(endpoint.name == seat.store, "assistant_serve_store_differs");
                (
                    Some(config.max_page.get()),
                    Address::Socket(endpoint.socket),
                    Duration::from_millis(config.timeout_ms.get()),
                )
            }
            // HAEMNET-003 R2: the store's page is the one its own hello
            // answers unless the seat file names one, and each call's bound
            // is mcp's default unless it names one.
            Place::Network { store, .. } => {
                let trust = Trust::system().map_err(anyhow::Error::msg)?;
                let address = Address::Network(
                    Remote::new(&store.endpoint, &store.server_name, &trust)
                        .map_err(anyhow::Error::msg)?,
                );
                let timeout = Duration::from_millis(store.timeout_ms.map_or(
                    crate::mcp::stdio::DEFAULT_TIMEOUT_MS.get(),
                    std::num::NonZeroU64::get,
                ));
                (
                    store.max_page.map(std::num::NonZeroU32::get),
                    address,
                    timeout,
                )
            }
        };
        Ok(Self {
            page,
            address,
            timeout,
            issuer: seat.issuer.clone(),
            agent: identity.agent.clone(),
            proof: identity.launch.clone(),
            bound: seat.harness.issuer_response_bytes,
        })
    }
    pub fn page(&mut self) -> anyhow::Result<u32> {
        if let Some(page) = self.page {
            return Ok(page);
        }
        let page = Client::reach(&self.address, self.timeout)
            .map_err(|ended| anyhow::anyhow!("assistant_store_unreached: {}", ended.why()))?
            .max_page()
            .get();
        self.page = Some(page);
        Ok(page)
    }
    pub fn read(&mut self, method: &str, params: &Value) -> Result<Value, ServiceRefusal> {
        let request = serde_json::from_value(json!({"method":method,"params":params}))
            .map_err(|error| encoding(&error))?;
        serde_json::from_str(self.send(request)?.get()).map_err(|error| encoding(&error))
    }
}
impl Sender for Native {
    fn send(&mut self, request: Operation) -> Result<Box<RawValue>, ServiceRefusal> {
        let pass = super::tools::acquire(&self.issuer, &self.agent, &self.proof, self.bound)
            .map_err(|_| ServiceRefusal {
                code: "assistant_record_pass_refused".to_owned(),
                message: "could not acquire the agent's record pass".to_owned(),
                receipt: None,
            })?;
        let mut client = Client::reach(&self.address, self.timeout).map_err(ended)?;
        let raw = serde_json::value::to_raw_value(&json!({"pass":pass}))
            .map_err(|error| encoding(&error))?;
        answered(client.call("session.pass", Some(&raw)).map_err(ended)?)?;
        let raw = serde_json::value::to_raw_value(&request).map_err(|error| encoding(&error))?;
        let operation: Wire = serde_json::from_str(raw.get()).map_err(|error| encoding(&error))?;
        answered(
            client
                .call(&operation.method, operation.params.as_deref())
                .map_err(ended)?,
        )
    }
}
fn answered(reply: Reply) -> Result<Box<RawValue>, ServiceRefusal> {
    match reply {
        Reply::Answered(raw) => Ok(raw),
        Reply::Refused(error) => Err(ServiceRefusal {
            code: error.code,
            message: error.message,
            receipt: error.verified.map(|receipt| receipt.raw().to_owned()),
        }),
    }
}
fn encoding(error: &serde_json::Error) -> ServiceRefusal {
    ServiceRefusal {
        code: "assistant_record_encoding_failed".to_owned(),
        message: error.to_string(),
        receipt: None,
    }
}
fn ended(error: Ended) -> ServiceRefusal {
    let (code, message, receipt) = match error {
        Ended::Usage(why) | Ended::NotAsked(why) => ("assistant_record_not_asked", why, None),
        Ended::Unknown(why) => ("assistant_record_outcome_unknown", why, None),
        Ended::Received { why, receipt } => (
            "assistant_record_outcome_unknown",
            why,
            Some(receipt.raw().to_owned()),
        ),
    };
    ServiceRefusal {
        code: code.to_owned(),
        message,
        receipt,
    }
}