use super::*;
pub(super) const MAX_TOOL_BODY_TOKENS: u32 = 1024;
pub(super) fn advance_envelope_streak(inside_parameter_body: bool, streak: u32) -> (u32, bool) {
if inside_parameter_body {
(streak, false)
} else {
let s = streak.saturating_add(1);
(s, s > MAX_TOOL_BODY_TOKENS)
}
}
pub fn update_tool_param_state(a: &mut ActiveSeq, tok: u32) {
if a.inside_thinking {
return;
}
if a.tool_call_start_token == Some(tok) {
a.inside_tool_body = true;
a.tool_body_streak_tokens = 0;
return;
}
if a.tool_call_end_token == Some(tok) {
a.inside_tool_body = false;
a.tool_body_streak_tokens = 0;
a.inside_parameter_body = false;
a.param_body_chars_emitted = 0;
return;
}
if !a.inside_tool_body {
return;
}
let (streak, exceeded) =
advance_envelope_streak(a.inside_parameter_body, a.tool_body_streak_tokens);
a.tool_body_streak_tokens = streak;
if exceeded {
tracing::warn!(
streak = a.tool_body_streak_tokens,
"Stuck in tool-call ENVELOPE for {MAX_TOOL_BODY_TOKENS}+ tokens with no </tool_call> (excludes parameter-value content); ending response (model never closed the envelope — would otherwise burn to max_tokens). Sanitizer will salvage what it can."
);
a.guard_stop = Some("tool_envelope_stuck");
a.finished = true;
}
const TOK_LT: u32 = 27;
const TOK_PARAMETER: u32 = 15704;
const TOK_EQ: u32 = 28;
const TOK_GT: u32 = 29;
const TOK_LT_SLASH: u32 = 510;
if a.inside_parameter_body {
match a.param_close_pending {
0 => {
if tok == TOK_LT_SLASH {
a.param_close_pending = 1;
} else {
a.param_body_chars_emitted = a.param_body_chars_emitted.saturating_add(1);
}
}
1 => {
if tok == TOK_PARAMETER {
a.param_close_pending = 2;
} else {
a.param_close_pending = 0;
a.param_body_chars_emitted = a.param_body_chars_emitted.saturating_add(2);
}
}
_ => {
a.param_close_pending = 0;
if tok == TOK_GT {
a.inside_parameter_body = false;
a.param_body_chars_emitted = 0;
a.tool_body_streak_tokens = 0;
} else {
a.param_body_chars_emitted = a.param_body_chars_emitted.saturating_add(3);
}
}
}
return;
}
if tok != TOK_GT {
return;
}
let n = a.output_tokens.len();
let n_for_lookback = if n > 0 && a.output_tokens[n - 1] == tok {
n - 1
} else {
n
};
if n_for_lookback < 3 {
return;
}
let start = n_for_lookback.saturating_sub(8);
let window = &a.output_tokens[start..n_for_lookback];
let mut sig_idx: Option<usize> = None;
for i in 0..window.len().saturating_sub(2) {
if window[i] == TOK_LT && window[i + 1] == TOK_PARAMETER && window[i + 2] == TOK_EQ {
sig_idx = Some(i + 3);
}
}
let Some(after_eq) = sig_idx else { return };
let body_segment = &window[after_eq..];
let intervening_close = body_segment
.iter()
.any(|&t| t == TOK_LT_SLASH || t == TOK_GT);
if !intervening_close {
a.inside_parameter_body = true;
a.param_body_chars_emitted = 0;
}
}