use super::*;
use crate::agentic::http_loop_tests::DisplayReplay;
use crate::agentic::trim::estimate_request_tokens;
use serde_json::{json, Value};
use std::sync::Mutex;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, Request, Respond, ResponseTemplate};
struct ReportMeasuredUsage {
requests: Arc<Mutex<Vec<Value>>>,
usage: ReportedUsage,
replay: DisplayReplay,
}
enum ReportedUsage {
Absent,
PromptOnly,
Complete,
}
impl Respond for ReportMeasuredUsage {
fn respond(&self, request: &Request) -> ResponseTemplate {
let body: Value = serde_json::from_slice(&request.body).unwrap();
if self.replay.take(request) {
return ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_string(
"data: {\"choices\":[{\"delta\":{\"content\":\"4.\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n",
);
}
let estimate = estimate_request_tokens(
body["messages"].as_array().unwrap(),
body.get("tools"),
Default::default(),
);
self.requests.lock().unwrap().push(body);
let mut reply = json!({
"choices": [{"finish_reason":"stop","message":{
"role":"assistant","content":"4."
}}]
});
if !matches!(self.usage, ReportedUsage::Absent) {
reply["usage"] = json!({"prompt_tokens":estimate * 3 / 2});
if matches!(self.usage, ReportedUsage::Complete) {
reply["usage"]["completion_tokens"] = json!(2);
}
}
self.replay.arm(request);
ResponseTemplate::new(200).set_body_json(reply)
}
}
async fn consecutive_requests(usage: ReportedUsage) -> Vec<Value> {
let server = MockServer::start().await;
let requests = Arc::new(Mutex::new(Vec::new()));
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(ReportMeasuredUsage {
requests: requests.clone(),
usage,
replay: Default::default(),
})
.mount(&server)
.await;
let mut config = TurnDriverConfig::new(
server.uri(),
"calibration-fixture",
BackendKind::Openai,
"driver-calibration-test-no-workspace",
);
config.openai_api = crate::OpenAiApi::ChatCompletions;
config.num_ctx = Some(65_536);
config.max_tool_rounds = 2;
config.workflow_grace_rounds = 0;
config.mid_loop_trim_threshold = usize::MAX;
let mut messages = vec![MemMessage::system("Answer the current question.")];
for i in 0..60 {
messages.push(MemMessage::user(format!(
"old question {i} {}",
"x".repeat(1_200)
)));
messages.push(MemMessage::assistant(format!(
"old answer {i} {}",
"y".repeat(1_200)
)));
}
let task = "What is 2 + 2?";
messages.push(MemMessage::user(task));
let driver = TurnDriver::with_transcript(config, messages);
for _ in 0..2 {
let result = run_one_turn(
&driver.config,
&driver.runtime.clone(),
driver.transcript(),
task,
)
.await
.unwrap();
assert!(result.error.is_none(), "{:?}", result.error);
assert_eq!(crate::agentic::model_explanation(&result.reply), "4.");
}
let requests = requests.lock().unwrap().clone();
assert_eq!(requests.len(), 2);
requests
}
fn raw(request: &Value) -> usize {
estimate_request_tokens(
request["messages"].as_array().unwrap(),
request.get("tools"),
Default::default(),
)
}
#[tokio::test]
async fn consecutive_driver_turns_keep_usage_calibration_and_shrink_the_next_request() {
assert_calibrated_shrink(&consecutive_requests(ReportedUsage::Complete).await);
}
#[tokio::test]
async fn consecutive_driver_turns_calibrate_prompt_usage_without_completion_usage() {
assert_calibrated_shrink(&consecutive_requests(ReportedUsage::PromptOnly).await);
}
fn assert_calibrated_shrink(requests: &[Value]) {
assert!(
raw(&requests[0]) < 65_536 * 80 / 100,
"cold fixture fits the declared budget"
);
assert!(
raw(&requests[0]) * 3 / 2 > 65_536 * 80 / 100,
"measured fixture needs compaction"
);
assert!(
raw(&requests[1]) < raw(&requests[0]),
"the next worker must preserve learned usage and send a smaller prompt"
);
assert!(requests[1]["messages"]
.as_array()
.unwrap()
.iter()
.any(|message| message["content"] == "What is 2 + 2?"));
}
#[tokio::test]
async fn consecutive_driver_turns_without_usage_preserve_the_cold_budget() {
let requests = consecutive_requests(ReportedUsage::Absent).await;
assert_eq!(
raw(&requests[1]),
raw(&requests[0]),
"absence of usage must not invent calibration"
);
assert!(requests[1]["messages"]
.as_array()
.unwrap()
.iter()
.skip(1)
.eq(requests[0]["messages"].as_array().unwrap().iter().skip(1)));
}
#[tokio::test]
async fn inferred_overflows_cannot_permanently_refuse_the_next_small_driver_prompt() {
let server = MockServer::start().await;
let requests = Arc::new(Mutex::new(Vec::<Value>::new()));
let captured = requests.clone();
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(move |request: &Request| {
captured
.lock()
.unwrap()
.push(serde_json::from_slice(&request.body).unwrap());
ResponseTemplate::new(200).set_body_json(json!({
"choices": [{"finish_reason":"stop","message":{
"role":"assistant","content":"Hello."
}}]
}))
})
.mount(&server)
.await;
let mut config = TurnDriverConfig::new(
server.uri(),
"calibration-fixture",
BackendKind::Openai,
"driver-calibration-test-no-workspace",
);
config.openai_api = crate::OpenAiApi::ChatCompletions;
config.num_ctx = Some(65_536);
config.max_tool_rounds = 2;
config.workflow_grace_rounds = 0;
config.mid_loop_trim_threshold = usize::MAX;
let task = "Hello";
let driver = TurnDriver::with_transcript(config, vec![MemMessage::user(task)]);
{
let mut state = driver.runtime.compress_state.lock().await;
for _ in 0..18 {
let applied = state.calibration.ratio(None);
state.calibration.overflow(applied);
}
}
let result = run_one_turn(
&driver.config,
&driver.runtime.clone(),
driver.transcript(),
task,
)
.await
.unwrap();
assert!(
result.error.is_none(),
"unmeasured overflow guesses must not permanently block a small prompt: {:?}",
result.error
);
assert_eq!(crate::agentic::model_explanation(&result.reply), "Hello.");
let requests = requests.lock().unwrap();
assert_eq!(requests.len(), 1, "one primary; no display reissue (#2372)");
for request in requests.iter() {
assert!(request["messages"]
.as_array()
.unwrap()
.iter()
.any(|message| message["role"] == "user" && message["content"] == task));
}
}
#[tokio::test]
async fn an_exhausted_context_overflow_ends_failed_not_untyped() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(
ResponseTemplate::new(500)
.set_body_json(json!({"error":{"message":"Context size has been exceeded."}})),
)
.mount(&server)
.await;
let mut config = TurnDriverConfig::new(
server.uri(),
"calibration-fixture",
BackendKind::Openai,
"driver-calibration-test-no-workspace",
);
config.openai_api = crate::OpenAiApi::ChatCompletions;
config.num_ctx = Some(65_536);
let task = "What is 2 + 2?";
let driver = TurnDriver::with_transcript(config, vec![MemMessage::user(task)]);
let result = run_one_turn(
&driver.config,
&driver.runtime.clone(),
driver.transcript(),
task,
)
.await
.unwrap();
assert!(result.error.is_some());
assert_eq!(result.end_reason, Some(crate::TurnEndReason::Failed));
assert_eq!(
result.error_class,
Some(crate::agentic::observability::ErrorClass::ContextExceeded)
);
}