Request
The payload is the tag byte 1 followed by the request as JSON: the arguments, as the image defines them, and the id of the template the container is made from; no secret rides it.
[1][request JSON …]
arguments. A JSON value: theargumentsof the containers::tools::run request that made the container, verbatim. What the value may be, the image’s/DIVERGE.mddefines, and the info channel answers.template. A string: thetemplateof the containers::tools::run request that made the container, verbatim: the id of the template the container is made from, which the server hands to the container’s program with the arguments and reads for nothing else.- No secret. The request carries no secret. Which secrets the
container’s program takes is its own answer, carried in the
secretsofBegun; their values follow on a secrets channel, the first of them before the run’s id. - Once, and first. The client sends this request before any other request on the connection, and never again on it.
- Malformed. A payload that does not decode as stated is answered by a response finish that no response precedes.
The request is defined by diverge-sdk-rs/src/container_proxy/outside/endpoints/tools/begin/client/request/frame.rs:
//! What a client's request frame carries for a tool container begin.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// Begin the server's work on a tool container, and hand it its
/// arguments.
///
/// The arguments are the
/// [`arguments`](crate::shared::containers::request::Container::arguments)
/// of the request that made the container, typed to the same depth
/// for the same reason — a JSON value, because the image defines what
/// it takes, and what the value may be is what the image's
/// [`info`](crate::shared::containers::info) answers. They ride
/// the begin rather than a channel of their own because they are
/// handed over exactly once, first, and never change: the container
/// that has begun is a container that holds its arguments, and
/// [`Begun`](super::super::super::server::response::Frame::Begun) says both.
///
/// The template rides beside them: the id of the template the
/// container is made from, as the run request named it, handed to
/// the program with them and read by nothing else. No secret does:
/// which secrets the program takes is its own answer, carried back on
/// `Begun`, and their values follow on a
/// [`Secrets`](super::super::channel_request::Frame::Secrets) channel
/// once the caller has them. The proxy sets nothing under `_meta` on
/// what it relays — who is on either end of a tool call is the
/// daemon's to attest, see [`shared::mcp`](crate::shared::mcp) — so
/// it has no need of the image it runs in, and is not told it.
///
/// # Once, and first
///
/// The server opens this before any other scope on the connection,
/// and never again on it: a second begin is answered
/// [`Error`](super::super::super::server::response::Frame::Error) and
/// finished, and the first goes on.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Frame {
/// The arguments, as the image defines them.
pub arguments: Value,
/// The template the container is made from, by id, as the
/// [`template`](crate::shared::containers::request::Container::template)
/// of the request that made the container names it.
pub template: String,
}
/// This frame's tag among the scope-opening requests.
///
/// One byte at the front of the payload, which is what tells a reader
/// which request it holds. The frame layer does not discriminate them
/// — [`ClientFrame::Request`](crate::wire::frame::client::ClientFrame::Request)
/// is one type carrying bytes — so the distinction has to be in the
/// bytes, and each request owns the value that names it.
///
/// See the table in
/// [`container_proxy_endpoints`](crate::container_proxy::outside) for
/// the whole allocation. The values are chosen across modules that do
/// not know about each other, so the table is the only place they can
/// be seen at once.
const TAG: u8 = 1;
impl Encode for Frame {
/// The ordinary JSON failure. The tag cannot fail.
type Error = serde_json::Error;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
out.extend_from_slice(&[TAG]);
serde_json::to_writer(out, self)
}
}
impl Decode<'_> for Frame {
/// Three ways to fail, and only one of them is JSON.
type Error = FrameError;
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
if *tag != TAG {
return Err(FrameError::UnexpectedTag(*tag));
}
serde_json::from_slice(rest).map_err(FrameError::Body)
}
}
/// A tools begin request that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag naming some other request.
///
/// A reader that dispatched on the tag will not see this. One that
/// assumed which request it held, and was wrong, will — which is
/// the point of checking a tag rather than skipping it.
UnexpectedTag(u8),
/// The request did not parse.
Body(serde_json::Error),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Empty => {
f.write_str("tools begin request frame is empty")
}
FrameError::UnexpectedTag(tag) => {
write!(f, "expected tools begin request tag {TAG}, found {tag}")
}
FrameError::Body(error) => {
write!(f, "tools begin request did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameError::Body(error) => Some(error),
FrameError::Empty | FrameError::UnexpectedTag(_) => None,
}
}
}