use std::io::{BufRead, BufReader, Read, Write};
use std::net::TcpListener;
use std::sync::{Arc, Mutex, PoisonError};
use std::thread;
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct Received {
pub line: String,
pub headers: Vec<(String, String)>,
pub body: Value,
}
impl Received {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str())
}
}
pub struct FakeServer {
pub address: String,
received: Arc<Mutex<Vec<Received>>>,
}
impl FakeServer {
pub fn start(answers: Vec<(u16, String)>) -> Self {
Self::start_holding(answers, std::time::Duration::ZERO)
}
pub fn start_holding(answers: Vec<(u16, String)>, hold: std::time::Duration) -> Self {
let listener = TcpListener::bind("127.0.0.1:0").expect("a free port on the loopback");
let address = format!("http://{}", listener.local_addr().expect("the port"));
let received = Arc::new(Mutex::new(Vec::new()));
let log = Arc::clone(&received);
thread::spawn(move || {
let mut answers = answers.into_iter();
for stream in listener.incoming() {
let Ok(mut stream) = stream else { return };
let Some(request) = read_request(&mut stream) else { continue };
log.lock().unwrap_or_else(PoisonError::into_inner).push(request);
let (status, body) = answers.next().unwrap_or((500, "no more answers".to_owned()));
let kind = if status == 200 { "text/event-stream" } else { "application/json" };
let head = format!("HTTP/1.1 {status} Answer\r\ncontent-type: {kind}\r\nconnection: close\r\n\r\n");
let _ = stream.write_all(head.as_bytes());
let _ = stream.write_all(body.as_bytes());
if !hold.is_zero() {
let _ = stream.flush();
thread::spawn(move || {
thread::sleep(hold);
drop(stream);
});
}
}
});
Self { address, received }
}
pub fn received(&self) -> Vec<Received> {
self.received.lock().unwrap_or_else(PoisonError::into_inner).clone()
}
}
fn read_request(stream: &mut std::net::TcpStream) -> Option<Received> {
let mut reader = BufReader::new(stream);
let mut line = String::new();
reader.read_line(&mut line).ok()?;
let mut headers = Vec::new();
loop {
let mut header = String::new();
reader.read_line(&mut header).ok()?;
let header = header.trim_end();
if header.is_empty() {
break;
}
let (name, value) = header.split_once(':')?;
headers.push((name.trim().to_ascii_lowercase(), value.trim().to_owned()));
}
let length: usize =
headers.iter().find(|(name, _)| name == "content-length").and_then(|(_, value)| value.parse().ok())?;
let mut body = vec![0; length];
reader.read_exact(&mut body).ok()?;
let body = serde_json::from_slice(&body).unwrap_or(Value::Null);
Some(Received { line: line.trim_end().to_owned(), headers, body })
}
#[derive(Debug, Clone, Copy)]
pub struct Usage {
pub read: Option<u64>,
pub written: Option<u64>,
}
impl Usage {
pub fn new(read: u64, written: u64) -> Self {
Self { read: Some(read), written: Some(written) }
}
pub const SILENT: Self = Self { read: None, written: None };
}
fn reads(read: Option<u64>) -> Option<Value> {
read.map(|read| serde_json::json!({"type": "message_start", "message": {"usage": {"input_tokens": read}}}))
}
fn stops(reason: &str, written: Option<u64>) -> Value {
let mut event = serde_json::json!({"type": "message_delta", "delta": {"stop_reason": reason}});
if let Some(written) = written {
event["usage"] = serde_json::json!({"output_tokens": written});
}
event
}
pub fn says(text: &str) -> String {
says_using(text, Usage::new(10, 0))
}
pub fn says_using(text: &str, usage: Usage) -> String {
let mut events = reads(usage.read).into_iter().collect::<Vec<_>>();
events.push(
serde_json::json!({"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}),
);
for piece in text.split_inclusive(' ') {
events.push(
serde_json::json!({"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": piece}}),
);
}
events.push(serde_json::json!({"type": "content_block_stop", "index": 0}));
events.push(stops("end_turn", usage.written));
events.push(serde_json::json!({"type": "message_stop"}));
sse(&events)
}
pub fn calls(id: &str, name: &str, input: &Value) -> String {
calls_using(id, name, input, Usage::SILENT)
}
pub fn calls_using(id: &str, name: &str, input: &Value, usage: Usage) -> String {
let mut events = reads(usage.read).into_iter().collect::<Vec<_>>();
events.push(serde_json::json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": id, "name": name, "input": {}}}));
events.push(serde_json::json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": input.to_string()}}));
events.push(serde_json::json!({"type": "content_block_stop", "index": 0}));
events.push(stops("tool_use", usage.written));
events.push(serde_json::json!({"type": "message_stop"}));
sse(&events)
}
pub fn sse(events: &[Value]) -> String {
events.iter().map(|event| format!("event: {}\ndata: {event}\n\n", event["type"].as_str().unwrap_or(""))).collect()
}