Gate tool-gateway behind an env var, so it's not in users' faces until we're ready. Even if users enable it, it'll be blocked server-side for now, until we unlock for non-admin users on tool-gateway.

This commit is contained in:
Robin Fernandes 2026-03-30 13:28:10 +09:00
parent e95965d76a
commit 1cbb1b99cc
35 changed files with 426 additions and 147 deletions

View file

@ -22,6 +22,8 @@ import tempfile
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
from tools.tool_backend_helpers import managed_nous_tools_enabled as _managed_nous_tools_enabled
_IS_WINDOWS = platform.system() == "Windows"
_ENV_VAR_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
# Env var names written to .env that aren't in OPTIONAL_ENV_VARS
@ -39,7 +41,6 @@ _EXTRA_ENV_KEYS = frozenset({
"MATTERMOST_HOME_CHANNEL", "MATTERMOST_REPLY_MODE",
"MATRIX_PASSWORD", "MATRIX_ENCRYPTION", "MATRIX_HOME_ROOM",
})
import yaml
from hermes_cli.colors import Colors, color
@ -959,6 +960,15 @@ OPTIONAL_ENV_VARS = {
},
}
if not _managed_nous_tools_enabled():
for _hidden_var in (
"FIRECRAWL_GATEWAY_URL",
"TOOL_GATEWAY_DOMAIN",
"TOOL_GATEWAY_SCHEME",
"TOOL_GATEWAY_USER_TOKEN",
):
OPTIONAL_ENV_VARS.pop(_hidden_var, None)
def get_missing_env_vars(required_only: bool = False) -> List[Dict[str, Any]]:
"""

View file

@ -11,6 +11,7 @@ from hermes_cli.config import get_env_value, load_config
from tools.managed_tool_gateway import is_managed_tool_gateway_ready
from tools.tool_backend_helpers import (
has_direct_modal_credentials,
managed_nous_tools_enabled,
normalize_browser_cloud_provider,
normalize_modal_mode,
resolve_openai_audio_api_key,
@ -156,6 +157,7 @@ def get_nous_subscription_features(
except Exception:
nous_status = {}
managed_tools_flag = managed_nous_tools_enabled()
nous_auth_present = bool(nous_status.get("logged_in"))
subscribed = provider_is_nous or nous_auth_present
@ -193,11 +195,11 @@ def get_nous_subscription_features(
direct_browser_use = bool(get_env_value("BROWSER_USE_API_KEY"))
direct_modal = has_direct_modal_credentials()
managed_web_available = nous_auth_present and is_managed_tool_gateway_ready("firecrawl")
managed_image_available = nous_auth_present and is_managed_tool_gateway_ready("fal-queue")
managed_tts_available = nous_auth_present and is_managed_tool_gateway_ready("openai-audio")
managed_browser_available = nous_auth_present and is_managed_tool_gateway_ready("browserbase")
managed_modal_available = nous_auth_present and is_managed_tool_gateway_ready("modal")
managed_web_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("firecrawl")
managed_image_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("fal-queue")
managed_tts_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("openai-audio")
managed_browser_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("browserbase")
managed_modal_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("modal")
web_managed = web_backend == "firecrawl" and managed_web_available and not direct_firecrawl
web_active = bool(
@ -355,6 +357,9 @@ def get_nous_subscription_features(
def get_nous_subscription_explainer_lines() -> list[str]:
if not managed_nous_tools_enabled():
return []
return [
"Nous subscription enables managed web tools, image generation, OpenAI TTS, and browser automation by default.",
"Those managed tools bill to your Nous subscription. Modal execution is optional and can bill to your subscription too.",
@ -364,6 +369,9 @@ def get_nous_subscription_explainer_lines() -> list[str]:
def apply_nous_provider_defaults(config: Dict[str, object]) -> set[str]:
"""Apply provider-level Nous defaults shared by `hermes setup` and `hermes model`."""
if not managed_nous_tools_enabled():
return set()
features = get_nous_subscription_features(config)
if not features.provider_is_nous:
return set()
@ -386,6 +394,9 @@ def apply_nous_managed_defaults(
*,
enabled_toolsets: Optional[Iterable[str]] = None,
) -> set[str]:
if not managed_nous_tools_enabled():
return set()
features = get_nous_subscription_features(config)
if not features.provider_is_nous:
return set()

View file

@ -38,6 +38,8 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set
from utils import env_var_enabled
try:
import yaml
except ImportError: # pragma: no cover yaml is optional at import time
@ -65,7 +67,7 @@ _NS_PARENT = "hermes_plugins"
def _env_enabled(name: str) -> bool:
"""Return True when an env var is set to a truthy opt-in value."""
return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"}
return env_var_enabled(name)
# ---------------------------------------------------------------------------

View file

@ -23,6 +23,7 @@ from hermes_cli.nous_subscription import (
get_nous_subscription_explainer_lines,
get_nous_subscription_features,
)
from tools.tool_backend_helpers import managed_nous_tools_enabled
logger = logging.getLogger(__name__)
@ -59,9 +60,13 @@ def _set_default_model(config: Dict[str, Any], model_name: str) -> None:
def _print_nous_subscription_guidance() -> None:
lines = get_nous_subscription_explainer_lines()
if not lines:
return
print()
print_header("Nous Subscription Tools")
for line in get_nous_subscription_explainer_lines():
for line in lines:
print_info(line)
@ -663,7 +668,7 @@ def _print_setup_summary(config: dict, hermes_home):
tool_status.append(("Modal Execution (direct Modal)", True, None))
else:
tool_status.append(("Modal Execution", False, "run 'hermes setup terminal'"))
elif subscription_features.nous_auth_present:
elif managed_nous_tools_enabled() and subscription_features.nous_auth_present:
tool_status.append(("Modal Execution (optional via Nous subscription)", True, None))
# Tinker + WandB (RL training)
@ -1912,7 +1917,7 @@ def _setup_tts_provider(config: dict):
choices = []
providers = []
if subscription_features.nous_auth_present:
if managed_nous_tools_enabled() and subscription_features.nous_auth_present:
choices.append("Nous Subscription (managed OpenAI TTS, billed to your subscription)")
providers.append("nous-openai")
choices.extend(
@ -2137,6 +2142,8 @@ def setup_terminal_backend(config: dict):
from tools.tool_backend_helpers import normalize_modal_mode
managed_modal_available = bool(
managed_nous_tools_enabled()
and
get_nous_subscription_features(config).nous_auth_present
and is_managed_tool_gateway_ready("modal")
)

View file

@ -18,6 +18,7 @@ from hermes_cli.models import provider_label
from hermes_cli.nous_subscription import get_nous_subscription_features
from hermes_cli.runtime_provider import resolve_requested_provider
from hermes_constants import OPENROUTER_MODELS_URL
from tools.tool_backend_helpers import managed_nous_tools_enabled
def check_mark(ok: bool) -> str:
if ok:
@ -190,26 +191,27 @@ def show_status(args):
# =========================================================================
# Nous Subscription Features
# =========================================================================
features = get_nous_subscription_features(config)
print()
print(color("◆ Nous Subscription Features", Colors.CYAN, Colors.BOLD))
if not features.nous_auth_present:
print(" Nous Portal ✗ not logged in")
else:
print(" Nous Portal ✓ managed tools available")
for feature in features.items():
if feature.managed_by_nous:
state = "active via Nous subscription"
elif feature.active:
current = feature.current_provider or "configured provider"
state = f"active via {current}"
elif feature.included_by_default and features.nous_auth_present:
state = "included by subscription, not currently selected"
elif feature.key == "modal" and features.nous_auth_present:
state = "available via subscription (optional)"
if managed_nous_tools_enabled():
features = get_nous_subscription_features(config)
print()
print(color("◆ Nous Subscription Features", Colors.CYAN, Colors.BOLD))
if not features.nous_auth_present:
print(" Nous Portal ✗ not logged in")
else:
state = "not configured"
print(f" {feature.label:<15} {check_mark(feature.available or feature.active or feature.managed_by_nous)} {state}")
print(" Nous Portal ✓ managed tools available")
for feature in features.items():
if feature.managed_by_nous:
state = "active via Nous subscription"
elif feature.active:
current = feature.current_provider or "configured provider"
state = f"active via {current}"
elif feature.included_by_default and features.nous_auth_present:
state = "included by subscription, not currently selected"
elif feature.key == "modal" and features.nous_auth_present:
state = "available via subscription (optional)"
else:
state = "not configured"
print(f" {feature.label:<15} {check_mark(feature.available or feature.active or feature.managed_by_nous)} {state}")
# =========================================================================
# API-Key Providers

View file

@ -22,6 +22,7 @@ from hermes_cli.nous_subscription import (
apply_nous_managed_defaults,
get_nous_subscription_features,
)
from tools.tool_backend_helpers import managed_nous_tools_enabled
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
@ -737,6 +738,8 @@ def _visible_providers(cat: dict, config: dict) -> list[dict]:
features = get_nous_subscription_features(config)
visible = []
for provider in cat.get("providers", []):
if provider.get("managed_nous_feature") and not managed_nous_tools_enabled():
continue
if provider.get("requires_nous_auth") and not features.nous_auth_present:
continue
visible.append(provider)
@ -1234,9 +1237,10 @@ def tools_command(args=None, first_install: bool = False, config: dict = None):
config,
enabled_toolsets=new_enabled,
)
for ts_key in sorted(auto_configured):
label = next((l for k, l, _ in CONFIGURABLE_TOOLSETS if k == ts_key), ts_key)
print(color(f"{label}: using your Nous subscription defaults", Colors.GREEN))
if managed_nous_tools_enabled():
for ts_key in sorted(auto_configured):
label = next((l for k, l, _ in CONFIGURABLE_TOOLSETS if k == ts_key), ts_key)
print(color(f"{label}: using your Nous subscription defaults", Colors.GREEN))
# Walk through ALL selected tools that have provider options or
# need API keys. This ensures browser (Local vs Browserbase),