Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor
This commit is contained in:
@@ -47,7 +47,15 @@ def _restore_tool_and_agent_modules():
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _enable_managed_nous_tools(monkeypatch):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
"""Ensure managed_nous_tools_enabled() returns True even after module reloads.
|
||||
|
||||
The _install_fake_tools_package() helper resets and reimports tool modules,
|
||||
so a simple monkeypatch on tool_backend_helpers doesn't survive. We patch
|
||||
the *source* modules that the reimported modules will import from — both
|
||||
hermes_cli.auth and hermes_cli.models — so the function body returns True.
|
||||
"""
|
||||
monkeypatch.setattr("hermes_cli.auth.get_nous_auth_status", lambda: {"logged_in": True})
|
||||
monkeypatch.setattr("hermes_cli.models.check_nous_free_tier", lambda: False)
|
||||
|
||||
|
||||
def _install_fake_tools_package():
|
||||
|
||||
@@ -46,7 +46,10 @@ def _restore_tool_and_agent_modules():
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _enable_managed_nous_tools(monkeypatch):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
"""Patch the source modules so managed_nous_tools_enabled() returns True
|
||||
even after tool modules are dynamically reloaded."""
|
||||
monkeypatch.setattr("hermes_cli.auth.get_nous_auth_status", lambda: {"logged_in": True})
|
||||
monkeypatch.setattr("hermes_cli.models.check_nous_free_tier", lambda: False)
|
||||
|
||||
|
||||
def _install_fake_tools_package():
|
||||
|
||||
@@ -19,11 +19,10 @@ def test_resolve_managed_tool_gateway_derives_vendor_origin_from_shared_domain()
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS": "1",
|
||||
"TOOL_GATEWAY_DOMAIN": "nousresearch.com",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
), patch.object(managed_tool_gateway, "managed_nous_tools_enabled", return_value=True):
|
||||
result = resolve_managed_tool_gateway(
|
||||
"firecrawl",
|
||||
token_reader=lambda: "nous-token",
|
||||
@@ -39,11 +38,10 @@ def test_resolve_managed_tool_gateway_uses_vendor_specific_override():
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS": "1",
|
||||
"BROWSER_USE_GATEWAY_URL": "http://browser-use-gateway.localhost:3009/",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
), patch.object(managed_tool_gateway, "managed_nous_tools_enabled", return_value=True):
|
||||
result = resolve_managed_tool_gateway(
|
||||
"browser-use",
|
||||
token_reader=lambda: "nous-token",
|
||||
@@ -57,11 +55,10 @@ def test_resolve_managed_tool_gateway_is_inactive_without_nous_token():
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS": "1",
|
||||
"TOOL_GATEWAY_DOMAIN": "nousresearch.com",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
), patch.object(managed_tool_gateway, "managed_nous_tools_enabled", return_value=True):
|
||||
result = resolve_managed_tool_gateway(
|
||||
"firecrawl",
|
||||
token_reader=lambda: None,
|
||||
@@ -70,8 +67,9 @@ def test_resolve_managed_tool_gateway_is_inactive_without_nous_token():
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_resolve_managed_tool_gateway_is_disabled_without_feature_flag():
|
||||
with patch.dict(os.environ, {"TOOL_GATEWAY_DOMAIN": "nousresearch.com"}, clear=False):
|
||||
def test_resolve_managed_tool_gateway_is_disabled_without_subscription():
|
||||
with patch.dict(os.environ, {"TOOL_GATEWAY_DOMAIN": "nousresearch.com"}, clear=False), \
|
||||
patch.object(managed_tool_gateway, "managed_nous_tools_enabled", return_value=False):
|
||||
result = resolve_managed_tool_gateway(
|
||||
"firecrawl",
|
||||
token_reader=lambda: "nous-token",
|
||||
|
||||
@@ -7,7 +7,6 @@ terminal_tool_module = importlib.import_module("tools.terminal_tool")
|
||||
def _clear_terminal_env(monkeypatch):
|
||||
"""Remove terminal env vars that could affect requirements checks."""
|
||||
keys = [
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS",
|
||||
"TERMINAL_ENV",
|
||||
"TERMINAL_MODAL_MODE",
|
||||
"TERMINAL_SSH_HOST",
|
||||
@@ -19,6 +18,11 @@ def _clear_terminal_env(monkeypatch):
|
||||
]
|
||||
for key in keys:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
# Default: no Nous subscription — patch both the terminal_tool local
|
||||
# binding and tool_backend_helpers (used by resolve_modal_backend_state).
|
||||
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: False)
|
||||
import tools.tool_backend_helpers as _tbh
|
||||
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: False)
|
||||
|
||||
|
||||
def test_local_terminal_requirements(monkeypatch, caplog):
|
||||
@@ -81,7 +85,9 @@ def test_modal_backend_without_token_or_config_logs_specific_error(monkeypatch,
|
||||
|
||||
def test_modal_backend_with_managed_gateway_does_not_require_direct_creds_or_minisweagent(monkeypatch, tmp_path):
|
||||
_clear_terminal_env(monkeypatch)
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: True)
|
||||
import tools.tool_backend_helpers as _tbh
|
||||
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setenv("TERMINAL_ENV", "modal")
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
||||
@@ -98,7 +104,9 @@ def test_modal_backend_with_managed_gateway_does_not_require_direct_creds_or_min
|
||||
|
||||
def test_modal_backend_auto_mode_prefers_managed_gateway_over_direct_creds(monkeypatch, tmp_path):
|
||||
_clear_terminal_env(monkeypatch)
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: True)
|
||||
import tools.tool_backend_helpers as _tbh
|
||||
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setenv("TERMINAL_ENV", "modal")
|
||||
monkeypatch.setenv("MODAL_TOKEN_ID", "tok-id")
|
||||
monkeypatch.setenv("MODAL_TOKEN_SECRET", "tok-secret")
|
||||
@@ -147,7 +155,7 @@ def test_modal_backend_managed_mode_does_not_fall_back_to_direct(monkeypatch, ca
|
||||
|
||||
assert ok is False
|
||||
assert any(
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS is not enabled" in record.getMessage()
|
||||
"paid Nous subscription is required" in record.getMessage()
|
||||
for record in caplog.records
|
||||
)
|
||||
|
||||
@@ -165,6 +173,6 @@ def test_modal_backend_managed_mode_without_feature_flag_logs_clear_error(monkey
|
||||
|
||||
assert ok is False
|
||||
assert any(
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS is not enabled" in record.getMessage()
|
||||
"paid Nous subscription is required" in record.getMessage()
|
||||
for record in caplog.records
|
||||
)
|
||||
|
||||
@@ -28,7 +28,8 @@ class TestTerminalRequirements:
|
||||
assert {"read_file", "write_file", "patch", "search_files"}.issubset(names)
|
||||
|
||||
def test_terminal_and_execute_code_tools_resolve_for_managed_modal(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
monkeypatch.setattr("tools.tool_backend_helpers.managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
||||
monkeypatch.delenv("MODAL_TOKEN_ID", raising=False)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Unit tests for tools/tool_backend_helpers.py.
|
||||
|
||||
Tests cover:
|
||||
- managed_nous_tools_enabled() feature flag
|
||||
- managed_nous_tools_enabled() subscription-based gate
|
||||
- normalize_browser_cloud_provider() coercion
|
||||
- coerce_modal_mode() / normalize_modal_mode() validation
|
||||
- has_direct_modal_credentials() detection
|
||||
@@ -27,24 +27,51 @@ from tools.tool_backend_helpers import (
|
||||
)
|
||||
|
||||
|
||||
def _raise_import():
|
||||
raise ImportError("simulated missing module")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# managed_nous_tools_enabled
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestManagedNousToolsEnabled:
|
||||
"""Feature flag driven by HERMES_ENABLE_NOUS_MANAGED_TOOLS."""
|
||||
"""Subscription-based gate: True for paid Nous subscribers."""
|
||||
|
||||
def test_disabled_by_default(self, monkeypatch):
|
||||
monkeypatch.delenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", raising=False)
|
||||
def test_disabled_when_not_logged_in(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_nous_auth_status",
|
||||
lambda: {},
|
||||
)
|
||||
assert managed_nous_tools_enabled() is False
|
||||
|
||||
@pytest.mark.parametrize("val", ["1", "true", "True", "yes"])
|
||||
def test_enabled_when_truthy(self, monkeypatch, val):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", val)
|
||||
def test_disabled_for_free_tier(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_nous_auth_status",
|
||||
lambda: {"logged_in": True},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.models.check_nous_free_tier",
|
||||
lambda: True,
|
||||
)
|
||||
assert managed_nous_tools_enabled() is False
|
||||
|
||||
def test_enabled_for_paid_subscriber(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_nous_auth_status",
|
||||
lambda: {"logged_in": True},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.models.check_nous_free_tier",
|
||||
lambda: False,
|
||||
)
|
||||
assert managed_nous_tools_enabled() is True
|
||||
|
||||
@pytest.mark.parametrize("val", ["0", "false", "no", ""])
|
||||
def test_disabled_when_falsy(self, monkeypatch, val):
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", val)
|
||||
def test_returns_false_on_exception(self, monkeypatch):
|
||||
"""Should never crash — returns False on any exception."""
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_nous_auth_status",
|
||||
_raise_import,
|
||||
)
|
||||
assert managed_nous_tools_enabled() is False
|
||||
|
||||
|
||||
@@ -171,10 +198,10 @@ class TestResolveModalBackendState:
|
||||
@staticmethod
|
||||
def _resolve(monkeypatch, mode, *, has_direct, managed_ready, nous_enabled=False):
|
||||
"""Helper to call resolve_modal_backend_state with feature flag control."""
|
||||
if nous_enabled:
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "1")
|
||||
else:
|
||||
monkeypatch.setenv("HERMES_ENABLE_NOUS_MANAGED_TOOLS", "")
|
||||
monkeypatch.setattr(
|
||||
"tools.tool_backend_helpers.managed_nous_tools_enabled",
|
||||
lambda: nous_enabled,
|
||||
)
|
||||
return resolve_modal_backend_state(
|
||||
mode, has_direct=has_direct, managed_ready=managed_ready
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@ class TestFirecrawlClientConfig:
|
||||
tools.web_tools._firecrawl_client = None
|
||||
tools.web_tools._firecrawl_client_config = None
|
||||
for key in (
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS",
|
||||
"FIRECRAWL_API_KEY",
|
||||
"FIRECRAWL_API_URL",
|
||||
"FIRECRAWL_GATEWAY_URL",
|
||||
@@ -35,7 +34,15 @@ class TestFirecrawlClientConfig:
|
||||
"TOOL_GATEWAY_USER_TOKEN",
|
||||
):
|
||||
os.environ.pop(key, None)
|
||||
os.environ["HERMES_ENABLE_NOUS_MANAGED_TOOLS"] = "1"
|
||||
# Enable managed tools by default for these tests — patch both the
|
||||
# local web_tools import and the managed_tool_gateway import so the
|
||||
# full firecrawl client init path sees True.
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
]
|
||||
for p in self._managed_patchers:
|
||||
p.start()
|
||||
|
||||
def teardown_method(self):
|
||||
"""Reset client after each test."""
|
||||
@@ -43,7 +50,6 @@ class TestFirecrawlClientConfig:
|
||||
tools.web_tools._firecrawl_client = None
|
||||
tools.web_tools._firecrawl_client_config = None
|
||||
for key in (
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS",
|
||||
"FIRECRAWL_API_KEY",
|
||||
"FIRECRAWL_API_URL",
|
||||
"FIRECRAWL_GATEWAY_URL",
|
||||
@@ -52,6 +58,8 @@ class TestFirecrawlClientConfig:
|
||||
"TOOL_GATEWAY_USER_TOKEN",
|
||||
):
|
||||
os.environ.pop(key, None)
|
||||
for p in self._managed_patchers:
|
||||
p.stop()
|
||||
|
||||
# ── Configuration matrix ─────────────────────────────────────────
|
||||
|
||||
@@ -298,7 +306,6 @@ class TestBackendSelection:
|
||||
"""
|
||||
|
||||
_ENV_KEYS = (
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS",
|
||||
"EXA_API_KEY",
|
||||
"PARALLEL_API_KEY",
|
||||
"FIRECRAWL_API_KEY",
|
||||
@@ -311,14 +318,20 @@ class TestBackendSelection:
|
||||
)
|
||||
|
||||
def setup_method(self):
|
||||
os.environ["HERMES_ENABLE_NOUS_MANAGED_TOOLS"] = "1"
|
||||
for key in self._ENV_KEYS:
|
||||
if key != "HERMES_ENABLE_NOUS_MANAGED_TOOLS":
|
||||
os.environ.pop(key, None)
|
||||
os.environ.pop(key, None)
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
]
|
||||
for p in self._managed_patchers:
|
||||
p.start()
|
||||
|
||||
def teardown_method(self):
|
||||
for key in self._ENV_KEYS:
|
||||
os.environ.pop(key, None)
|
||||
for p in self._managed_patchers:
|
||||
p.stop()
|
||||
|
||||
# ── Config-based selection (web.backend in config.yaml) ───────────
|
||||
|
||||
@@ -523,7 +536,6 @@ class TestCheckWebApiKey:
|
||||
"""Test suite for check_web_api_key() unified availability check."""
|
||||
|
||||
_ENV_KEYS = (
|
||||
"HERMES_ENABLE_NOUS_MANAGED_TOOLS",
|
||||
"EXA_API_KEY",
|
||||
"PARALLEL_API_KEY",
|
||||
"FIRECRAWL_API_KEY",
|
||||
@@ -536,14 +548,20 @@ class TestCheckWebApiKey:
|
||||
)
|
||||
|
||||
def setup_method(self):
|
||||
os.environ["HERMES_ENABLE_NOUS_MANAGED_TOOLS"] = "1"
|
||||
for key in self._ENV_KEYS:
|
||||
if key != "HERMES_ENABLE_NOUS_MANAGED_TOOLS":
|
||||
os.environ.pop(key, None)
|
||||
os.environ.pop(key, None)
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
]
|
||||
for p in self._managed_patchers:
|
||||
p.start()
|
||||
|
||||
def teardown_method(self):
|
||||
for key in self._ENV_KEYS:
|
||||
os.environ.pop(key, None)
|
||||
for p in self._managed_patchers:
|
||||
p.stop()
|
||||
|
||||
def test_parallel_key_only(self):
|
||||
with patch.dict(os.environ, {"PARALLEL_API_KEY": "test-key"}):
|
||||
|
||||
Reference in New Issue
Block a user