use super::ToolError;
use serde_json::Value;
const DEFAULT_LIMIT: usize = 256 * 1024;
const WRITE_LIMIT: usize = 1024 * 1024;
const QUERY_LIMIT: usize = 64 * 1024;
const BROWSER_FILE_INPUT_WIRE_LIMIT: usize = 1_405_000;
pub fn tool_input_limit(execution_name: &str) -> usize {
match execution_name {
"Write" | "Edit" | "NotebookEdit" => WRITE_LIMIT,
"Bash" | "exec_command" | "Read" | "Grep" | "Glob" => QUERY_LIMIT,
_ => DEFAULT_LIMIT,
}
}
pub fn check_raw_tool_input(execution_name: &str, raw: &str) -> Result<(), ToolError> {
if execution_name == "browser"
&& raw.len() > DEFAULT_LIMIT
&& raw.len() <= BROWSER_FILE_INPUT_WIRE_LIMIT
{
if let Ok(args) = serde_json::from_str::<Value>(raw) {
return check_size(
execution_name,
raw.len(),
input_limit(execution_name, &args),
);
}
}
check_size(execution_name, raw.len(), tool_input_limit(execution_name))
}
pub fn check_parsed_tool_input(execution_name: &str, args: &Value) -> Result<(), ToolError> {
let size = serde_json::to_vec(args)
.map_err(|_| ToolError::InvalidArguments("Could not measure tool arguments".into()))?
.len();
check_size(execution_name, size, input_limit(execution_name, args))
}
fn input_limit(execution_name: &str, args: &Value) -> usize {
if execution_name == "browser"
&& args.get("action").and_then(Value::as_str) == Some("set_file_input")
{
BROWSER_FILE_INPUT_WIRE_LIMIT
} else {
tool_input_limit(execution_name)
}
}
fn check_size(execution_name: &str, size: usize, limit: usize) -> Result<(), ToolError> {
if size <= limit {
return Ok(());
}
let advice = match execution_name {
"Write" | "Edit" | "NotebookEdit" => "Split the content into smaller writes or patches.",
"Bash" | "exec_command" | "js_repl" => {
"Move large inline content into a file and shorten the command."
}
_ => "Split the request or pass a file reference instead of inline content.",
};
Err(ToolError::InvalidArguments(format!(
"Tool {execution_name} arguments contain {size} bytes; the limit is {limit} bytes. {advice}"
)))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn exact_boundary_is_allowed_but_one_more_byte_is_rejected() {
let raw = "x".repeat(tool_input_limit("Bash"));
assert!(check_raw_tool_input("Bash", &raw).is_ok());
let error = check_raw_tool_input("Bash", &(raw + "x")).unwrap_err();
assert!(error.to_string().contains("65537 bytes"));
assert!(check_raw_tool_input("exec_command", &"x".repeat(QUERY_LIMIT)).is_ok());
assert!(check_raw_tool_input("exec_command", &"x".repeat(QUERY_LIMIT + 1)).is_err());
}
#[test]
fn utf8_bytes_and_preparsed_values_are_both_bounded() {
assert!(check_raw_tool_input("Read", &"字".repeat(30_000)).is_err());
assert!(check_parsed_tool_input("Read", &json!({"path":"x".repeat(QUERY_LIMIT)})).is_err());
assert!(check_parsed_tool_input("Write", &json!({"content":"字".repeat(30_000)})).is_ok());
}
#[test]
fn custom_names_do_not_inherit_builtin_limits() {
assert_eq!(tool_input_limit("Write"), WRITE_LIMIT);
assert_eq!(tool_input_limit("vendor::Write"), DEFAULT_LIMIT);
assert_eq!(tool_input_limit("BashOutput"), DEFAULT_LIMIT);
assert_eq!(tool_input_limit("vendor::browser"), DEFAULT_LIMIT);
}
#[test]
fn only_browser_file_input_gets_the_one_mebibyte_wire_budget() {
let data_base64 = format!("{}==", "A".repeat((1024_usize * 1024).div_ceil(3) * 4 - 2));
let args = json!({
"action": "set_file_input",
"selector": "#upload",
"filename": "sample.bin",
"mime_type": "application/octet-stream",
"data_base64": data_base64,
"expected_epoch": 42,
});
let raw = args.to_string();
assert!(raw.len() > DEFAULT_LIMIT);
assert!(check_raw_tool_input("browser", &raw).is_ok());
assert!(check_parsed_tool_input("browser", &args).is_ok());
let ordinary = json!({"action":"navigate", "url":"x".repeat(DEFAULT_LIMIT)});
assert!(check_raw_tool_input("browser", &ordinary.to_string()).is_err());
assert!(check_parsed_tool_input("browser", &ordinary).is_err());
let malformed = format!(
"{{\"action\":\"set_file_input\",\"data_base64\":\"{}",
"A".repeat(DEFAULT_LIMIT)
);
assert!(check_raw_tool_input("browser", &malformed).is_err());
assert!(check_raw_tool_input("vendor::browser", &raw).is_err());
assert!(check_parsed_tool_input("vendor::browser", &args).is_err());
let oversized = json!({"action":"set_file_input", "data_base64":"A".repeat(BROWSER_FILE_INPUT_WIRE_LIMIT)});
assert!(check_raw_tool_input("browser", &oversized.to_string()).is_err());
assert!(check_parsed_tool_input("browser", &oversized).is_err());
}
}