# API
```rust
pub const MAX_INBOUND_LINE_BYTES: usize = 8 * 1024 * 1024;
pub const MAX_STDERR_CAPTURE_BYTES: usize = 64 * 1024;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RequestId(pub u64);
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PeerId { Number(serde_json::Number), String(String) }
#[derive(Clone, Debug, PartialEq)]
pub struct RpcError { pub code: i64, pub message: String, pub data: Option<serde_json::Value> }
#[derive(Clone, Debug, PartialEq)]
pub enum IncomingMessage {
Response { id: PeerId, result: Result<serde_json::Value, RpcError> },
Request { id: PeerId, method: String, params: serde_json::Value },
Notification { method: String, params: serde_json::Value },
}
#[derive(Clone, Debug)]
pub struct ProcessExit { pub status: std::process::ExitStatus, pub stderr: String }
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Json(serde_json::Error),
InvalidMessage(String),
InboundLineTooLong,
ProcessExited(ProcessExit),
StderrTask(String),
}
pub struct StdioRpc { /* private fields */ }
impl StdioRpc {
pub fn spawn(command: tokio::process::Command) -> Result<Self, Error>;
pub async fn send_request(&mut self, method: impl Into<String>, params: serde_json::Value) -> Result<RequestId, Error>;
pub async fn send_notification(&mut self, method: impl Into<String>, params: serde_json::Value) -> Result<(), Error>;
pub async fn next(&mut self) -> Result<IncomingMessage, Error>;
pub async fn respond(&mut self, id: PeerId, result: serde_json::Value) -> Result<(), Error>;
pub async fn respond_error(&mut self, id: PeerId, code: i64, message: impl Into<String>, data: Option<serde_json::Value>) -> Result<(), Error>;
pub async fn shutdown(self) -> Result<ProcessExit, Error>;
}
```
`StdioRpc` owns one child process using JSON-RPC 2.0 with exactly one UTF-8 JSON object per stdin or stdout line. `spawn` must run inside a Tokio runtime. Locally issued request IDs begin at one and increase for the life of the instance. Peer IDs may be integer JSON numbers or strings. Missing parameters are exposed as JSON null. Messages with an incorrect protocol version, ambiguous request/response fields, unsupported IDs, malformed JSON, or a line longer than `MAX_INBOUND_LINE_BYTES` fail explicitly. A line-limit failure drains that line before returning, so later framing remains aligned.
Each outbound operation performs one serialization, one line write, and one flush with work and temporary memory linear in the serialized message. `next` uses memory linear in the current line up to the public limit. Stderr is drained concurrently to prevent pipe backpressure; only its first `MAX_STDERR_CAPTURE_BYTES` bytes are retained and returned lossily as UTF-8. No operation retries or imposes a timeout.
The supplied command is configured with piped standard streams and kill-on-drop. `shutdown` explicitly kills and reaps a still-running child. End-of-stdout from any child is returned as `Error::ProcessExited` with its status and captured stderr. Distinct `StdioRpc` values are independent and may run concurrently; one value uses mutable methods to make its wire order caller-controlled.