use std::io::{Read, Write};
use std::net::TcpListener;
use futures::StreamExt as _;
use serde_json::json;
use wcode_harness::event::LlmStreamEvent;
use wcode_harness::message::AgentMessage;
use wcode_harness::streamfn::{LlmEndpoint, LlmOpts, rig_stream_fn};
const SUMMARY: &str = "Let me think";
fn read_request(stream: &mut std::net::TcpStream) -> String {
let mut buf = Vec::new();
let mut chunk = [0u8; 4096];
let header_end = loop {
let n = stream.read(&mut chunk).expect("read request headers");
if n == 0 {
break buf.len();
}
buf.extend_from_slice(&chunk[..n]);
if let Some(pos) = buf.windows(4).position(|w| w == b"\r\n\r\n") {
break pos + 4;
}
};
let headers = String::from_utf8_lossy(&buf[..header_end]).to_string();
let content_length = headers
.lines()
.find_map(|line| {
let (name, value) = line.split_once(':')?;
name.eq_ignore_ascii_case("content-length")
.then(|| value.trim().parse::<usize>().ok())
.flatten()
})
.unwrap_or(0);
while buf.len() < header_end + content_length {
let n = stream.read(&mut chunk).expect("read request body");
if n == 0 {
break;
}
buf.extend_from_slice(&chunk[..n]);
}
String::from_utf8_lossy(&buf[header_end..]).to_string()
}
fn spawn_sse_server(body: String) -> (String, std::sync::mpsc::Receiver<String>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port");
let addr = listener.local_addr().expect("local addr");
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept one connection");
let request_body = read_request(&mut stream);
let _ = tx.send(request_body);
let response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: text/event-stream\r\n\
Cache-Control: no-cache\r\n\
Connection: close\r\n\
Content-Length: {}\r\n\r\n{}",
body.len(),
body
);
stream
.write_all(response.as_bytes())
.expect("write response");
stream.flush().expect("flush response");
});
(format!("http://{addr}/v1"), rx)
}
fn frame(value: serde_json::Value) -> String {
format!("data: {}\n\n", serde_json::to_string(&value).unwrap())
}
fn completion_response(status: &str, output: serde_json::Value) -> serde_json::Value {
json!({
"id": "resp_1",
"object": "response",
"created_at": 0,
"status": status,
"model": "test-model",
"output": output,
})
}
fn canned_body() -> String {
[
frame(json!({
"type": "response.created",
"sequence_number": 0,
"response": completion_response("in_progress", json!([])),
})),
frame(json!({
"type": "response.reasoning_summary_text.delta",
"sequence_number": 1,
"item_id": "rs_1",
"output_index": 0,
"summary_index": 0,
"delta": "Let me",
})),
frame(json!({
"type": "response.reasoning_summary_text.delta",
"sequence_number": 2,
"item_id": "rs_1",
"output_index": 0,
"summary_index": 0,
"delta": " think",
})),
frame(json!({
"type": "response.output_item.done",
"sequence_number": 3,
"output_index": 0,
"item": {
"type": "reasoning",
"id": "rs_1",
"summary": [{"type": "summary_text", "text": SUMMARY}],
"status": "completed",
},
})),
frame(json!({
"type": "response.output_text.delta",
"sequence_number": 4,
"item_id": "msg_1",
"output_index": 1,
"content_index": 0,
"delta": "pong",
})),
frame(json!({
"type": "response.completed",
"sequence_number": 5,
"response": completion_response("completed", json!([])),
})),
"data: [DONE]\n\n".to_string(),
]
.concat()
}
#[tokio::test]
async fn responses_wire_requests_and_renders_reasoning() {
let (base_url, request_rx) = spawn_sse_server(canned_body());
let opts = LlmOpts {
model: "test-model".to_string(),
base_url: Some(base_url),
api_key: None, endpoint: LlmEndpoint::Responses,
effort: None, ..LlmOpts::default()
};
let stream = rig_stream_fn()(
&[AgentMessage::user_text("Reply with exactly: pong")],
"You are terse.",
&[],
&opts,
);
let events: Vec<LlmStreamEvent> =
tokio::time::timeout(std::time::Duration::from_secs(10), stream.collect())
.await
.expect("stream terminates");
let request_body = request_rx
.recv_timeout(std::time::Duration::from_secs(5))
.expect("server recorded a request");
assert!(
request_body.contains("\"summary\":\"auto\""),
"the Responses request must request a reasoning summary, got: {request_body}"
);
let parsed: serde_json::Value =
serde_json::from_str(&request_body).expect("request body is JSON");
assert_eq!(
parsed["reasoning"]["summary"], "auto",
"reasoning.summary must be auto: {request_body}"
);
assert_eq!(
events.len(),
5,
"expected exactly 2 deltas + replace + text + done, got: {events:?}"
);
assert!(
matches!(events.first(), Some(LlmStreamEvent::ThinkingDelta(t)) if t == "Let me"),
"first event must be a reasoning delta, got: {events:?}"
);
assert!(
matches!(events.get(1), Some(LlmStreamEvent::ThinkingDelta(t)) if t == " think"),
"second event must be a reasoning delta, got: {events:?}"
);
assert!(
matches!(events.get(2), Some(LlmStreamEvent::ThinkingReplace(t)) if t == SUMMARY),
"third event must be the complete summary (not erased), got: {events:?}"
);
assert!(
matches!(events.get(3), Some(LlmStreamEvent::TextDelta(t)) if t == "pong"),
"fourth event must be the answer text, got: {events:?}"
);
assert!(
matches!(events.get(4), Some(LlmStreamEvent::Done { .. })),
"last event must be Done, got: {events:?}"
);
assert!(
!events
.iter()
.any(|e| matches!(e, LlmStreamEvent::Error { .. })),
"no error on the reasoning path, got: {events:?}"
);
}