use futures_util::StreamExt;
use tokio_util::sync::CancellationToken;
use super::client::{ATTRIBUTION_REFERER, ATTRIBUTION_TITLE, KeyVerdict};
use super::{OpenAiClient, wire};
use crate::entities::sampling::{ReasoningEffort, SamplingConfig};
use crate::shared::api::contract::{
ApiImage, ApiMessage, ApiToolCall, ChatChunk, ChatRequest, EngineBackend, Served, VisionSupport,
};
#[derive(Debug, Clone)]
pub(super) struct Seen {
line: String,
headers: String,
body: String,
}
impl Seen {
pub(super) fn path(&self) -> &str {
self.line.split_whitespace().nth(1).unwrap_or_default()
}
fn header(&self, name: &str) -> Option<&str> {
self.headers.lines().find_map(|l| {
let (k, v) = l.split_once(':')?;
k.trim().eq_ignore_ascii_case(name).then(|| v.trim())
})
}
pub(super) fn json(&self) -> serde_json::Value {
serde_json::from_str(&self.body).unwrap_or_else(|e| panic!("{e}: {:?}", self.body))
}
}
pub(super) type Route = (&'static str, &'static str, &'static str, &'static str);
pub(super) const JSON: &str = "application/json";
pub(super) const SSE: &str = "text/event-stream";
pub(super) fn stub(
connections: usize,
routes: &'static [Route],
) -> (String, std::thread::JoinHandle<Vec<Seen>>) {
use std::io::{Read, Write};
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
listener.set_nonblocking(true).unwrap();
let handle = std::thread::spawn(move || {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
let mut seen = Vec::new();
while seen.len() < connections {
let mut sock = match listener.accept() {
Ok((sock, _)) => sock,
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
&& std::time::Instant::now() < deadline =>
{
std::thread::sleep(std::time::Duration::from_millis(5));
continue;
}
Err(_) => return seen,
};
sock.set_nonblocking(false).unwrap();
let mut raw = Vec::new();
let mut chunk = [0u8; 8192];
let request = loop {
let n = match sock.read(&mut chunk) {
Ok(0) | Err(_) => break None,
Ok(n) => n,
};
raw.extend_from_slice(&chunk[..n]);
let text = String::from_utf8_lossy(&raw).to_string();
let Some((head, body)) = text.split_once("\r\n\r\n") else {
continue;
};
let want: usize = head
.lines()
.find_map(|l| {
let (k, v) = l.split_once(':')?;
k.eq_ignore_ascii_case("content-length")
.then(|| v.trim().parse().ok())?
})
.unwrap_or(0);
if body.len() >= want {
let (line, headers) = head.split_once("\r\n").unwrap_or((head, ""));
break Some(Seen {
line: line.to_string(),
headers: headers.replace("\r\n", "\n"),
body: body.to_string(),
});
}
};
let Some(request) = request else {
return seen;
};
let (status, kind, body) = routes
.iter()
.find(|(path, ..)| *path == request.path())
.map(|(_, status, kind, body)| (*status, *kind, *body))
.unwrap_or((
"404 Not Found",
JSON,
r#"{"error":{"message":"Not Found","code":404}}"#,
));
let _ = sock.write_all(
format!(
"HTTP/1.1 {status}\r\nContent-Type: {kind}\r\nConnection: close\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
)
.as_bytes(),
);
seen.push(request);
}
seen
});
(format!("http://{addr}/v1"), handle)
}
pub(super) const ONE_CHUNK: &str = "data: {\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hi\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n";
const HAIKU: &str = r#"{"data":{"id":"anthropic/claude-haiku-4.5","context_length":200000,
"architecture":{"input_modalities":["text","image","file"],"output_modalities":["text"]},
"supported_parameters":["include_reasoning","max_tokens","reasoning","temperature","tools","top_k","top_p"],
"reasoning":{"mandatory":false}}}"#;
const GEMINI: &str = r#"{"data":{"id":"google/gemini-3.5-flash","context_length":1048576,
"architecture":{"input_modalities":["text","image","video","file","audio"],"output_modalities":["text"]},
"supported_parameters":["reasoning","include_reasoning","max_tokens","temperature","top_p","seed","tools","reasoning_effort"],
"reasoning":{"mandatory":true,"default_enabled":true,"supported_efforts":["high","medium","low","minimal"],"default_effort":"medium"}}}"#;
const R1: &str = r#"{"data":{"id":"deepseek/deepseek-r1","context_length":64000,
"architecture":{"input_modalities":["text"],"output_modalities":["text"]},
"supported_parameters":["max_tokens","reasoning","repetition_penalty","temperature","top_k","top_p"],
"reasoning":{"mandatory":true}}}"#;
fn gateway(url: String, model: &str) -> OpenAiClient {
OpenAiClient::new(url)
.with_api_key(Some("k".into()))
.with_model(Some(model.into()))
.for_openrouter(true)
}
pub(super) fn turn(sampling: SamplingConfig) -> ChatRequest {
ChatRequest {
continue_final: false,
system: None,
messages: vec![ApiMessage::user("hi")],
sampling,
tools: Vec::new(),
}
}
pub(super) fn muted() -> ChatRequest {
turn(SamplingConfig {
reasoning_effort: Some(ReasoningEffort::None),
reasoning_budget: Some(0),
thinking: Some(false),
..Default::default()
})
}
pub(super) async fn run(client: &OpenAiClient, req: ChatRequest) -> Vec<ChatChunk> {
let mut stream = client
.chat_stream(req, CancellationToken::new())
.await
.expect("a stream");
let mut out = Vec::new();
while let Some(chunk) = stream.next().await {
out.push(chunk);
}
out
}
fn chat_body(seen: &[Seen]) -> serde_json::Value {
seen.iter()
.find(|s| s.path() == "/v1/chat/completions")
.unwrap_or_else(|| panic!("no chat request among {seen:?}"))
.json()
}
#[tokio::test]
async fn the_model_list_is_named_by_the_same_switch() {
use crate::shared::api::catalogue::{CatalogueRequest, CatalogueShape, fetch};
const ROUTES: &[Route] = &[("/v1/models/user", "200 OK", JSON, r#"{"data":[]}"#)];
for attribution in [true, false] {
let (url, server) = stub(1, ROUTES);
let listed = fetch(&CatalogueRequest {
shape: CatalogueShape::OpenRouter,
base: url,
key: Some("k".into()),
attribution,
})
.await;
assert_eq!(listed, Ok(vec![]), "the stub answered an empty list");
let seen = server.join().unwrap();
let named = (
seen[0].header("http-referer"),
seen[0].header("x-openrouter-title"),
);
let expected = attribution.then_some((ATTRIBUTION_REFERER, ATTRIBUTION_TITLE));
assert_eq!(
(named.0.zip(named.1)),
expected,
"attribution={attribution}: {seen:?}"
);
assert_eq!(seen[0].header("authorization"), Some("Bearer k"));
}
}
#[tokio::test]
async fn a_gateway_request_names_the_application_and_no_other_request_does() {
const ROUTES: &[Route] = &[
(
"/v1/model/anthropic/claude-haiku-4.5",
"200 OK",
JSON,
HAIKU,
),
("/v1/chat/completions", "200 OK", SSE, ONE_CHUNK),
("/v1/key", "200 OK", JSON, r#"{"data":{"limit":null}}"#),
];
let named = |seen: &[Seen]| -> Vec<(Option<String>, Option<String>)> {
seen.iter()
.map(|s| {
(
s.header("http-referer").map(str::to_string),
s.header("x-openrouter-title").map(str::to_string),
)
})
.collect()
};
let (url, server) = stub(3, ROUTES);
let client = gateway(url, "anthropic/claude-haiku-4.5");
assert_eq!(client.check_key().await, KeyVerdict::Accepted);
run(
&client,
turn(SamplingConfig {
thinking: Some(true),
..Default::default()
}),
)
.await;
let seen = server.join().unwrap();
assert_eq!(seen.len(), 3, "{seen:?}");
for pair in named(&seen) {
assert_eq!(
pair,
(
Some(ATTRIBUTION_REFERER.to_string()),
Some(ATTRIBUTION_TITLE.to_string())
)
);
}
assert!(
seen.iter()
.all(|s| s.header("authorization") == Some("Bearer k")),
"the key travels with all three: {seen:?}"
);
let (url, server) = stub(3, ROUTES);
let silent = OpenAiClient::new(url)
.with_api_key(Some("k".into()))
.with_model(Some("anthropic/claude-haiku-4.5".into()))
.for_openrouter(false);
silent.check_key().await;
run(
&silent,
turn(SamplingConfig {
thinking: Some(true),
..Default::default()
}),
)
.await;
let seen = server.join().unwrap();
assert_eq!(seen.len(), 3, "{seen:?}");
assert!(named(&seen).iter().all(|p| *p == (None, None)), "{seen:?}");
let (url, server) = stub(1, ROUTES);
run(
&OpenAiClient::new(url).with_api_key(Some("k".into())),
turn(Default::default()),
)
.await;
assert!(
named(&server.join().unwrap())
.iter()
.all(|p| *p == (None, None))
);
}
#[tokio::test]
async fn the_body_is_written_in_the_gateways_words() {
let sampling = SamplingConfig {
temperature: Some(0.7),
top_k: Some(40),
top_p: Some(0.9),
min_p: Some(0.05),
max_tokens: Some(512),
seed: Some(7),
frequency_penalty: Some(0.1),
presence_penalty: Some(0.2),
repeat_penalty: Some(1.15),
repeat_last_n: Some(64),
dynatemp_range: Some(0.5),
dynatemp_exponent: Some(1.0),
typical_p: Some(0.9),
top_n_sigma: Some(1.0),
adaptive_target: Some(0.5),
adaptive_decay: Some(0.9),
mirostat: Some(2),
mirostat_tau: Some(5.0),
mirostat_eta: Some(0.1),
dry_multiplier: Some(0.8),
dry_base: Some(1.75),
dry_allowed_length: Some(2),
dry_penalty_last_n: Some(64),
dry_sequence_breakers: Some(vec!["\n".into()]),
xtc_probability: Some(0.5),
xtc_threshold: Some(0.1),
samplers: Some(vec!["top_k".into()]),
..Default::default()
};
const ROUTES: &[Route] = &[("/v1/chat/completions", "200 OK", SSE, ONE_CHUNK)];
let (url, server) = stub(1, ROUTES);
run(
&gateway(url, "meta-llama/llama-3.3-70b-instruct"),
turn(sampling.clone()),
)
.await;
let body = chat_body(&server.join().unwrap());
let mut keys: Vec<&str> = body
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
keys.sort_unstable();
assert_eq!(
keys,
[
"frequency_penalty",
"max_tokens",
"messages",
"min_p",
"model",
"presence_penalty",
"repetition_penalty",
"seed",
"stream",
"stream_options",
"temperature",
"top_k",
"top_p",
],
"{body}"
);
let hundredths = |v: &serde_json::Value| (v.as_f64().unwrap() * 100.0).round() as i64;
assert_eq!(hundredths(&body["repetition_penalty"]), 115, "{body}");
let (url, server) = stub(1, ROUTES);
run(&OpenAiClient::new(url), turn(sampling)).await;
let plain = chat_body(&server.join().unwrap());
assert_eq!(hundredths(&plain["repeat_penalty"]), 115, "{plain}");
assert!(plain.get("repetition_penalty").is_none(), "{plain}");
for llama in ["mirostat", "dry_multiplier", "samplers", "typical_p"] {
assert!(
plain.get(llama).is_some(),
"{llama} still reaches llama.cpp"
);
}
}
#[tokio::test]
async fn a_muted_turn_asks_a_model_that_must_reason_for_its_lowest_effort() {
const ROUTES: &[Route] = &[
("/v1/model/google/gemini-3.5-flash", "200 OK", JSON, GEMINI),
("/v1/chat/completions", "200 OK", SSE, ONE_CHUNK),
];
let (url, server) = stub(2, ROUTES);
let client = gateway(url, "google/gemini-3.5-flash");
let chunks = run(&client, muted()).await;
assert!(
chunks.contains(&ChatChunk::Text("hi".into())),
"the turn answered: {chunks:?}"
);
let seen = server.join().unwrap();
assert_eq!(
seen.iter().map(Seen::path).collect::<Vec<_>>(),
["/v1/model/google/gemini-3.5-flash", "/v1/chat/completions"],
"one question, one request"
);
let body = chat_body(&seen);
assert_eq!(body["reasoning_effort"], "minimal", "{body}");
assert!(body.get("reasoning").is_none(), "{body}");
}
#[tokio::test]
async fn the_request_to_stop_reasoning_follows_what_the_catalogue_says() {
const ROUTES: &[Route] = &[
("/v1/model/deepseek/deepseek-r1", "200 OK", JSON, R1),
(
"/v1/model/anthropic/claude-haiku-4.5",
"200 OK",
JSON,
HAIKU,
),
("/v1/chat/completions", "200 OK", SSE, ONE_CHUNK),
];
let (url, server) = stub(2, ROUTES);
run(&gateway(url, "deepseek/deepseek-r1"), muted()).await;
let r1 = chat_body(&server.join().unwrap());
assert!(r1.get("reasoning_effort").is_none(), "{r1}");
assert!(r1.get("reasoning").is_none(), "{r1}");
let (url, server) = stub(2, ROUTES);
run(&gateway(url, "anthropic/claude-haiku-4.5"), muted()).await;
let haiku = chat_body(&server.join().unwrap());
assert_eq!(haiku["reasoning_effort"], "none", "{haiku}");
let (url, server) = stub(1, ROUTES);
run(
&gateway(url, "deepseek/deepseek-r1"),
turn(SamplingConfig {
reasoning_effort: Some(ReasoningEffort::High),
..Default::default()
}),
)
.await;
let chosen = chat_body(&server.join().unwrap());
assert_eq!(chosen["reasoning_effort"], "high", "{chosen}");
}
#[tokio::test]
async fn the_gateway_is_asked_about_one_model_and_never_for_props() {
const ROUTES: &[Route] = &[(
"/v1/model/anthropic/claude-haiku-4.5:nitro",
"200 OK",
JSON,
HAIKU,
)];
let (url, server) = stub(1, ROUTES);
let client = gateway(url, "anthropic/claude-haiku-4.5:nitro");
assert_eq!(client.vision().await, VisionSupport::Supported);
let caps = client.model_capabilities().await.expect("an entry");
assert_eq!(caps.context_length, Some(200_000));
assert!(
caps.sampling_fields
.as_deref()
.is_some_and(|f| f.iter().any(|p| p == "top_k"))
);
assert_eq!(client.context_budget().await, None);
assert_eq!(client.parallel_slots().await, None);
assert_eq!(client.model_id().await, None);
let seen = server.join().unwrap();
assert_eq!(
seen.iter().map(Seen::path).collect::<Vec<_>>(),
["/v1/model/anthropic/claude-haiku-4.5:nitro"],
"one request answers every question asked above"
);
}
#[tokio::test]
async fn a_model_the_gateway_does_not_know_is_asked_about_once() {
let (url, server) = stub(1, &[]);
let client = gateway(url, "nope/not-a-model");
assert_eq!(client.vision().await, VisionSupport::Unknown);
assert!(client.model_capabilities().await.is_none());
assert_eq!(server.join().unwrap().len(), 1);
}
#[tokio::test]
async fn an_outage_of_the_catalogue_is_asked_about_again() {
const DOWN: &[Route] = &[(
"/v1/model/anthropic/claude-haiku-4.5",
"503 Service Unavailable",
JSON,
"{}",
)];
const SLOW_DOWN: &[Route] = &[(
"/v1/model/anthropic/claude-haiku-4.5",
"429 Too Many Requests",
JSON,
"{}",
)];
for routes in [DOWN, SLOW_DOWN] {
let (url, server) = stub(2, routes);
let client = gateway(url, "anthropic/claude-haiku-4.5");
assert!(client.model_capabilities().await.is_none());
assert_eq!(client.vision().await, VisionSupport::Unknown);
assert_eq!(
server.join().unwrap().len(),
2,
"the second question was put to the gateway as well"
);
}
let dead = {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
format!("http://{}/v1", l.local_addr().unwrap())
};
let client = gateway(dead, "anthropic/claude-haiku-4.5");
assert!(client.model_capabilities().await.is_none());
assert_eq!(client.vision().await, VisionSupport::Unknown);
}
#[tokio::test]
async fn an_entry_that_does_not_parse_claims_nothing() {
const ODD: &[Route] = &[(
"/v1/model/anthropic/claude-haiku-4.5",
"200 OK",
"text/html",
"<html>maintenance</html>",
)];
let (url, server) = stub(1, ODD);
let client = gateway(url, "anthropic/claude-haiku-4.5");
assert!(client.model_capabilities().await.is_none());
assert_eq!(client.vision().await, VisionSupport::Unknown);
assert_eq!(server.join().unwrap().len(), 1);
}
#[tokio::test]
async fn a_tools_image_is_re_homed_without_waiting_for_the_catalogue() {
const ROUTES: &[Route] = &[
(
"/v1/model/anthropic/claude-haiku-4.5",
"503 Service Unavailable",
JSON,
"{}",
),
("/v1/chat/completions", "200 OK", SSE, ONE_CHUNK),
];
let round = ChatRequest {
continue_final: false,
system: None,
messages: vec![
ApiMessage::user("what is on the screen?"),
ApiMessage::assistant_tool_calls(
"",
vec![ApiToolCall {
id: "call-1".into(),
name: "take_screenshot".into(),
arguments: "{}".into(),
thought_signature: None,
}],
),
ApiMessage::tool("call-1", "Screenshot taken.").with_images(vec![ApiImage {
mime: "image/png".into(),
data: std::sync::Arc::from("QUJD"),
label: Some("screen.png".into()),
}]),
],
sampling: Default::default(),
tools: Vec::new(),
};
assert!(wire::carries_tool_images(&round.messages));
let (url, server) = stub(1, ROUTES);
run(&gateway(url, "anthropic/claude-haiku-4.5"), round).await;
let seen = server.join().unwrap();
let body = chat_body(&seen);
let messages = body["messages"].as_array().unwrap();
let tool = messages.iter().find(|m| m["role"] == "tool").unwrap();
assert_eq!(tool["content"], "Screenshot taken.", "text only: {tool}");
let last = messages.last().unwrap();
assert_eq!(last["role"], "user");
assert!(
last["content"]
.as_array()
.is_some_and(|parts| parts.iter().any(|p| p["type"] == "image_url")),
"the image follows in a user message: {last}"
);
}
#[tokio::test]
async fn who_served_and_for_how_much_rides_beside_the_usage() {
const REPLY: &str = concat!(
"data: {\"provider\":\"Amazon Bedrock\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Paris\"},\"finish_reason\":null}]}\n\n",
"data: {\"provider\":\"Amazon Bedrock\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n",
"data: {\"provider\":\"Amazon Bedrock\",\"choices\":[],\"usage\":{\"prompt_tokens\":17,\"completion_tokens\":4,\"cost\":0.000037}}\n\n",
"data: [DONE]\n\n",
);
const ROUTES: &[Route] = &[("/v1/chat/completions", "200 OK", SSE, REPLY)];
let (url, server) = stub(1, ROUTES);
let chunks = run(
&gateway(url, "anthropic/claude-haiku-4.5"),
turn(Default::default()),
)
.await;
let _ = server.join();
let served: Vec<&Served> = chunks
.iter()
.filter_map(|c| match c {
ChatChunk::Served(s) => Some(s),
_ => None,
})
.collect();
assert_eq!(
served,
[&Served {
provider: Some("Amazon Bedrock".into()),
cost_nanos: Some(37_000),
}],
"{chunks:?}"
);
const PLAIN: &[Route] = &[(
"/v1/chat/completions",
"200 OK",
SSE,
"data: {\"choices\":[],\"usage\":{\"prompt_tokens\":17,\"completion_tokens\":4}}\n\ndata: [DONE]\n\n",
)];
let (url, server) = stub(1, PLAIN);
let chunks = run(&OpenAiClient::new(url), turn(Default::default())).await;
let _ = server.join();
assert!(
!chunks.iter().any(|c| matches!(c, ChatChunk::Served(_))),
"a llama.cpp says neither: {chunks:?}"
);
}
#[tokio::test]
async fn a_key_is_accepted_refused_or_not_judged_at_all() {
const OK: &[Route] = &[("/v1/key", "200 OK", JSON, r#"{"data":{"limit":null}}"#)];
const REFUSED: &[Route] = &[(
"/v1/key",
"401 Unauthorized",
JSON,
r#"{"error":{"message":"User not found.","code":401}}"#,
)];
const DOWN: &[Route] = &[(
"/v1/key",
"503 Service Unavailable",
JSON,
r#"{"error":{"message":"try later","code":503}}"#,
)];
let (url, server) = stub(1, OK);
assert_eq!(gateway(url, "m").check_key().await, KeyVerdict::Accepted);
let _ = server.join();
let (url, server) = stub(1, REFUSED);
match gateway(url, "m").check_key().await {
KeyVerdict::Refused(said) => assert_eq!(said, "User not found."),
other => panic!("a 401 refuses the key: {other:?}"),
}
let _ = server.join();
const BARE: &[Route] = &[("/v1/key", "403 Forbidden", "text/html", "<html>no</html>")];
let (url, server) = stub(1, BARE);
assert_eq!(
gateway(url, "m").check_key().await,
KeyVerdict::Refused("403 Forbidden".into())
);
let _ = server.join();
let (url, server) = stub(1, DOWN);
match gateway(url, "m").check_key().await {
KeyVerdict::Unjudged(said) => assert!(said.contains("try later"), "{said}"),
other => panic!("an outage judges nothing: {other:?}"),
}
let _ = server.join();
let dead = {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
format!("http://{}/v1", l.local_addr().unwrap())
};
assert!(
matches!(
gateway(dead, "m").check_key().await,
KeyVerdict::NoAnswer(_)
),
"no answer is not a refusal"
);
}