use super::*;
#[test]
fn responses_budget_state_reports_one_enforced_ceiling() {
use super::ResponsesBudgetState;
assert_eq!(num_ctx_input_ceiling(Some(32_768), 80, None), Some(26_214));
assert_eq!(32_768 - 16_000, 16_768);
let mut state = ResponsesBudgetState::new(
Some(32_768),
80,
resolve_output_allowance(None, Some(Cognition::Meticulous)),
None,
None,
None,
);
state.set_tool_schema_tokens(0);
assert_eq!(
state.actionable_input_budget(),
Some(16_768),
"dispatch preflight + get_context_remaining ceiling"
);
assert_eq!(
state.exposure_budget(),
Some(16_768),
"tool exposure clip budget"
);
assert_eq!(
state.learned_hard_ceiling(),
Some(16_768),
"hard ceiling (seeds cw-400 recovery)"
);
assert_eq!(
state.recovered_budget_for_window(32_768),
16_768,
"cw-400 recovery seed"
);
let recovered = state.recovered_budget_for_window(32_768);
state.recover_from_cw400(recovered);
assert_eq!(
state.compaction_budget(1.0, true),
16_768,
"compaction target"
);
}
#[test]
fn learned_hard_ceiling_only_ever_tightens() {
use super::ResponsesBudgetState;
let mut state = ResponsesBudgetState::new(
Some(32_768),
80,
resolve_output_allowance(None, Some(Cognition::Meticulous)),
None,
None,
None,
);
assert_eq!(state.learned_hard_ceiling(), Some(16_768));
state.recover_from_cw400(8_000);
assert_eq!(state.learned_hard_ceiling(), Some(8_000));
assert_eq!(
state.soft_send_budget(),
Some(8_000),
"soft collapses to hard"
);
assert_eq!(state.actionable_input_budget(), Some(8_000));
state.recover_from_cw400(20_000);
assert_eq!(
state.learned_hard_ceiling(),
Some(8_000),
"a later value cannot raise the learned ceiling"
);
assert_eq!(state.recovered_budget_for_window(1_000_000), 8_000);
}
#[test]
fn no_cognition_reserve_reports_the_percentage_ceiling() {
use super::ResponsesBudgetState;
let state = ResponsesBudgetState::new(Some(32_768), 80, None, None, None, None);
assert_eq!(state.learned_hard_ceiling(), Some(26_214));
assert_eq!(state.actionable_input_budget(), Some(26_214));
assert_eq!(state.exposure_budget(), Some(26_214));
}
#[test]
fn tightening_and_overhead_only_shrink_reported_remaining() {
use super::ResponsesBudgetState;
use crate::agentic::budget::render_context_budget;
let mut state = ResponsesBudgetState::new(
Some(32_768),
80,
resolve_output_allowance(None, Some(Cognition::Meticulous)),
None,
None,
None,
);
let render = |s: &ResponsesBudgetState, used: usize| {
render_context_budget(
used,
s.learned_hard_ceiling(),
s.num_ctx(),
s.input_ceiling_pct(),
15,
)
};
assert!(render(&state, 4_000).contains("12768 tokens remaining"));
assert!(render(&state, 8_000).contains("8768 tokens remaining"));
state.recover_from_cw400(8_000);
assert!(render(&state, 4_000).contains("4000 tokens remaining"));
}
#[test]
fn cloud_responses_none_num_ctx_stays_ceiling_less() {
use super::ResponsesBudgetState;
let state = ResponsesBudgetState::new(
None,
80,
resolve_output_allowance(None, Some(Cognition::Meticulous)),
None,
None,
None,
);
assert_eq!(state.learned_hard_ceiling(), None, "no window → no ceiling");
assert_eq!(
state.actionable_input_budget(),
None,
"nothing to refuse against"
);
assert_eq!(state.exposure_budget(), None, "no live budget → don't clip");
}
#[test]
fn zero_input_room_window_stays_authoritative_zero() {
use super::ResponsesBudgetState;
let state = ResponsesBudgetState::new(
Some(16_000),
80,
resolve_output_allowance(None, Some(Cognition::Meticulous)),
Some(2_000),
None,
None,
);
assert_eq!(
state.learned_hard_ceiling(),
Some(0),
"no input room is an authoritative zero"
);
assert_eq!(
state.soft_send_budget(),
Some(0),
"authoritative zero shadows cached evidence"
);
assert_eq!(state.actionable_input_budget(), Some(0));
}
#[test]
fn responses_budget_moves_only_in_the_safe_direction() {
use super::ResponsesBudgetState;
let est = crate::tokens::TokenEstimation::default();
let input = [serde_json::json!({"role": "user", "content": "hi"})];
let loose = ResponsesBudgetState::new(Some(65_536), 80, None, None, None, None);
let tight = ResponsesBudgetState::new(Some(32_768), 80, None, None, None, None);
assert!(
tight.actionable_input_budget() <= loose.actionable_input_budget(),
"a smaller window cannot raise the actionable budget",
);
let capped = ResponsesBudgetState::new(Some(65_536), 80, None, None, Some(8_000), Some(6_000));
assert!(
capped.actionable_input_budget() <= loose.actionable_input_budget(),
"a tighter safe_context + mid-loop trim cannot raise it",
);
let s = ResponsesBudgetState::new(Some(65_536), 80, None, None, None, None);
let ceiling = s.actionable_input_budget().expect("window binds");
let rem = |instr: Option<&str>, tools: Option<&[serde_json::Value]>| {
ceiling.saturating_sub(crate::agentic::estimate_responses_request_tokens(
instr, &input, tools, est,
))
};
let big = "POLICY. ".repeat(200);
assert!(
rem(Some(&big), None) <= rem(None, None),
"instructions never increase remaining",
);
let wire_tools = [serde_json::json!({
"type": "function", "name": "run_command",
"description": "run a shell command",
"parameters": {"type": "object",
"properties": {"command": {"type": "string"}}, "required": ["command"]}
})];
assert!(
rem(None, Some(&wire_tools)) <= rem(None, None),
"tool schemas never increase remaining",
);
let mut s = ResponsesBudgetState::new(Some(65_536), 80, None, None, None, None);
let before = s.actionable_input_budget();
s.recover_from_cw400(10_000);
let after = s.actionable_input_budget();
assert!(
after <= before && after == Some(10_000),
"a cw-400 tightens the actionable budget to the recovered window",
);
s.recover_from_cw400(50_000);
assert_eq!(
s.actionable_input_budget(),
Some(10_000),
"a later larger recovered window cannot raise the hard ceiling",
);
}