haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! The person's routes into the seat and its record, under the door session.
use super::{
    assistant_state::Pair,
    http::{Request, Response},
    route::{Site, refusal},
    signin::{Door, Issuer},
};
use crate::serve::storage::Failure;
use serde_json::{Value, json};

pub(super) fn answer<I: Issuer>(
    door: &Door<I>,
    site: &Site,
    request: &Request,
    now: u64,
) -> Response {
    result(door, site, request, now).unwrap_or_else(refusal)
}
fn result<I: Issuer>(
    door: &Door<I>,
    site: &Site,
    request: &Request,
    now: u64,
) -> Result<Response, Failure> {
    let write = request.method == "POST";
    if (write && request.header("origin") != Some(site.origin.as_str()))
        || request
            .header("origin")
            .is_some_and(|origin| origin != site.origin)
    {
        return Err(Failure::new(
            "origin_refused",
            "assistant requests are taken only from Studio's own origin",
        ));
    }
    let rest = request
        .path
        .strip_prefix("/api/assistant/")
        .ok_or_else(unknown)?;
    let (store, route) = rest.split_once('/').unwrap_or((rest, ""));
    let socket = site
        .stores
        .get(store)
        .ok_or_else(|| Failure::new("store_unknown", "the door serves no such store"))?;
    let session = request
        .cookie("haem_session")
        .ok_or_else(|| Failure::new("session_unknown", "sign in to Studio first"))?;
    let token = match door.pass(session, now) {
        Ok(token) => token,
        Err(error)
            if matches!(
                error.code.as_str(),
                "lys_could_not_be_asked" | "lys_unavailable"
            ) =>
        {
            return Ok(Response::json(
                502,
                &json!({"code":"lys_unavailable","message":"Lys is temporarily unavailable; retry with the same session"}),
            ));
        }
        Err(error) => return Err(error),
    };
    let person = door.session_at(session, now)?["holder"]
        .as_str()
        .ok_or_else(|| Failure::new("session_unknown", "session has no person"))?
        .to_owned();
    if !super::assistant_wire::identifier(&person) {
        return Err(Failure::new(
            "assistant_person_invalid",
            "the session person is not a valid assistant identifier",
        ));
    }
    let pair = (person, store.to_owned());
    match (request.method.as_str(), route) {
        ("POST", "start") => Ok(Response::json(
            501,
            &json!({"code":"assistant_start_in_lys","message":"start the assistant profile for this store in Lys","lys":door.issuer().assistant_url()?,"profile":"assistant","store":store}),
        )),
        ("POST", "decide") => super::assistant_decide::decide(door, &pair, &token, &request.body),
        ("POST", "") => {
            let op = super::assistant_ops::parse(&request.body, store)?;
            deliver(door, &pair, op)
        }
        ("GET", "events") => {
            super::assistant_events::events(door, request, pair, socket.clone(), site.timeout)
        }
        ("GET", "conversations") => {
            let hub = door.assistant()?;
            let conversations = super::assistant_record::conversations(
                socket,
                site.timeout,
                &token,
                &pair.0,
                hub.config.events_after_bytes.get(),
            )?;
            Ok(Response::json(200, &json!({"conversations":conversations})))
        }
        _ => Err(unknown()),
    }
}
pub(super) fn deliver<I: Issuer>(
    door: &Door<I>,
    pair: &Pair,
    op: Value,
) -> Result<Response, Failure> {
    match door.assistant()?.operation(pair, op) {
        Ok(id) => Ok(Response::json(202, &json!({"id":id}))),
        Err(error) if error.code == "assistant_not_started" => Ok(Response::json(
            409,
            &json!({"code":error.code,"message":error.message,"start":format!("/api/assistant/{}/start",pair.1),"lys":door.issuer().assistant_url()?}),
        )),
        Err(error) => {
            let status = if error.code == "assistant_bound" {
                429
            } else if error.code == "assistant_ack_unknown" {
                502
            } else {
                409
            };
            Ok(Response::json(
                status,
                &json!({"code":error.code,"message":error.message}),
            ))
        }
    }
}
fn unknown() -> Failure {
    Failure::new("route_unknown", "the door serves no such assistant route")
}