use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use agent_core::config::{
RequestCompressionMode, DEFAULT_REQUEST_COMPRESSION_LEVEL, MAX_REQUEST_COMPRESSION_LEVEL,
};
use bytes::Bytes;
use reqwest::RequestBuilder;
pub const MIN_BYTES: usize = 16 * 1024;
pub const ENV_SWITCH: &str = "SYNAPS_GZIP_REQUEST_BODIES";
const ANTHROPIC_HOST: &str = "api.anthropic.com";
const SKIP_VARS: &[&str] = &[
"HTTPS_PROXY",
"https_proxy",
"ALL_PROXY",
"all_proxy",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum Mode {
#[default]
Auto,
On,
Off,
#[cfg(test)]
Always,
}
#[derive(Debug, Clone)]
pub struct RequestCompression {
mode: Mode,
level: u32,
refused: Arc<AtomicBool>,
}
fn process_latch() -> &'static Arc<AtomicBool> {
static LATCH: OnceLock<Arc<AtomicBool>> = OnceLock::new();
LATCH.get_or_init(|| Arc::new(AtomicBool::new(false)))
}
impl Default for RequestCompression {
fn default() -> Self {
Self::from_config(
RequestCompressionMode::default(),
DEFAULT_REQUEST_COMPRESSION_LEVEL,
)
}
}
impl RequestCompression {
pub fn from_config(mode: RequestCompressionMode, level: u32) -> Self {
Self {
mode: match mode {
RequestCompressionMode::Auto => Mode::Auto,
RequestCompressionMode::On => Mode::On,
RequestCompressionMode::Off => Mode::Off,
},
level: level.clamp(1, MAX_REQUEST_COMPRESSION_LEVEL),
refused: Arc::clone(process_latch()),
}
}
#[cfg(test)]
pub fn always() -> Self {
Self {
mode: Mode::Always,
level: DEFAULT_REQUEST_COMPRESSION_LEVEL,
refused: Arc::new(AtomicBool::new(false)),
}
}
#[cfg(test)]
pub(crate) fn configured(&self) -> (Option<RequestCompressionMode>, u32) {
let mode = match self.mode {
Mode::Auto => Some(RequestCompressionMode::Auto),
Mode::On => Some(RequestCompressionMode::On),
Mode::Off => Some(RequestCompressionMode::Off),
Mode::Always => None,
};
(mode, self.level)
}
pub fn refused(&self) -> bool {
self.refused.load(Ordering::Relaxed)
}
fn wants_gzip(&self, url: &str, len: usize, env: impl Fn(&str) -> Option<String>) -> bool {
if self.refused() {
return false;
}
match self.mode {
#[cfg(test)]
Mode::Always => true,
Mode::Off => false,
Mode::On => len >= MIN_BYTES && !switched_off(&env),
Mode::Auto => {
len >= MIN_BYTES
&& !switched_off(&env)
&& is_direct_anthropic(url)
&& !SKIP_VARS
.iter()
.any(|&k| env(k).is_some_and(|v| !v.trim().is_empty()))
}
}
}
pub(super) async fn encode(&self, url: &str, raw: &Bytes) -> WireBody {
if !self.wants_gzip(url, raw.len(), |k| std::env::var(k).ok()) {
return WireBody::identity(raw);
}
let input = raw.clone();
let level = self.level;
let started = std::time::Instant::now();
match tokio::task::spawn_blocking(move || gzip(&input, level)).await {
Ok(Ok(out)) if out.len() < raw.len() => {
tracing::debug!(
level,
raw_bytes = raw.len(),
wire_bytes = out.len(),
elapsed_us = started.elapsed().as_micros() as u64,
"gzip request body"
);
WireBody {
bytes: out.into(),
gzip: true,
resend: false,
}
}
Ok(Ok(_)) => WireBody::identity(raw),
Ok(Err(e)) => {
tracing::warn!(error = %e, "gzip request body failed; sending uncompressed");
WireBody::identity(raw)
}
Err(e) => {
tracing::warn!(error = %e, "gzip request body task failed; sending uncompressed");
WireBody::identity(raw)
}
}
}
pub(super) fn resend_uncompressed(&self, wire: &WireBody, raw: &Bytes, status: u16) -> Option<WireBody> {
if !wire.gzip || !matches!(status, 400 | 415) {
return None;
}
tracing::warn!(status, "gzip request body refused; resending it uncompressed once");
Some(WireBody {
bytes: raw.clone(),
gzip: false,
resend: true,
})
}
pub(super) fn settle(&self, wire: &mut WireBody, success: bool) {
if !std::mem::take(&mut wire.resend) {
return;
}
if success && !self.refused.swap(true, Ordering::Relaxed) {
tracing::warn!(
"{ANTHROPIC_HOST} accepted a request only uncompressed; sending request bodies uncompressed for the rest of this process"
);
}
}
}
#[derive(Debug, Clone)]
pub(super) struct WireBody {
pub(super) bytes: Bytes,
pub(super) gzip: bool,
resend: bool,
}
impl WireBody {
pub(super) fn identity(raw: &Bytes) -> Self {
Self {
bytes: raw.clone(),
gzip: false,
resend: false,
}
}
pub(super) fn encoding_header(&self, req: RequestBuilder) -> RequestBuilder {
if self.gzip {
req.header(reqwest::header::CONTENT_ENCODING, "gzip")
} else {
req
}
}
}
fn switched_off(env: &impl Fn(&str) -> Option<String>) -> bool {
env(ENV_SWITCH).is_some_and(|v| {
matches!(
v.trim().to_ascii_lowercase().as_str(),
"0" | "false" | "off" | "no"
)
})
}
fn is_direct_anthropic(url: &str) -> bool {
reqwest::Url::parse(url).is_ok_and(|u| {
u.scheme() == "https"
&& u.host_str() == Some(ANTHROPIC_HOST)
&& u.port_or_known_default() == Some(443)
})
}
fn gzip(raw: &[u8], level: u32) -> std::io::Result<Vec<u8>> {
let mut enc = flate2::write::GzEncoder::new(
Vec::with_capacity(raw.len() / 2),
flate2::Compression::new(level),
);
enc.write_all(raw)?;
enc.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::io::Read;
const API: &str = "https://api.anthropic.com/v1/messages";
fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
move |k| map.get(k).cloned()
}
fn policy(mode: RequestCompressionMode, level: u32) -> RequestCompression {
RequestCompression {
refused: Arc::new(AtomicBool::new(false)),
..RequestCompression::from_config(mode, level)
}
}
fn auto() -> RequestCompression {
policy(RequestCompressionMode::Auto, DEFAULT_REQUEST_COMPRESSION_LEVEL)
}
fn gunzip(b: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
flate2::read::GzDecoder::new(b).read_to_end(&mut out).unwrap();
out
}
fn big_body() -> Bytes {
let msgs: Vec<serde_json::Value> = (0..400)
.map(|i| serde_json::json!({"role": "user", "content": [{"type": "text", "text": format!("turn {i}: cargo test --workspace output line")}]}))
.collect();
serde_json::to_vec(&serde_json::json!({"model": "claude-sonnet-4-6", "messages": msgs}))
.unwrap()
.into()
}
#[test]
fn auto_compresses_large_bodies_to_the_anthropic_api_only() {
let c = auto();
let none = env(&[]);
assert!(c.wants_gzip(API, MIN_BYTES, &none));
assert!(!c.wants_gzip(API, MIN_BYTES - 1, &none), "below threshold");
for url in [
"http://api.anthropic.com/v1/messages",
"https://api.anthropic.com:8443/v1/messages",
"https://gateway.example.com/v1/messages",
"https://api.anthropic.com.example.com/v1/messages",
"http://127.0.0.1:4000/v1/messages",
"not a url",
] {
assert!(!c.wants_gzip(url, 1 << 20, &none), "{url}");
}
assert!(c.wants_gzip("https://api.anthropic.com:443/v1/messages", 1 << 20, &none));
}
#[test]
fn proxies_custom_cas_and_the_switch_turn_it_off() {
let c = auto();
for &var in SKIP_VARS {
assert!(!c.wants_gzip(API, 1 << 20, env(&[(var, "http://proxy:3128")])), "{var}");
}
assert!(c.wants_gzip(API, 1 << 20, env(&[("HTTPS_PROXY", " ")])));
assert!(c.wants_gzip(API, 1 << 20, env(&[("HTTP_PROXY", "http://proxy:3128")])));
for off in ["0", "false", "OFF", " no "] {
assert!(!c.wants_gzip(API, 1 << 20, env(&[(ENV_SWITCH, off)])), "{off}");
}
for on in ["1", "true", "yes", ""] {
assert!(c.wants_gzip(API, 1 << 20, env(&[(ENV_SWITCH, on)])), "{on:?}");
}
}
#[test]
fn on_skips_endpoint_proxy_and_ca_checks_but_not_the_switch() {
let on = policy(RequestCompressionMode::On, 1);
let none = env(&[]);
assert!(on.wants_gzip("https://gateway.example.com/v1/messages", 1 << 20, &none));
assert!(on.wants_gzip("http://127.0.0.1:4000/v1/messages", 1 << 20, &none));
for &var in SKIP_VARS {
assert!(on.wants_gzip(API, 1 << 20, env(&[(var, "x")])), "{var}");
}
assert!(!on.wants_gzip(API, MIN_BYTES - 1, &none), "threshold still applies");
assert!(!on.wants_gzip(API, 1 << 20, env(&[(ENV_SWITCH, "0")])), "the env switch wins");
let off = policy(RequestCompressionMode::Off, 9);
assert!(!off.wants_gzip(API, 1 << 20, &none));
}
#[test]
fn config_level_is_clamped_and_shares_the_process_latch() {
let at = |level| RequestCompression::from_config(RequestCompressionMode::Auto, level).level;
assert_eq!(at(0), 1);
assert_eq!(at(6), 6);
assert_eq!(at(42), MAX_REQUEST_COMPRESSION_LEVEL);
assert_eq!(RequestCompression::default().level, DEFAULT_REQUEST_COMPRESSION_LEVEL);
assert_eq!(RequestCompression::default().mode, Mode::Auto);
assert!(Arc::ptr_eq(
&RequestCompression::from_config(RequestCompressionMode::On, 9).refused,
process_latch()
));
}
#[test]
fn the_runtime_takes_the_policy_from_config_and_passes_it_on() {
let mut rt = crate::runtime::Runtime::new_headless();
assert_eq!(rt.request_compression.mode, Mode::Auto);
assert_eq!(rt.request_compression.level, DEFAULT_REQUEST_COMPRESSION_LEVEL);
rt.apply_config(&agent_core::config::SynapsConfig {
request_compression: RequestCompressionMode::On,
request_compression_level: 6,
..Default::default()
});
assert_eq!(rt.request_compression.mode, Mode::On);
assert_eq!(rt.request_compression.level, 6);
let child = rt.clone();
assert_eq!((child.request_compression.mode, child.request_compression.level), (Mode::On, 6));
assert!(Arc::ptr_eq(&child.request_compression.refused, process_latch()));
}
#[tokio::test]
async fn a_higher_level_shrinks_more() {
const WORDS: &[&str] = &[
"fn", "let", "mut", "self", "impl", "match", "Some", "None", "Ok", "Err", "await",
"async", "return", "struct", "enum", "pub", "use", "crate", "error:", "warning:",
"test", "passed", "failed", "src/runtime/api.rs", "line", "=>", "{", "}", "(", ")",
];
let mut x: u64 = 0x2545_f491_4f6c_dd1d;
let text: Vec<&str> = (0..20_000)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
WORDS[(x % WORDS.len() as u64) as usize]
})
.collect();
let body = serde_json::json!({"messages": [{"role": "user", "content": text.join(" ")}]});
let raw: Bytes = serde_json::to_vec(&body).unwrap().into();
let fast = policy(RequestCompressionMode::On, 1).encode(API, &raw).await;
let best = policy(RequestCompressionMode::On, 9).encode(API, &raw).await;
assert!(fast.gzip && best.gzip);
let (best_len, fast_len) = (best.bytes.len(), fast.bytes.len());
assert!(best_len < fast_len, "level 9 {best_len} vs level 1 {fast_len}");
assert_eq!(gunzip(&best.bytes), raw.as_ref());
}
#[test]
fn modes_and_the_latch() {
let none = env(&[]);
let always = RequestCompression::always();
assert!(always.wants_gzip("http://127.0.0.1:1/v1/messages", 1, &none));
always.refused.store(true, Ordering::Relaxed);
assert!(!always.wants_gzip("http://127.0.0.1:1/v1/messages", 1, &none));
assert!(!RequestCompression::always().refused());
assert!(Arc::ptr_eq(
&RequestCompression::default().refused,
&RequestCompression::default().refused
));
}
#[tokio::test]
async fn encode_round_trips_and_shrinks() {
let raw = big_body();
let wire = RequestCompression::always().encode(API, &raw).await;
assert!(wire.gzip);
assert!(wire.bytes.len() * 2 < raw.len(), "{} -> {}", raw.len(), wire.bytes.len());
assert_eq!(gunzip(&wire.bytes), raw.as_ref());
let plain = auto().encode("http://127.0.0.1:1/v1/messages", &raw).await;
assert!(!plain.gzip);
assert_eq!(plain.bytes, raw);
}
#[tokio::test]
async fn incompressible_bodies_go_out_as_they_are() {
let mut x: u64 = 0x9e37_79b9_7f4a_7c15;
let raw: Bytes = (0..64 * 1024)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x as u8
})
.collect::<Vec<u8>>()
.into();
let wire = RequestCompression::always().encode(API, &raw).await;
assert!(!wire.gzip);
assert_eq!(wire.bytes, raw);
}
#[tokio::test]
async fn refusal_latches_only_when_the_uncompressed_resend_succeeds() {
let raw = big_body();
for (status, success, latched) in [(400, true, true), (415, true, true), (400, false, false)] {
let c = RequestCompression::always();
let wire = c.encode(API, &raw).await;
let mut resend = c.resend_uncompressed(&wire, &raw, status).expect("resend");
assert!(!resend.gzip);
assert_eq!(resend.bytes, raw);
c.settle(&mut resend, success);
assert_eq!(c.refused(), latched, "{status} success={success}");
c.settle(&mut resend, true);
assert_eq!(c.refused(), latched);
}
}
#[tokio::test]
async fn no_resend_for_other_statuses_or_uncompressed_requests() {
let raw = big_body();
let c = RequestCompression::always();
let wire = c.encode(API, &raw).await;
for status in [401, 403, 404, 413, 422, 429, 500, 529] {
assert!(c.resend_uncompressed(&wire, &raw, status).is_none(), "{status}");
}
let plain = WireBody::identity(&raw);
assert!(c.resend_uncompressed(&plain, &raw, 400).is_none());
let mut w = wire.clone();
c.settle(&mut w, true);
assert!(!c.refused(), "a normal success never latches");
}
}
#[cfg(test)]
mod transport_tests {
use super::RequestCompression;
use crate::runtime::api::{ApiMethods, ApiOptions};
use crate::runtime::telemetry::TelemetryLevel;
use crate::runtime::types::AuthState;
use crate::{StreamEvent, ToolRegistry};
use axum::body::Bytes;
use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse;
use axum::routing::post as axum_post;
use axum::Router;
use serde_json::{json, Value};
use std::io::Read;
use std::sync::{Arc, Mutex};
use tokio::sync::{mpsc, RwLock};
type Seen = Arc<Mutex<Vec<(Option<String>, Value)>>>;
#[derive(Clone, Copy)]
enum Server {
Accepts,
RefusesGzip,
RefusesAll,
}
const SSE: &str = concat!(
"data: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_1\",\"type\":\"message\",",
"\"role\":\"assistant\",\"content\":[],\"model\":\"claude-sonnet-4-6\",\"stop_reason\":null,",
"\"usage\":{\"input_tokens\":10,\"output_tokens\":1}}}\n\n",
"data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
"data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ok\"}}\n\n",
"data: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
"data: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"output_tokens\":2}}\n\n",
"data: {\"type\":\"message_stop\"}\n\n",
);
async fn server(mode: Server) -> (String, Seen) {
let seen: Seen = Arc::new(Mutex::new(Vec::new()));
let cap = Arc::clone(&seen);
let app = Router::new().route(
"/v1/messages",
axum_post(move |headers: HeaderMap, body: Bytes| {
let cap = Arc::clone(&cap);
async move {
let encoding = headers
.get("content-encoding")
.and_then(|v| v.to_str().ok())
.map(str::to_string);
let raw = match encoding.as_deref() {
Some("gzip") => {
let mut out = Vec::new();
flate2::read::GzDecoder::new(&body[..])
.read_to_end(&mut out)
.expect("valid gzip");
out
}
None => body.to_vec(),
Some(other) => panic!("unexpected encoding {other}"),
};
let json: Value = serde_json::from_slice(&raw).expect("JSON body");
let stream = json["stream"].as_bool().unwrap_or(false);
let refuse = match mode {
Server::Accepts => false,
Server::RefusesGzip => encoding.is_some(),
Server::RefusesAll => true,
};
cap.lock().unwrap().push((encoding, json));
if refuse {
return (
StatusCode::BAD_REQUEST,
r#"{"type":"error","error":{"type":"invalid_request_error","message":"bad request"}}"#,
)
.into_response();
}
if stream {
(StatusCode::OK, [("content-type", "text/event-stream")], SSE)
.into_response()
} else {
(
StatusCode::OK,
[("content-type", "application/json")],
json!({
"content": [{"type": "text", "text": "summary"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 3, "output_tokens": 1}
})
.to_string(),
)
.into_response()
}
}
}),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
(format!("http://{addr}"), seen)
}
fn auth() -> Arc<RwLock<AuthState>> {
Arc::new(RwLock::new(AuthState {
auth_token: "sk-test".into(),
auth_type: "api_key".into(),
refresh_token: None,
token_expires: Some(9_999_999_999_999),
auth_file_mtime: None,
bound_credential: None,
}))
}
fn messages() -> Vec<crate::SharedMessage> {
let log = "test runtime::request_encoding::tests ... ok\n".repeat(800);
vec![Arc::new(json!({"role": "user", "content": format!("why did this pass?\n{log}")}))]
}
fn options(url: &str, compression: RequestCompression) -> ApiOptions {
ApiOptions {
anthropic_base_url: Some(url.to_string()),
request_compression: compression,
..Default::default()
}
}
async fn stream_turn(options: &ApiOptions) -> crate::error::Result<Value> {
let (tx, _rx) = mpsc::unbounded_channel::<StreamEvent>();
ApiMethods::call_api_stream(
&auth(),
&reqwest::Client::new(),
"claude-sonnet-4-6",
&ToolRegistry::new(),
&None,
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&messages(),
tx,
0, 0,
options,
TelemetryLevel::Off,
)
.await
}
fn encodings(seen: &Seen) -> Vec<Option<String>> {
seen.lock().unwrap().iter().map(|(e, _)| e.clone()).collect()
}
#[tokio::test]
async fn streaming_body_goes_out_gzip_and_decodes_to_the_identity_json() {
let (url, seen) = server(Server::Accepts).await;
stream_turn(&options(&url, RequestCompression::always()))
.await
.unwrap();
stream_turn(&options(&url, RequestCompression::default()))
.await
.unwrap();
assert_eq!(encodings(&seen), [Some("gzip".to_string()), None]);
let seen = seen.lock().unwrap();
assert_eq!(seen[0].1, seen[1].1, "same request either way");
}
#[tokio::test]
async fn refused_gzip_is_resent_uncompressed_then_stays_off() {
let (url, seen) = server(Server::RefusesGzip).await;
let c = RequestCompression::always();
let opts = options(&url, c.clone());
stream_turn(&opts)
.await
.expect("a gzip refusal must never fail the turn");
assert!(c.refused(), "latched off once the plain resend went through");
stream_turn(&opts).await.unwrap();
assert_eq!(
encodings(&seen),
[Some("gzip".to_string()), None, None],
"refused + resend + next turn"
);
let seen = seen.lock().unwrap();
assert_eq!(seen[0].1, seen[1].1, "the resend is the same request");
}
#[tokio::test]
async fn a_genuine_400_still_fails_and_leaves_gzip_on() {
let (url, seen) = server(Server::RefusesAll).await;
let c = RequestCompression::always();
let err = stream_turn(&options(&url, c.clone())).await.unwrap_err();
assert!(err.to_string().contains("400"), "{err}");
assert_eq!(encodings(&seen), [Some("gzip".to_string()), None]);
assert!(!c.refused(), "the plain resend failed too: not the encoding");
}
#[tokio::test]
async fn every_transport_clamps_max_tokens_only_for_models_that_reject_overflow() {
let near = vec![Arc::new(json!({"role": "user", "content": "x".repeat(400_000)}))
as crate::SharedMessage];
for (model, clamped) in [("claude-sonnet-4-20250514", true), ("claude-sonnet-4-5-20250929", false)] {
let (url, seen) = server(Server::Accepts).await;
let opts = options(&url, RequestCompression::default());
let (tx, _rx) = mpsc::unbounded_channel::<StreamEvent>();
ApiMethods::call_api_stream(
&auth(),
&reqwest::Client::new(),
model,
&ToolRegistry::new(),
&None,
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&near,
tx,
0,
0,
&opts,
TelemetryLevel::Off,
)
.await
.expect("stream");
ApiMethods::call_api(
&auth(),
&reqwest::Client::new(),
model,
&ToolRegistry::new(),
&None,
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&near,
0,
&opts,
)
.await
.expect("sync");
ApiMethods::call_api_simple(
&auth(),
&reqwest::Client::new(),
model,
"summarize",
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&near,
0,
&opts,
)
.await
.expect("compaction");
let seen = seen.lock().unwrap();
assert_eq!(seen.len(), 3, "{model}");
for (_, body) in seen.iter() {
let max = body["max_tokens"].as_u64().unwrap();
assert_eq!(body["thinking"]["budget_tokens"], 16_384, "{model}");
if clamped {
assert!((20_480..40_000).contains(&max), "{model}: {max}");
} else {
assert_eq!(max, 64_000, "{model}");
}
}
}
}
#[tokio::test]
async fn thinking_display_reaches_both_turn_transports_not_compaction() {
let (url, seen) = server(Server::Accepts).await;
let opts = ApiOptions {
thinking_display: crate::config::ThinkingDisplay::Omitted,
..options(&url, RequestCompression::default())
};
let level = agent_core::reasoning::ReasoningLevel::High;
let model = "claude-opus-4-7";
let (tx, _rx) = mpsc::unbounded_channel::<StreamEvent>();
ApiMethods::call_api_stream(
&auth(), &reqwest::Client::new(), model, &ToolRegistry::new(), &None, 0, level,
&messages(), tx, 0, 0, &opts, TelemetryLevel::Off,
)
.await
.expect("stream");
ApiMethods::call_api(
&auth(), &reqwest::Client::new(), model, &ToolRegistry::new(), &None, 0, level,
&messages(), 0, &opts,
)
.await
.expect("sync");
ApiMethods::call_api_simple(
&auth(), &reqwest::Client::new(), model, "summarize", 0, level, &messages(), 0, &opts,
)
.await
.expect("compaction");
let seen = seen.lock().unwrap();
let displays: Vec<&Value> = seen.iter().map(|(_, b)| &b["thinking"]["display"]).collect();
assert_eq!(displays, [&json!("omitted"), &json!("omitted"), &json!("summarized")]);
}
#[tokio::test]
async fn sync_and_compaction_paths_compress_and_fall_back_too() {
let (url, seen) = server(Server::RefusesGzip).await;
let c = RequestCompression::always();
let opts = options(&url, c.clone());
ApiMethods::call_api(
&auth(),
&reqwest::Client::new(),
"claude-sonnet-4-6",
&ToolRegistry::new(),
&None,
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&messages(),
0,
&opts,
)
.await
.expect("sync call survives the refusal");
assert!(c.refused());
let (url, seen2) = server(Server::RefusesGzip).await;
let c = RequestCompression::always();
let text = ApiMethods::call_api_simple(
&auth(),
&reqwest::Client::new(),
"claude-haiku-4-5",
"summarize",
0,
agent_core::reasoning::ReasoningLevel::Adaptive,
&messages(),
0,
&options(&url, c.clone()),
)
.await
.expect("compaction survives the refusal");
assert_eq!(text, "summary");
assert!(c.refused());
for seen in [&seen, &seen2] {
assert_eq!(encodings(seen), [Some("gzip".to_string()), None]);
}
}
}