feat: ungate Tool Gateway — subscription-based access with per-tool opt-in

Replace the HERMES_ENABLE_NOUS_MANAGED_TOOLS env-var feature flag with
subscription-based detection. The Tool Gateway is now available to any
paid Nous subscriber without needing a hidden env var.

Core changes:
- managed_nous_tools_enabled() checks get_nous_auth_status() +
  check_nous_free_tier() instead of an env var
- New use_gateway config flag per tool section (web, tts, browser,
  image_gen) records explicit user opt-in and overrides direct API
  keys at runtime
- New prefers_gateway(section) shared helper in tool_backend_helpers.py
  used by all 4 tool runtimes (web, tts, image gen, browser)

UX flow:
- hermes model: after Nous login/model selection, shows a curses
  prompt listing all gateway-eligible tools with current status.
  User chooses to enable all, enable only unconfigured tools, or skip.
  Defaults to Enable for new users, Skip when direct keys exist.
- hermes tools: provider selection now manages use_gateway flag —
  selecting Nous Subscription sets it, selecting any other provider
  clears it
- hermes status: renamed section to Nous Tool Gateway, added
  free-tier upgrade nudge for logged-in free users
- curses_radiolist: new description parameter for multi-line context
  that survives the screen clear

Runtime behavior:
- Each tool runtime (web_tools, tts_tool, image_generation_tool,
  browser_use) checks prefers_gateway() before falling back to
  direct env-var credentials
- get_nous_subscription_features() respects use_gateway flags,
  suppressing direct credential detection when the user opted in

Removed:
- HERMES_ENABLE_NOUS_MANAGED_TOOLS env var and all references
- apply_nous_provider_defaults() silent TTS auto-set
- get_nous_subscription_explainer_lines() static text
- Override env var warnings (use_gateway handles this properly now)
This commit is contained in:
emozilla 2026-04-16 01:59:51 -04:00 committed by Teknium
parent 25c7b1baa7
commit f188ac74f0
26 changed files with 544 additions and 187 deletions

View file

@ -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"}):