fix(tool_hints): respect max_length for plain (non-path/non-command) tool values

Plain tool values (grep patterns, web_search/x_search queries, find_files
globs) were never truncated by format_tool_hints(), so long arguments
overflowed tool_hint_max_length and were pushed to chat/UI verbatim.

Add a hard-truncation fallback for the plain branch, mirroring the existing
truncation used by abbreviate_path / _abbreviate_command. This completes the
same class of fix started in 99209a80 for is_path tools.

Adds 4 regression tests to tests/agent/test_tool_hint.py.
This commit is contained in:
LostInTwilight
2026-09-03 17:18:21 +08:00
committed by Xubin Ren
parent 816a999cac
commit eddfa0dd6b
2 changed files with 41 additions and 0 deletions
+4
View File
@@ -104,6 +104,10 @@ def _fmt_known(tc: ToolCallRequest, fmt: ToolFormat, max_length: int = 40) -> st
val = abbreviate_path(val, max_len=max_length)
elif fmt[3]: # is_command
val = _abbreviate_command(val, max_len=max_length)
elif len(val) > max_length:
# Plain values (grep patterns, search queries, ...) have no path or
# command structure to fold, so fall back to a hard truncation.
val = val[:max_length - 1] + "\u2026"
return fmt[1].format(val)
+37
View File
@@ -303,6 +303,43 @@ class TestToolHintMaxLength:
long = _hint([_tc("list_dir", {"path": long_path})], max_length=120)
assert len(long) > len(short)
def test_plain_value_tools_respect_max_length(self):
"""Plain-value tools must truncate like the is_path/is_command branches.
grep, web_search, x_search and find_files carry no path or command
structure to fold, so their raw value used to reach the progress hint
untruncated: a 400-char search query produced a 400-char hint.
"""
for name, key in (
("grep", "pattern"),
("web_search", "query"),
("x_search", "query"),
("find_files", "query"),
):
short = _hint([_tc(name, {key: "x" * 400})], max_length=40)
long = _hint([_tc(name, {key: "x" * 400})], max_length=120)
assert len(long) > len(short), name
# Longest template is 'search X "{}"' — 12 chars of overhead.
assert len(short) <= 40 + 12, name
assert "\u2026" in short, name
def test_plain_value_short_value_untouched(self):
"""Values inside the budget must not gain an ellipsis."""
result = _hint([_tc("grep", {"pattern": "TODO|FIXME"})], max_length=40)
assert result == 'grep "TODO|FIXME"'
def test_plain_value_exactly_at_max_length_untouched(self):
"""A value exactly at max_length already fits."""
query = "a" * 40
result = _hint([_tc("web_search", {"query": query})], max_length=40)
assert result == f'search "{query}"'
assert "\u2026" not in result
def test_plain_value_one_over_max_length_truncates(self):
"""One character over the budget truncates instead of overflowing."""
result = _hint([_tc("grep", {"pattern": "a" * 41})], max_length=40)
assert result == 'grep "' + "a" * 39 + "\u2026" + '"'
class TestToolHintMalformedCalls:
"""Malformed tool calls must not crash hint formatting (see HKUDS/nanobot)."""