feat(display): configurable tool preview length -- show full paths by default (#3841)

Tool call previews (paths, commands, queries) were hardcoded to truncate
at 35-40 chars across CLI spinners, completion lines, and gateway progress
messages. Users could not see full file paths in tool output.

New config option: display.tool_preview_length (default 0 = no limit).
Set a positive number to truncate at that length.

Changes:
- display.py: module-level _tool_preview_max_len with getter/setter;
  build_tool_preview() and get_cute_tool_message() _trunc/_path respect it
- cli.py: reads config at startup, spinner widget respects config
- gateway/run.py: reads config per-message, progress callback respects config
- run_agent.py: removed redundant 30-char quiet-mode spinner truncation
- config.py: added display.tool_preview_length to DEFAULT_CONFIG

Reported by kriskaminski
This commit is contained in:
Teknium
2026-03-29 18:02:42 -07:00
committed by GitHub
parent 59f2b228f7
commit e314833c9d
5 changed files with 56 additions and 10 deletions

View File

@@ -17,6 +17,23 @@ _RESET = "\033[0m"
logger = logging.getLogger(__name__)
# =========================================================================
# Configurable tool preview length (0 = no limit)
# Set once at startup by CLI or gateway from display.tool_preview_length config.
# =========================================================================
_tool_preview_max_len: int = 0 # 0 = unlimited
def set_tool_preview_max_len(n: int) -> None:
"""Set the global max length for tool call previews. 0 = no limit."""
global _tool_preview_max_len
_tool_preview_max_len = max(int(n), 0) if n else 0
def get_tool_preview_max_len() -> int:
"""Return the configured max preview length (0 = unlimited)."""
return _tool_preview_max_len
# =========================================================================
# Skin-aware helpers (lazy import to avoid circular deps)
@@ -94,8 +111,14 @@ def _oneline(text: str) -> str:
return " ".join(text.split())
def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) -> str | None:
"""Build a short preview of a tool call's primary argument for display."""
def build_tool_preview(tool_name: str, args: dict, max_len: int | None = None) -> str | None:
"""Build a short preview of a tool call's primary argument for display.
*max_len* controls truncation. ``None`` (default) defers to the global
``_tool_preview_max_len`` set via config; ``0`` means unlimited.
"""
if max_len is None:
max_len = _tool_preview_max_len
if not args:
return None
primary_args = {
@@ -190,7 +213,7 @@ def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) -> str | N
preview = _oneline(str(value))
if not preview:
return None
if len(preview) > max_len:
if max_len > 0 and len(preview) > max_len:
preview = preview[:max_len - 3] + "..."
return preview
@@ -484,10 +507,14 @@ def get_cute_tool_message(
def _trunc(s, n=40):
s = str(s)
if _tool_preview_max_len == 0:
return s # no limit
return (s[:n-3] + "...") if len(s) > n else s
def _path(p, n=35):
p = str(p)
if _tool_preview_max_len == 0:
return p # no limit
return ("..." + p[-(n-3):]) if len(p) > n else p
def _wrap(line: str) -> str:

14
cli.py
View File

@@ -449,6 +449,14 @@ try:
except Exception:
pass # Skin engine is optional — default skin used if unavailable
# Initialize tool preview length from config
try:
from agent.display import set_tool_preview_max_len
_tpl = CLI_CONFIG.get("display", {}).get("tool_preview_length", 0)
set_tool_preview_max_len(int(_tpl) if _tpl else 0)
except Exception:
pass
# Neuter AsyncHttpxClientWrapper.__del__ before any AsyncOpenAI clients are
# created. The SDK's __del__ schedules aclose() on asyncio.get_running_loop()
# which, during CLI idle time, finds prompt_toolkit's event loop and tries to
@@ -4782,8 +4790,10 @@ class HermesCLI:
from agent.display import get_tool_emoji
emoji = get_tool_emoji(function_name)
label = preview or function_name
if len(label) > 50:
label = label[:47] + "..."
from agent.display import get_tool_preview_max_len
_pl = get_tool_preview_max_len()
if _pl > 0 and len(label) > _pl:
label = label[:_pl - 3] + "..."
self._spinner_text = f"{emoji} {label}"
self._invalidate()

View File

@@ -4941,6 +4941,14 @@ class GatewayRunner:
from hermes_cli.tools_config import _get_platform_tools
enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))
# Apply tool preview length config (0 = no limit)
try:
from agent.display import set_tool_preview_max_len
_tpl = user_config.get("display", {}).get("tool_preview_length", 0)
set_tool_preview_max_len(int(_tpl) if _tpl else 0)
except Exception:
pass
# Tool progress mode from config.yaml: "all", "new", "verbose", "off"
# Falls back to env vars for backward compatibility.
# YAML 1.1 parses bare `off` as boolean False — normalise before
@@ -4986,9 +4994,11 @@ class GatewayRunner:
return
if preview:
# Truncate preview to keep messages clean
if len(preview) > 80:
preview = preview[:77] + "..."
# Truncate preview unless config says unlimited
from agent.display import get_tool_preview_max_len
_pl = get_tool_preview_max_len()
if _pl > 0 and len(preview) > _pl:
preview = preview[:_pl - 3] + "..."
msg = f"{emoji} {tool_name}: \"{preview}\""
else:
msg = f"{emoji} {tool_name}..."

View File

@@ -285,6 +285,7 @@ DEFAULT_CONFIG = {
"show_cost": False, # Show $ cost in the status bar (off by default)
"skin": "default",
"tool_progress_command": False, # Enable /verbose command in messaging gateway
"tool_preview_length": 0, # Max chars for tool call previews (0 = no limit, show full paths/commands)
},
# Privacy settings

View File

@@ -5662,8 +5662,6 @@ class AIAgent:
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
emoji = _get_tool_emoji(function_name)
preview = _build_tool_preview(function_name, function_args) or function_name
if len(preview) > 30:
preview = preview[:27] + "..."
spinner = KawaiiSpinner(f"{face} {emoji} {preview}", spinner_type='dots', print_fn=self._print_fn)
spinner.start()
_spinner_result = None