use regex::Regex;
use std::sync::LazyLock;
use std::time::Instant;
static SHORTHAND_START_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^\s*\+(\d+(?:\.\d+)?)\s*(k|m|b)\b").unwrap());
static SHORTHAND_END_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\s\+(\d+(?:\.\d+)?)\s*(k|m|b)\s*[.!?]?\s*$").unwrap()
});
static VERBOSE_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?i)\b(?:use|spend)\s+(\d+(?:\.\d+)?)\s*(k|m|b)\s*tokens?\b").unwrap()
});
static VERBOSE_RE_G: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?i)\b(?:use|spend)\s+(\d+(?:\.\d+)?)\s*(k|m|b)\s*tokens?\b").unwrap()
});
const COMPLETION_THRESHOLD: f64 = 0.9;
const DIMINISHING_RETURNS_THRESHOLD: u64 = 3;
const LOW_PRODUCTION_TOKENS: u64 = 500;
fn parse_budget_match(value: &str, suffix: &str) -> f64 {
let value: f64 = value.parse().unwrap_or(0.0);
let multiplier = match suffix.to_lowercase().as_str() {
"k" => 1_000.0,
"m" => 1_000_000.0,
"b" => 1_000_000_000.0,
_ => 1.0,
};
value * multiplier
}
pub fn parse_token_budget(text: &str) -> Option<f64> {
if let Some(caps) = SHORTHAND_START_RE.captures(text) {
let value = caps.get(1).map(|m| m.as_str()).unwrap();
let suffix = caps.get(2).map(|m| m.as_str()).unwrap();
return Some(parse_budget_match(value, suffix));
}
if let Some(caps) = SHORTHAND_END_RE.captures(text) {
let value = caps.get(1).map(|m| m.as_str()).unwrap();
let suffix = caps.get(2).map(|m| m.as_str()).unwrap();
return Some(parse_budget_match(value, suffix));
}
if let Some(caps) = VERBOSE_RE.captures(text) {
let value = caps.get(1).map(|m| m.as_str()).unwrap();
let suffix = caps.get(2).map(|m| m.as_str()).unwrap();
return Some(parse_budget_match(value, suffix));
}
None
}
#[derive(Debug)]
pub struct BudgetPosition {
pub start: usize,
pub end: usize,
}
pub fn find_token_budget_positions(text: &str) -> Vec<BudgetPosition> {
let mut positions = Vec::new();
if let Some(m) = SHORTHAND_START_RE.find(text) {
let offset = m.start() + m.as_str().len() - m.as_str().trim_start().len();
positions.push(BudgetPosition {
start: offset,
end: m.end(),
});
}
if let Some(m) = SHORTHAND_END_RE.find(text) {
let end_start = m.start() + 1;
let already_covered = positions
.iter()
.any(|p| end_start >= p.start && end_start < p.end);
if !already_covered {
positions.push(BudgetPosition {
start: end_start,
end: m.end(),
});
}
}
for m in VERBOSE_RE_G.find_iter(text) {
positions.push(BudgetPosition {
start: m.start(),
end: m.end(),
});
}
positions
}
pub fn get_budget_continuation_message(pct: f64, turn_tokens: u64, budget: f64) -> String {
format!(
"Stopped at {pct}% of token target ({turn_tokens} / {budget}). Keep working \u{2014} do not summarize."
)
}
#[derive(Debug)]
pub struct BudgetTracker {
pub continuation_count: u64,
pub last_delta_tokens: u64,
pub last_global_turn_tokens: u64,
pub started_at: Instant,
}
impl BudgetTracker {
pub fn new() -> Self {
Self {
continuation_count: 0,
last_delta_tokens: 0,
last_global_turn_tokens: 0,
started_at: Instant::now(),
}
}
}
impl Default for BudgetTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TokenBudgetCompletion {
pub pct: f64,
pub tokens: u64,
pub budget: f64,
pub continuation_count: u64,
pub duration_ms: u64,
pub diminishing_returns: bool,
}
#[derive(Debug)]
pub enum TokenBudgetDecision {
Continue { nudge_message: String },
Stop { completion: Option<TokenBudgetCompletion> },
}
pub fn check_token_budget(
tracker: &mut BudgetTracker,
_agent_id: Option<&str>,
budget: Option<f64>,
turn_tokens: u64,
) -> TokenBudgetDecision {
let budget = match budget {
Some(b) if b > 0.0 => b,
_ => return TokenBudgetDecision::Stop { completion: None },
};
if _agent_id.is_some() {
return TokenBudgetDecision::Stop { completion: None };
}
let current_delta = if turn_tokens >= tracker.last_global_turn_tokens {
turn_tokens - tracker.last_global_turn_tokens
} else {
turn_tokens
};
let diminishing_returns = tracker.continuation_count >= DIMINISHING_RETURNS_THRESHOLD
&& current_delta < LOW_PRODUCTION_TOKENS
&& tracker.last_delta_tokens < LOW_PRODUCTION_TOKENS;
let pct = if budget > 0.0 {
(turn_tokens as f64 / budget)
} else {
1.0
};
if pct < COMPLETION_THRESHOLD && !diminishing_returns {
tracker.continuation_count += 1;
tracker.last_delta_tokens = current_delta;
tracker.last_global_turn_tokens = turn_tokens;
return TokenBudgetDecision::Continue {
nudge_message: get_budget_continuation_message((pct * 100.0) as u64 as f64, turn_tokens, budget),
};
}
let completion = if tracker.continuation_count > 0 || diminishing_returns {
Some(TokenBudgetCompletion {
pct,
tokens: turn_tokens,
budget,
continuation_count: tracker.continuation_count,
duration_ms: tracker.started_at.elapsed().as_millis() as u64,
diminishing_returns,
})
} else {
None
};
TokenBudgetDecision::Stop { completion }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_token_budget_shorthand_start() {
assert_eq!(parse_token_budget("+500k"), Some(500_000.0));
assert_eq!(parse_token_budget("+2m"), Some(2_000_000.0));
assert_eq!(parse_token_budget("+1.5b"), Some(1_500_000_000.0));
}
#[test]
fn test_parse_token_budget_shorthand_end() {
assert_eq!(parse_token_budget("I want +500k."), Some(500_000.0));
}
#[test]
fn test_parse_token_budget_verbose() {
assert_eq!(parse_token_budget("use 2M tokens"), Some(2_000_000.0));
assert_eq!(parse_token_budget("spend 500k tokens"), Some(500_000.0));
}
#[test]
fn test_parse_token_budget_none() {
assert!(parse_token_budget("hello world").is_none());
}
#[test]
fn test_find_positions() {
let positions = find_token_budget_positions("+500k");
assert!(!positions.is_empty());
}
#[test]
fn test_budget_continuation_message() {
let msg = get_budget_continuation_message(80.0, 160000, 200000.0);
assert!(msg.contains("80%"));
assert!(msg.contains("160000"));
assert!(msg.contains("200000"));
}
#[test]
fn test_budget_tracker_new() {
let t = BudgetTracker::new();
assert_eq!(t.continuation_count, 0);
assert_eq!(t.last_delta_tokens, 0);
}
#[test]
fn test_check_no_budget() {
let mut t = BudgetTracker::new();
let d = check_token_budget(&mut t, None, None, 100);
assert!(matches!(d, TokenBudgetDecision::Stop { completion: None }));
let d2 = check_token_budget(&mut t, None, Some(0.0), 100);
assert!(matches!(d2, TokenBudgetDecision::Stop { completion: None }));
}
#[test]
fn test_check_subagent_skips_budget() {
let mut t = BudgetTracker::new();
let d = check_token_budget(&mut t, Some("sub1"), Some(5_000.0), 100);
assert!(matches!(d, TokenBudgetDecision::Stop { completion: None }));
}
#[test]
fn test_check_continue_under_threshold() {
let mut t = BudgetTracker::new();
let d = check_token_budget(&mut t, None, Some(5_000.0), 100);
match d {
TokenBudgetDecision::Continue { nudge_message } => {
assert!(nudge_message.contains("Keep working"));
}
other => panic!("Expected Continue, got {:?}", other),
}
assert_eq!(t.continuation_count, 1);
}
#[test]
fn test_check_stop_over_threshold() {
let mut t = BudgetTracker::new();
let d = check_token_budget(&mut t, None, Some(5_000.0), 5_000);
match d {
TokenBudgetDecision::Stop { completion } => {
assert!(completion.is_none());
}
other => panic!("Expected Stop, got {:?}", other),
}
}
#[test]
fn test_check_continuation_then_stop() {
let mut t = BudgetTracker::new();
let d = check_token_budget(&mut t, None, Some(5_000.0), 100);
assert!(matches!(d, TokenBudgetDecision::Continue { .. }));
let d = check_token_budget(&mut t, None, Some(5_000.0), 4_800);
match d {
TokenBudgetDecision::Stop { completion } => {
let c = completion.expect("should have completion");
assert!(c.pct >= 0.9);
assert_eq!(c.continuation_count, 1);
assert!(!c.diminishing_returns);
}
other => panic!("Expected Stop, got {:?}", other),
}
}
#[test]
fn test_check_diminishing_returns() {
let mut t = BudgetTracker::new();
for _ in 0..3 {
let tokens = t.last_global_turn_tokens + 100;
let d = check_token_budget(&mut t, None, Some(10_000.0), tokens);
assert!(matches!(d, TokenBudgetDecision::Continue { .. }));
}
let tokens = t.last_global_turn_tokens + 100;
let d = check_token_budget(&mut t, None, Some(10_000.0), tokens);
match d {
TokenBudgetDecision::Stop { completion } => {
let c = completion.expect("should have completion");
assert!(c.diminishing_returns);
}
TokenBudgetDecision::Continue { .. } => {
panic!("Expected Stop due to diminishing returns");
}
}
}
}