use std::io::{BufRead, Write};
use std::sync::{Mutex, PoisonError};
use anyhow::{Context as _, Result, bail};
use super::{Relay, Routed};
const MAX_LINE: u64 = 64 * 1024 * 1024;
fn read_line(input: &mut impl BufRead) -> Result<Option<String>> {
let mut bytes = Vec::new();
let read = std::io::Read::take(&mut *input, MAX_LINE + 1)
.read_until(b'\n', &mut bytes)
.context("reading a message")?;
if read == 0 {
return Ok(None);
}
if bytes.len() as u64 > MAX_LINE {
bail!("a message is longer than {} MiB", MAX_LINE / (1024 * 1024));
}
while bytes.last().is_some_and(|b| *b == b'\n' || *b == b'\r') {
bytes.pop();
}
Ok(Some(String::from_utf8_lossy(&bytes).into_owned()))
}
fn send<W: Write>(out: &Mutex<Option<W>>, line: &str) -> Result<()> {
let mut out = out.lock().unwrap_or_else(PoisonError::into_inner);
let Some(out) = out.as_mut() else {
bail!("the pipe is closed");
};
out.write_all(line.as_bytes())?;
out.write_all(b"\n")?;
out.flush()?;
Ok(())
}
fn route<S: Write, H: Write>(
routed: Vec<Routed>,
server: &Mutex<Option<S>>,
host: &Mutex<Option<H>>,
note: &(dyn Fn(&str) + Sync),
) -> Result<()> {
for routed in routed {
match routed {
Routed::Server(text) => send(server, &text).context("writing to the MCP server")?,
Routed::Host(text) => send(host, &text).context("writing to the agent")?,
Routed::Dropped(why) => note(&why),
}
}
Ok(())
}
pub fn pump_host<S: Write, H: Write>(
relay: &Relay,
mut input: impl BufRead,
server: &Mutex<Option<S>>,
host: &Mutex<Option<H>>,
note: &(dyn Fn(&str) + Sync),
) -> Result<()> {
while let Some(line) = read_line(&mut input)? {
route(relay.host_line(&line), server, host, note)?;
}
Ok(())
}
pub fn pump_server<S: Write, H: Write>(
relay: &Relay,
mut input: impl BufRead,
server: &Mutex<Option<S>>,
host: &Mutex<Option<H>>,
note: &(dyn Fn(&str) + Sync),
) -> Result<()> {
while let Some(line) = read_line(&mut input)? {
route(relay.server_line(&line), server, host, note)?;
}
Ok(())
}