mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
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)
151 lines
6.7 KiB
Python
151 lines
6.7 KiB
Python
"""Tests for Nous subscription feature detection."""
|
|
|
|
from hermes_cli import nous_subscription as ns
|
|
|
|
|
|
def test_get_nous_subscription_features_recognizes_direct_exa_backend(monkeypatch):
|
|
env = {"EXA_API_KEY": "exa-test"}
|
|
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: False)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "web")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: False)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
|
|
|
features = ns.get_nous_subscription_features({"web": {"backend": "exa"}})
|
|
|
|
assert features.web.available is True
|
|
assert features.web.active is True
|
|
assert features.web.managed_by_nous is False
|
|
assert features.web.direct_override is True
|
|
assert features.web.current_provider == "exa"
|
|
|
|
|
|
def test_get_nous_subscription_features_prefers_managed_modal_in_auto_mode(monkeypatch):
|
|
monkeypatch.setattr("tools.tool_backend_helpers.managed_nous_tools_enabled", lambda: True)
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: "")
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "terminal")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: False)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: True)
|
|
monkeypatch.setattr(ns, "is_managed_tool_gateway_ready", lambda vendor: vendor == "modal")
|
|
|
|
features = ns.get_nous_subscription_features(
|
|
{"terminal": {"backend": "modal", "modal_mode": "auto"}}
|
|
)
|
|
|
|
assert features.modal.available is True
|
|
assert features.modal.active is True
|
|
assert features.modal.managed_by_nous is True
|
|
assert features.modal.direct_override is False
|
|
|
|
|
|
def test_get_nous_subscription_features_marks_browser_use_as_managed_when_gateway_ready(monkeypatch):
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: "")
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: True)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
|
monkeypatch.setattr(
|
|
ns,
|
|
"is_managed_tool_gateway_ready",
|
|
lambda vendor: vendor == "browser-use",
|
|
)
|
|
|
|
features = ns.get_nous_subscription_features(
|
|
{"browser": {"cloud_provider": "browser-use"}}
|
|
)
|
|
|
|
assert features.browser.available is True
|
|
assert features.browser.active is True
|
|
assert features.browser.managed_by_nous is True
|
|
assert features.browser.direct_override is False
|
|
assert features.browser.current_provider == "Browser Use"
|
|
|
|
|
|
def test_get_nous_subscription_features_uses_direct_browserbase_when_no_managed_gateway(monkeypatch):
|
|
"""When direct Browserbase keys are set and no managed gateway is available,
|
|
the unconfigured fallback should pick Browserbase as a direct provider."""
|
|
env = {
|
|
"BROWSERBASE_API_KEY": "bb-key",
|
|
"BROWSERBASE_PROJECT_ID": "bb-project",
|
|
}
|
|
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: True)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
|
monkeypatch.setattr(
|
|
ns,
|
|
"is_managed_tool_gateway_ready",
|
|
lambda vendor: False, # No managed gateway available
|
|
)
|
|
|
|
features = ns.get_nous_subscription_features({})
|
|
|
|
assert features.browser.available is True
|
|
assert features.browser.active is True
|
|
assert features.browser.managed_by_nous is False
|
|
assert features.browser.direct_override is True
|
|
assert features.browser.current_provider == "Browserbase"
|
|
|
|
|
|
def test_get_nous_subscription_features_prefers_camofox_over_managed_browser_use(monkeypatch):
|
|
env = {"CAMOFOX_URL": "http://localhost:9377"}
|
|
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {"logged_in": True})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: False)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
|
monkeypatch.setattr(
|
|
ns,
|
|
"is_managed_tool_gateway_ready",
|
|
lambda vendor: vendor == "browser-use",
|
|
)
|
|
|
|
features = ns.get_nous_subscription_features(
|
|
{"browser": {"cloud_provider": "browser-use"}}
|
|
)
|
|
|
|
assert features.browser.available is True
|
|
assert features.browser.active is True
|
|
assert features.browser.managed_by_nous is False
|
|
assert features.browser.direct_override is True
|
|
assert features.browser.current_provider == "Camofox"
|
|
|
|
|
|
def test_get_nous_subscription_features_requires_agent_browser_for_browserbase(monkeypatch):
|
|
env = {
|
|
"BROWSERBASE_API_KEY": "bb-key",
|
|
"BROWSERBASE_PROJECT_ID": "bb-project",
|
|
}
|
|
|
|
monkeypatch.setattr(ns, "get_env_value", lambda name: env.get(name, ""))
|
|
monkeypatch.setattr(ns, "get_nous_auth_status", lambda: {})
|
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: False)
|
|
monkeypatch.setattr(ns, "_toolset_enabled", lambda config, key: key == "browser")
|
|
monkeypatch.setattr(ns, "_has_agent_browser", lambda: False)
|
|
monkeypatch.setattr(ns, "resolve_openai_audio_api_key", lambda: "")
|
|
monkeypatch.setattr(ns, "has_direct_modal_credentials", lambda: False)
|
|
monkeypatch.setattr(ns, "is_managed_tool_gateway_ready", lambda vendor: False)
|
|
|
|
features = ns.get_nous_subscription_features(
|
|
{"browser": {"cloud_provider": "browserbase"}}
|
|
)
|
|
|
|
assert features.browser.available is False
|
|
assert features.browser.active is False
|
|
assert features.browser.managed_by_nous is False
|
|
assert features.browser.current_provider == "Browserbase"
|