use super::RoundObservation;
pub(super) fn num_ctx_input_ceiling(num_ctx: Option<u32>, input_ceiling_pct: u32) -> Option<usize> {
num_ctx
.map(|c| (c as usize) * (input_ceiling_pct as usize) / 100)
.filter(|&c| c > 0)
}
pub(super) fn initial_send_budget(
max_ok_input: Option<u32>,
safe_context: Option<u32>,
num_ctx: Option<u32>,
input_ceiling_pct: u32,
) -> Option<usize> {
let cached = match (max_ok_input, safe_context) {
(Some(m), Some(s)) => Some(m.max(s) as usize),
(m, s) => m.or(s).map(|c| c as usize),
};
match (cached, num_ctx_input_ceiling(num_ctx, input_ceiling_pct)) {
(Some(budget), Some(ceiling)) => Some(budget.min(ceiling)),
(budget, ceiling) => budget.or(ceiling),
}
}
pub(super) fn calibrate_up(est: usize, ratio: f32) -> usize {
(est as f32 * ratio).ceil() as usize
}
pub(super) fn calibrate_down(real: usize, ratio: f32) -> usize {
(real as f32 / ratio).floor() as usize
}
pub(super) fn sanitize_estimate_ratio(estimate_ratio: Option<f32>) -> f32 {
estimate_ratio
.filter(|r| r.is_finite() && (0.5..=3.0).contains(r))
.unwrap_or(1.0)
}
pub(super) fn emit_accepted(
hook: &mut Option<&mut dyn FnMut(RoundObservation)>,
round_usage: Option<crate::TokenUsage>,
truncation_suspect: bool,
estimated_tokens: usize,
) {
if truncation_suspect {
return;
}
if let (Some(hook), Some(u)) = (hook.as_deref_mut(), round_usage) {
hook(RoundObservation::Accepted {
prompt_tokens: u.input_tokens,
estimated_tokens,
});
}
}
#[cfg(test)]
mod send_budget_tests {
use super::super::compress::{compression_trigger, CompressionTriggerLimits};
use super::{initial_send_budget, num_ctx_input_ceiling};
use crate::CompactionTriggerPolicy;
#[test]
fn first_turn_fresh_cache_trigger_sees_the_num_ctx_ceiling() {
let budget = initial_send_budget(None, None, Some(4096), 80);
assert_eq!(budget, Some(3276), "80% of 4096 — reply headroom reserved");
let trigger = compression_trigger(
3,
41_355,
39_900,
CompressionTriggerLimits {
count_threshold: 40,
token_threshold: None,
send_budget: budget,
tool_tokens: 1_432,
policy: CompactionTriggerPolicy::HeadroomAware,
has_authoritative_headroom: true,
},
)
.expect("the ceiling must fire the trigger on the first turn");
assert!(trigger.hard_budget, "a real token budget, not a soft halve");
assert_eq!(
trigger.budget,
3_276 - 1_432,
"budget lands in message space: ceiling minus tool-schema tokens"
);
assert_eq!(trigger.max_messages, None, "no count firing here");
}
#[test]
fn absent_num_ctx_leaves_the_budget_unchanged() {
assert_eq!(initial_send_budget(None, None, None, 80), None);
assert_eq!(
initial_send_budget(Some(2_000), None, None, 80),
Some(2_000)
);
assert_eq!(
initial_send_budget(None, Some(5_000), None, 80),
Some(5_000)
);
assert_eq!(
initial_send_budget(Some(2_000), Some(5_000), None, 80),
Some(5_000),
"an HWM below safe_context is a floor, not a cap — safe_context wins"
);
assert_eq!(
compression_trigger(
3,
41_355,
39_900,
CompressionTriggerLimits {
count_threshold: 40,
token_threshold: None,
send_budget: None,
tool_tokens: 1_432,
policy: CompactionTriggerPolicy::HeadroomAware,
has_authoritative_headroom: false,
},
),
None
);
}
#[test]
fn cached_budget_is_max_of_proven_and_believed() {
assert_eq!(
initial_send_budget(Some(6_068), Some(26_214), None, 80),
Some(26_214),
"HWM below safe_context → safe_context"
);
assert_eq!(
initial_send_budget(Some(8_734), Some(6_553), None, 80),
Some(8_734),
"HWM above safe_context (proven beyond the claim) → HWM"
);
assert_eq!(
initial_send_budget(Some(800_000), Some(64_000), None, 80),
Some(800_000),
"post-cw-400: max_ok_input is the authoritative cap"
);
assert_eq!(
initial_send_budget(Some(800_000), Some(800_000), None, 80),
Some(800_000)
);
}
#[test]
fn calibration_helpers_round_in_the_safe_direction() {
use super::{calibrate_down, calibrate_up, sanitize_estimate_ratio};
assert_eq!(calibrate_up(6_068, 1.0), 6_068);
assert_eq!(calibrate_down(6_068, 1.0), 6_068);
assert_eq!(calibrate_up(1_000, 1.3), 1_300);
assert_eq!(calibrate_down(1_000, 1.3), 769, "floor, never round up");
assert_eq!(calibrate_up(3, 1.5), 5, "4.5 ceils to 5");
assert_eq!(calibrate_down(3, 2.0), 1, "1.5 floors to 1");
assert_eq!(sanitize_estimate_ratio(None), 1.0);
assert_eq!(sanitize_estimate_ratio(Some(f32::NAN)), 1.0);
assert_eq!(sanitize_estimate_ratio(Some(0.1)), 1.0);
assert_eq!(sanitize_estimate_ratio(Some(5.0)), 1.0);
assert_eq!(sanitize_estimate_ratio(Some(1.29)), 1.29);
assert_eq!(sanitize_estimate_ratio(Some(0.5)), 0.5, "clamp inclusive");
assert_eq!(sanitize_estimate_ratio(Some(3.0)), 3.0, "clamp inclusive");
}
#[test]
fn calibration_composes_across_the_trigger_boundary() {
use super::{calibrate_down, calibrate_up};
let cal = 1.3_f32;
let send_budget = 8_734_usize; let tool_tokens_est = 1_000_usize; let tool_tokens_real = calibrate_up(tool_tokens_est, cal); let current_real = calibrate_up(9_000, cal); let trigger = compression_trigger(
3,
current_real,
9_000,
CompressionTriggerLimits {
count_threshold: 40,
token_threshold: None,
send_budget: Some(send_budget),
tool_tokens: tool_tokens_real,
policy: CompactionTriggerPolicy::HeadroomAware,
has_authoritative_headroom: true,
},
)
.expect("over-budget context fires the guard");
assert!(trigger.hard_budget);
assert_eq!(trigger.budget, send_budget - tool_tokens_real);
let pipeline_budget = calibrate_down(trigger.budget, cal);
assert_eq!(pipeline_budget, calibrate_down(8_734 - 1_300, cal));
assert!(
pipeline_budget < trigger.budget,
"ratio > 1: the estimate-space target is tighter than the real one"
);
}
#[test]
fn ceiling_composes_with_cached_budgets_via_min() {
assert_eq!(
initial_send_budget(Some(2_135), None, Some(4_096), 80),
Some(2_135)
);
assert_eq!(
initial_send_budget(None, Some(104_857), Some(4_096), 80),
Some(3_276)
);
assert_eq!(
initial_send_budget(Some(104_857), Some(104_857), Some(4_096), 80),
Some(3_276)
);
}
#[test]
fn zero_or_tiny_num_ctx_is_no_budget_at_all() {
assert_eq!(num_ctx_input_ceiling(None, 80), None);
assert_eq!(num_ctx_input_ceiling(Some(0), 80), None);
assert_eq!(
num_ctx_input_ceiling(Some(1), 80),
None,
"80% rounds to zero"
);
assert_eq!(initial_send_budget(None, None, Some(0), 80), None);
assert_eq!(
initial_send_budget(Some(2_000), None, Some(0), 80),
Some(2_000)
);
}
}