use crate::{config::CodeModeToolSettings, providers::ProviderToolResult, tools::ToolCapability};
use std::collections::HashMap;
const MECHANICAL_TOOLS: &[&str] = &[
"read",
"grep",
"find",
"list_files",
"ast_grep",
"write",
"hash_edit",
];
const CALL_THRESHOLD: usize = 6;
const REPEATED_TOOL_THRESHOLD: usize = 4;
const RESULT_BYTES_THRESHOLD: usize = 64 * 1024;
pub(crate) const NUDGE: &str = "Several direct tool calls/results have accumulated. If remaining work is mechanical and needs no reasoning between steps, consider code_mode to batch it and return only relevant results.";
#[derive(Debug, Default)]
pub(crate) struct DirectToolUsage {
calls: usize,
counts: HashMap<String, usize>,
bytes: usize,
nudged: bool,
used_code_mode: bool,
}
impl DirectToolUsage {
pub(crate) fn observe(
&mut self,
settings: &CodeModeToolSettings,
result: &ProviderToolResult,
) -> Option<&'static str> {
if result.tool_name == super::TOOL_NAME {
self.used_code_mode = true;
}
if !settings.enabled || !settings.nudges_enabled || self.nudged || self.used_code_mode {
return None;
}
let name = ToolCapability::from_dispatch_name(&result.tool_name)
.map(ToolCapability::canonical_name)
.unwrap_or(&result.tool_name);
self.bytes = self.bytes.saturating_add(result.output.len());
let mut repeated = 0;
if MECHANICAL_TOOLS.contains(&name) || name.starts_with("mcp__") {
self.calls = self.calls.saturating_add(1);
let count = self.counts.entry(name.to_owned()).or_default();
*count += 1;
repeated = *count;
}
self.nudged = self.calls >= CALL_THRESHOLD
|| repeated >= REPEATED_TOOL_THRESHOLD
|| self.bytes >= RESULT_BYTES_THRESHOLD;
self.nudged.then_some(NUDGE)
}
}