use std::{
io::{Read, Write},
net::{TcpListener, TcpStream},
sync::mpsc,
thread,
};
use serde_json::json;
use crate::a2a::A2aAgent;
pub(crate) fn free_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.expect("bind")
.local_addr()
.expect("address")
.port()
}
pub(crate) fn agent_at(name: &str, port: u16) -> A2aAgent {
A2aAgent::new(mentra::a2a::A2aAgentConfig::new(
name,
format!("http://127.0.0.1:{port}"),
))
}
pub(crate) fn serve_card(port: u16) {
let listener = TcpListener::bind(("127.0.0.1", port)).expect("bind the reserved port");
thread::spawn(move || {
for stream in listener.incoming() {
let Ok(stream) = stream else { break };
answer(stream, port);
}
});
}
pub(crate) struct Stalling {
pub(crate) first_arrived: mpsc::Receiver<()>,
pub(crate) release: mpsc::Sender<()>,
}
pub(crate) fn serve_card_stalling_first(port: u16) -> Stalling {
let listener = TcpListener::bind(("127.0.0.1", port)).expect("bind the reserved port");
let (arrived, first_arrived) = mpsc::channel();
let (release, released) = mpsc::channel::<()>();
thread::spawn(move || {
let mut incoming = listener.incoming();
let Some(Ok(held)) = incoming.next() else {
return;
};
let _ = arrived.send(());
thread::spawn(move || {
let _ = released.recv();
let _ = held.shutdown(std::net::Shutdown::Both);
});
for stream in incoming {
let Ok(stream) = stream else { break };
answer(stream, port);
}
});
Stalling {
first_arrived,
release,
}
}
fn answer(mut stream: TcpStream, port: u16) {
let card = json!({
"name": "Reviewer",
"description": "Reviews diffs.",
"version": "1.0.0",
"supportedInterfaces": [
{"url": format!("http://127.0.0.1:{port}/rpc"), "protocolBinding": "JSONRPC", "protocolVersion": "1.0"},
],
"capabilities": {},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [],
})
.to_string();
let mut head = Vec::new();
let mut byte = [0_u8; 1];
while !head.ends_with(b"\r\n\r\n") && stream.read(&mut byte).unwrap_or(0) == 1 {
head.push(byte[0]);
}
let _ = write!(
stream,
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\
connection: close\r\n\r\n{card}",
card.len()
);
}