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:
@@ -17,6 +17,23 @@ _RESET = "\033[0m"
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
# Skin-aware helpers (lazy import to avoid circular deps)
|
||||||
@@ -94,8 +111,14 @@ def _oneline(text: str) -> str:
|
|||||||
return " ".join(text.split())
|
return " ".join(text.split())
|
||||||
|
|
||||||
|
|
||||||
def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) -> str | None:
|
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."""
|
"""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:
|
if not args:
|
||||||
return None
|
return None
|
||||||
primary_args = {
|
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))
|
preview = _oneline(str(value))
|
||||||
if not preview:
|
if not preview:
|
||||||
return None
|
return None
|
||||||
if len(preview) > max_len:
|
if max_len > 0 and len(preview) > max_len:
|
||||||
preview = preview[:max_len - 3] + "..."
|
preview = preview[:max_len - 3] + "..."
|
||||||
return preview
|
return preview
|
||||||
|
|
||||||
@@ -484,10 +507,14 @@ def get_cute_tool_message(
|
|||||||
|
|
||||||
def _trunc(s, n=40):
|
def _trunc(s, n=40):
|
||||||
s = str(s)
|
s = str(s)
|
||||||
|
if _tool_preview_max_len == 0:
|
||||||
|
return s # no limit
|
||||||
return (s[:n-3] + "...") if len(s) > n else s
|
return (s[:n-3] + "...") if len(s) > n else s
|
||||||
|
|
||||||
def _path(p, n=35):
|
def _path(p, n=35):
|
||||||
p = str(p)
|
p = str(p)
|
||||||
|
if _tool_preview_max_len == 0:
|
||||||
|
return p # no limit
|
||||||
return ("..." + p[-(n-3):]) if len(p) > n else p
|
return ("..." + p[-(n-3):]) if len(p) > n else p
|
||||||
|
|
||||||
def _wrap(line: str) -> str:
|
def _wrap(line: str) -> str:
|
||||||
|
|||||||
14
cli.py
14
cli.py
@@ -449,6 +449,14 @@ try:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass # Skin engine is optional — default skin used if unavailable
|
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
|
# Neuter AsyncHttpxClientWrapper.__del__ before any AsyncOpenAI clients are
|
||||||
# created. The SDK's __del__ schedules aclose() on asyncio.get_running_loop()
|
# 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
|
# 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
|
from agent.display import get_tool_emoji
|
||||||
emoji = get_tool_emoji(function_name)
|
emoji = get_tool_emoji(function_name)
|
||||||
label = preview or function_name
|
label = preview or function_name
|
||||||
if len(label) > 50:
|
from agent.display import get_tool_preview_max_len
|
||||||
label = label[:47] + "..."
|
_pl = get_tool_preview_max_len()
|
||||||
|
if _pl > 0 and len(label) > _pl:
|
||||||
|
label = label[:_pl - 3] + "..."
|
||||||
self._spinner_text = f"{emoji} {label}"
|
self._spinner_text = f"{emoji} {label}"
|
||||||
self._invalidate()
|
self._invalidate()
|
||||||
|
|
||||||
|
|||||||
@@ -4941,6 +4941,14 @@ class GatewayRunner:
|
|||||||
from hermes_cli.tools_config import _get_platform_tools
|
from hermes_cli.tools_config import _get_platform_tools
|
||||||
enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))
|
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"
|
# Tool progress mode from config.yaml: "all", "new", "verbose", "off"
|
||||||
# Falls back to env vars for backward compatibility.
|
# Falls back to env vars for backward compatibility.
|
||||||
# YAML 1.1 parses bare `off` as boolean False — normalise before
|
# YAML 1.1 parses bare `off` as boolean False — normalise before
|
||||||
@@ -4986,9 +4994,11 @@ class GatewayRunner:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
# Truncate preview to keep messages clean
|
# Truncate preview unless config says unlimited
|
||||||
if len(preview) > 80:
|
from agent.display import get_tool_preview_max_len
|
||||||
preview = preview[:77] + "..."
|
_pl = get_tool_preview_max_len()
|
||||||
|
if _pl > 0 and len(preview) > _pl:
|
||||||
|
preview = preview[:_pl - 3] + "..."
|
||||||
msg = f"{emoji} {tool_name}: \"{preview}\""
|
msg = f"{emoji} {tool_name}: \"{preview}\""
|
||||||
else:
|
else:
|
||||||
msg = f"{emoji} {tool_name}..."
|
msg = f"{emoji} {tool_name}..."
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ DEFAULT_CONFIG = {
|
|||||||
"show_cost": False, # Show $ cost in the status bar (off by default)
|
"show_cost": False, # Show $ cost in the status bar (off by default)
|
||||||
"skin": "default",
|
"skin": "default",
|
||||||
"tool_progress_command": False, # Enable /verbose command in messaging gateway
|
"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
|
# Privacy settings
|
||||||
|
|||||||
@@ -5662,8 +5662,6 @@ class AIAgent:
|
|||||||
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
|
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
|
||||||
emoji = _get_tool_emoji(function_name)
|
emoji = _get_tool_emoji(function_name)
|
||||||
preview = _build_tool_preview(function_name, function_args) or 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 = KawaiiSpinner(f"{face} {emoji} {preview}", spinner_type='dots', print_fn=self._print_fn)
|
||||||
spinner.start()
|
spinner.start()
|
||||||
_spinner_result = None
|
_spinner_result = None
|
||||||
|
|||||||
Reference in New Issue
Block a user