Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/tools/test_tool_output_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
from tools import tool_output_limits as tol


@pytest.fixture(autouse=True)
def _clear_limits_cache():
"""Ensure lru_cache is empty before every test so patches take effect."""
tol._invalidate_tool_output_limits_cache()
yield
tol._invalidate_tool_output_limits_cache()


class TestDefaults:
def test_defaults_match_previous_hardcoded_values(self):
assert tol.DEFAULT_MAX_BYTES == 50_000
Expand Down Expand Up @@ -150,3 +158,22 @@ def test_pagination_default_when_config_missing(self):
# Clamped to default MAX_LINES (2000).
assert limit == tol.DEFAULT_MAX_LINES
assert offset == 10


class TestCaching:
"""get_tool_output_limits must read config only once thanks to lru_cache."""

def test_get_tool_output_limits_cached(self):
"""Calling get_tool_output_limits twice should invoke load_config once."""
with patch("hermes_cli.config.load_config", return_value={}) as mock_load:
tol.get_tool_output_limits()
tol.get_tool_output_limits()
mock_load.assert_called_once()

def test_invalidate_tool_output_limits_cache(self):
"""After cache invalidation, the next call re-invokes load_config."""
with patch("hermes_cli.config.load_config", return_value={}) as mock_load:
tol.get_tool_output_limits()
tol._invalidate_tool_output_limits_cache()
tol.get_tool_output_limits()
assert mock_load.call_count == 2
7 changes: 7 additions & 0 deletions tools/tool_output_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from __future__ import annotations

import functools
from typing import Any, Dict

# Hardcoded defaults — these match the pre-existing values, so adding
Expand All @@ -52,6 +53,7 @@ def _coerce_positive_int(value: Any, default: int) -> int:
return iv


@functools.lru_cache(maxsize=1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate limits cache when config changes

With this zero-argument lru_cache, the first terminal/read_file call in a long-lived CLI, gateway, or dashboard-backed process freezes tool_output limits until process restart. hermes_cli.config.load_config() already caches by config-path mtime/size, so it would pick up edits or saves through /api/config/save_config; wrapping it here prevents that mtime check from running again, making documented config changes to tool_output.max_bytes/max_lines ineffective for the rest of the session.

Useful? React with 👍 / 👎.

def get_tool_output_limits() -> Dict[str, int]:
"""Return resolved tool-output limits, reading ``tool_output`` from config.

Expand All @@ -77,6 +79,11 @@ def get_tool_output_limits() -> Dict[str, int]:
}


def _invalidate_tool_output_limits_cache() -> None:
"""Clear the lru_cache on get_tool_output_limits. Intended for tests."""
get_tool_output_limits.cache_clear()


def get_max_bytes() -> int:
"""Shortcut for terminal-tool callers that only need the byte cap."""
return get_tool_output_limits()["max_bytes"]
Expand Down
Loading