tandem-core 0.4.16

Core types and helpers for the Tandem engine
Documentation
pub(super) const MIN_TOOL_CALL_LIMIT: usize = 200;

pub(super) fn tool_budget_for(tool_name: &str) -> usize {
    if env_budget_guards_disabled() {
        return usize::MAX;
    }
    let normalized = super::normalize_tool_name(tool_name);
    let env_key = match normalized.as_str() {
        "glob" => "TANDEM_TOOL_BUDGET_GLOB",
        "read" => "TANDEM_TOOL_BUDGET_READ",
        "websearch" => "TANDEM_TOOL_BUDGET_WEBSEARCH",
        "batch" => "TANDEM_TOOL_BUDGET_BATCH",
        "grep" | "search" | "codesearch" => "TANDEM_TOOL_BUDGET_SEARCH",
        _ => "TANDEM_TOOL_BUDGET_DEFAULT",
    };
    if let Some(override_budget) = parse_budget_override(env_key) {
        if override_budget == usize::MAX {
            return usize::MAX;
        }
        return override_budget.max(MIN_TOOL_CALL_LIMIT);
    }
    MIN_TOOL_CALL_LIMIT
}

pub(super) fn duplicate_signature_limit_for(_tool_name: &str) -> usize {
    if let Ok(raw) = std::env::var("TANDEM_TOOL_LOOP_DUPLICATE_SIGNATURE_LIMIT") {
        if let Ok(parsed) = raw.trim().parse::<usize>() {
            if parsed > 0 {
                return parsed.max(MIN_TOOL_CALL_LIMIT);
            }
        }
    }
    MIN_TOOL_CALL_LIMIT
}

pub(super) fn websearch_duplicate_signature_limit() -> Option<usize> {
    std::env::var("TANDEM_WEBSEARCH_DUPLICATE_SIGNATURE_LIMIT")
        .ok()
        .and_then(|raw| raw.trim().parse::<usize>().ok())
        .filter(|value| *value > 0)
        .map(|value| value.max(MIN_TOOL_CALL_LIMIT))
}

fn env_budget_guards_disabled() -> bool {
    std::env::var("TANDEM_DISABLE_TOOL_GUARD_BUDGETS")
        .ok()
        .map(|raw| {
            matches!(
                raw.trim().to_ascii_lowercase().as_str(),
                "1" | "true" | "yes" | "on"
            )
        })
        .unwrap_or(false)
}

pub(super) fn parse_budget_override(env_key: &str) -> Option<usize> {
    let raw = std::env::var(env_key).ok()?;
    let trimmed = raw.trim().to_ascii_lowercase();
    if matches!(
        trimmed.as_str(),
        "0" | "inf" | "infinite" | "unlimited" | "none"
    ) {
        return Some(usize::MAX);
    }
    trimmed
        .parse::<usize>()
        .ok()
        .and_then(|value| if value > 0 { Some(value) } else { None })
}