85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""Configurable tool-output truncation limits (``tool_output`` in config.yaml).
|
|
|
|
Centralises the caps previously hardcoded in ``terminal_tool`` (``max_bytes``)
|
|
and ``file_operations`` (``max_lines`` / ``max_line_length``). Defaults equal
|
|
the old constants, so behaviour is unchanged when the section is absent, and
|
|
the reader never raises — any config error falls back to the defaults.
|
|
Ported from anomalyco/opencode PR #23770. Example::
|
|
|
|
tool_output:
|
|
max_bytes: 100000 # terminal output cap (chars)
|
|
max_lines: 5000 # read_file pagination + truncation cap
|
|
max_line_length: 2000 # per-line cap before '... [truncated]'
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict
|
|
|
|
DEFAULT_MAX_BYTES = 50_000 # terminal_tool.MAX_OUTPUT_CHARS
|
|
DEFAULT_MAX_LINES = 2000 # file_operations.MAX_LINES
|
|
DEFAULT_MAX_LINE_LENGTH = 2000 # file_operations.MAX_LINE_LENGTH
|
|
|
|
# Process-lifetime cache: avoids re-reading config.yaml on every tool call.
|
|
_cached_limits: dict | None = None
|
|
|
|
|
|
def _coerce_int(value: Any, default: int, minimum: int) -> int:
|
|
"""Return ``value`` as an int >= ``minimum``, or ``default`` on any issue."""
|
|
try:
|
|
iv = int(value)
|
|
except (TypeError, ValueError):
|
|
return default
|
|
return default if iv < minimum else iv
|
|
|
|
|
|
def _coerce_positive_int(value: Any, default: int) -> int:
|
|
"""Return ``value`` as a positive int, or ``default`` on any issue."""
|
|
return _coerce_int(value, default, 1)
|
|
|
|
|
|
def get_tool_output_limits() -> Dict[str, int]:
|
|
"""Return resolved limits ``{max_bytes, max_lines, max_line_length}``; never raises.
|
|
|
|
Cached for the process lifetime — ``_reset_tool_output_limits_cache()``
|
|
forces a fresh read after config changes.
|
|
"""
|
|
global _cached_limits
|
|
if _cached_limits is not None:
|
|
return _cached_limits
|
|
try:
|
|
from hermes_cli.config import load_config
|
|
cfg = load_config() or {}
|
|
section = cfg.get("tool_output") if isinstance(cfg, dict) else None
|
|
if not isinstance(section, dict):
|
|
section = {}
|
|
except Exception:
|
|
section = {}
|
|
|
|
_cached_limits = {
|
|
"max_bytes": _coerce_positive_int(section.get("max_bytes"), DEFAULT_MAX_BYTES),
|
|
"max_lines": _coerce_positive_int(section.get("max_lines"), DEFAULT_MAX_LINES),
|
|
"max_line_length": _coerce_positive_int(
|
|
section.get("max_line_length"), DEFAULT_MAX_LINE_LENGTH
|
|
),
|
|
}
|
|
return _cached_limits
|
|
|
|
|
|
def _reset_tool_output_limits_cache() -> None:
|
|
"""Reset the cached limits — for tests or after config hot-reload."""
|
|
global _cached_limits
|
|
_cached_limits = None
|
|
|
|
|
|
def get_max_bytes() -> int:
|
|
return get_tool_output_limits()["max_bytes"]
|
|
|
|
|
|
def get_max_lines() -> int:
|
|
return get_tool_output_limits()["max_lines"]
|
|
|
|
|
|
def get_max_line_length() -> int:
|
|
return get_tool_output_limits()["max_line_length"]
|