2026-03-26 15:27:27 -07:00
|
|
|
"""Helpers for Nous subscription managed-tool capabilities."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Dict, Iterable, Optional, Set
|
|
|
|
|
|
|
|
|
|
from hermes_cli.auth import get_nous_auth_status
|
|
|
|
|
from hermes_cli.config import get_env_value, load_config
|
|
|
|
|
from tools.managed_tool_gateway import is_managed_tool_gateway_ready
|
2026-04-26 11:28:42 +03:00
|
|
|
from utils import is_truthy_value
|
2026-03-26 15:27:27 -07:00
|
|
|
from tools.tool_backend_helpers import (
|
2026-04-21 01:59:15 -07:00
|
|
|
fal_key_is_configured,
|
2026-03-26 15:27:27 -07:00
|
|
|
has_direct_modal_credentials,
|
2026-03-30 13:28:10 +09:00
|
|
|
managed_nous_tools_enabled,
|
2026-03-26 15:27:27 -07:00
|
|
|
normalize_browser_cloud_provider,
|
|
|
|
|
normalize_modal_mode,
|
2026-03-31 08:48:54 +09:00
|
|
|
resolve_modal_backend_state,
|
2026-03-26 15:27:27 -07:00
|
|
|
resolve_openai_audio_api_key,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_DEFAULT_PLATFORM_TOOLSETS = {
|
|
|
|
|
"cli": "hermes-cli",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-26 11:28:42 +03:00
|
|
|
def _uses_gateway(section: object) -> bool:
|
|
|
|
|
"""Return True when a config section explicitly opts into the gateway."""
|
|
|
|
|
if not isinstance(section, dict):
|
|
|
|
|
return False
|
|
|
|
|
return is_truthy_value(section.get("use_gateway"), default=False)
|
|
|
|
|
|
|
|
|
|
|
2026-03-26 15:27:27 -07:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class NousFeatureState:
|
|
|
|
|
key: str
|
|
|
|
|
label: str
|
|
|
|
|
included_by_default: bool
|
|
|
|
|
available: bool
|
|
|
|
|
active: bool
|
|
|
|
|
managed_by_nous: bool
|
|
|
|
|
direct_override: bool
|
|
|
|
|
toolset_enabled: bool
|
|
|
|
|
current_provider: str = ""
|
|
|
|
|
explicit_configured: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class NousSubscriptionFeatures:
|
|
|
|
|
subscribed: bool
|
|
|
|
|
nous_auth_present: bool
|
|
|
|
|
provider_is_nous: bool
|
|
|
|
|
features: Dict[str, NousFeatureState]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def web(self) -> NousFeatureState:
|
|
|
|
|
return self.features["web"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def image_gen(self) -> NousFeatureState:
|
|
|
|
|
return self.features["image_gen"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def tts(self) -> NousFeatureState:
|
|
|
|
|
return self.features["tts"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def browser(self) -> NousFeatureState:
|
|
|
|
|
return self.features["browser"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def modal(self) -> NousFeatureState:
|
|
|
|
|
return self.features["modal"]
|
|
|
|
|
|
|
|
|
|
def items(self) -> Iterable[NousFeatureState]:
|
|
|
|
|
ordered = ("web", "image_gen", "tts", "browser", "modal")
|
|
|
|
|
for key in ordered:
|
|
|
|
|
yield self.features[key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _model_config_dict(config: Dict[str, object]) -> Dict[str, object]:
|
|
|
|
|
model_cfg = config.get("model")
|
|
|
|
|
if isinstance(model_cfg, dict):
|
|
|
|
|
return dict(model_cfg)
|
|
|
|
|
if isinstance(model_cfg, str) and model_cfg.strip():
|
|
|
|
|
return {"default": model_cfg.strip()}
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _toolset_enabled(config: Dict[str, object], toolset_key: str) -> bool:
|
|
|
|
|
from toolsets import resolve_toolset
|
|
|
|
|
|
|
|
|
|
platform_toolsets = config.get("platform_toolsets")
|
|
|
|
|
if not isinstance(platform_toolsets, dict) or not platform_toolsets:
|
|
|
|
|
platform_toolsets = {"cli": [_DEFAULT_PLATFORM_TOOLSETS["cli"]]}
|
|
|
|
|
|
|
|
|
|
target_tools = set(resolve_toolset(toolset_key))
|
|
|
|
|
if not target_tools:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
for platform, raw_toolsets in platform_toolsets.items():
|
|
|
|
|
if isinstance(raw_toolsets, list):
|
|
|
|
|
toolset_names = list(raw_toolsets)
|
|
|
|
|
else:
|
|
|
|
|
default_toolset = _DEFAULT_PLATFORM_TOOLSETS.get(platform)
|
|
|
|
|
toolset_names = [default_toolset] if default_toolset else []
|
|
|
|
|
if not toolset_names:
|
|
|
|
|
default_toolset = _DEFAULT_PLATFORM_TOOLSETS.get(platform)
|
|
|
|
|
if default_toolset:
|
|
|
|
|
toolset_names = [default_toolset]
|
|
|
|
|
|
|
|
|
|
available_tools: Set[str] = set()
|
|
|
|
|
for toolset_name in toolset_names:
|
|
|
|
|
if not isinstance(toolset_name, str) or not toolset_name:
|
|
|
|
|
continue
|
|
|
|
|
try:
|
|
|
|
|
available_tools.update(resolve_toolset(toolset_name))
|
|
|
|
|
except Exception:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if target_tools and target_tools.issubset(available_tools):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_agent_browser() -> bool:
|
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
|
|
agent_browser_bin = shutil.which("agent-browser")
|
|
|
|
|
local_bin = (
|
|
|
|
|
Path(__file__).parent.parent / "node_modules" / ".bin" / "agent-browser"
|
|
|
|
|
)
|
|
|
|
|
return bool(agent_browser_bin or local_bin.exists())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _browser_label(current_provider: str) -> str:
|
|
|
|
|
mapping = {
|
|
|
|
|
"browserbase": "Browserbase",
|
|
|
|
|
"browser-use": "Browser Use",
|
2026-04-06 14:05:26 -07:00
|
|
|
"firecrawl": "Firecrawl",
|
2026-03-31 09:29:43 +09:00
|
|
|
"camofox": "Camofox",
|
2026-03-26 15:27:27 -07:00
|
|
|
"local": "Local browser",
|
|
|
|
|
}
|
|
|
|
|
return mapping.get(current_provider or "local", current_provider or "Local browser")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _tts_label(current_provider: str) -> str:
|
|
|
|
|
mapping = {
|
|
|
|
|
"openai": "OpenAI TTS",
|
|
|
|
|
"elevenlabs": "ElevenLabs",
|
|
|
|
|
"edge": "Edge TTS",
|
feat(xai): upgrade to Responses API, add TTS provider
Cherry-picked and trimmed from PR #10600 by Jaaneek.
- Switch xAI transport from openai_chat to codex_responses (Responses API)
- Add codex_responses detection for xAI in all runtime_provider resolution paths
- Add xAI api_mode detection in AIAgent.__init__ (provider name + URL auto-detect)
- Add extra_headers passthrough for codex_responses requests
- Add x-grok-conv-id session header for xAI prompt caching
- Add xAI reasoning support (encrypted_content include, no effort param)
- Move x-grok-conv-id from chat_completions path to codex_responses path
- Add xAI TTS provider (dedicated /v1/tts endpoint with Opus conversion)
- Add xAI provider aliases (grok, x-ai, x.ai) across auth, models, providers, auxiliary
- Trim xAI model list to agentic models (grok-4.20-reasoning, grok-4-1-fast-reasoning)
- Add XAI_API_KEY/XAI_BASE_URL to OPTIONAL_ENV_VARS
- Add xAI TTS config section, setup wizard entry, tools_config provider option
- Add shared xai_http.py helper for User-Agent string
Co-authored-by: Jaaneek <Jaaneek@users.noreply.github.com>
2026-04-15 22:27:26 -07:00
|
|
|
"xai": "xAI TTS",
|
2026-04-06 19:04:00 +01:00
|
|
|
"mistral": "Mistral Voxtral TTS",
|
2026-03-26 15:27:27 -07:00
|
|
|
"neutts": "NeuTTS",
|
|
|
|
|
}
|
|
|
|
|
return mapping.get(current_provider or "edge", current_provider or "Edge TTS")
|
2026-03-31 09:29:43 +09:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_browser_feature_state(
|
|
|
|
|
*,
|
|
|
|
|
browser_tool_enabled: bool,
|
|
|
|
|
browser_provider: str,
|
|
|
|
|
browser_provider_explicit: bool,
|
|
|
|
|
browser_local_available: bool,
|
|
|
|
|
direct_camofox: bool,
|
|
|
|
|
direct_browserbase: bool,
|
|
|
|
|
direct_browser_use: bool,
|
2026-04-06 14:05:26 -07:00
|
|
|
direct_firecrawl: bool,
|
2026-03-31 09:29:43 +09:00
|
|
|
managed_browser_available: bool,
|
|
|
|
|
) -> tuple[str, bool, bool, bool]:
|
|
|
|
|
"""Resolve browser availability using the same precedence as runtime."""
|
|
|
|
|
if direct_camofox:
|
|
|
|
|
return "camofox", True, bool(browser_tool_enabled), False
|
|
|
|
|
|
|
|
|
|
if browser_provider_explicit:
|
|
|
|
|
current_provider = browser_provider or "local"
|
|
|
|
|
if current_provider == "browserbase":
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
available = bool(browser_local_available and direct_browserbase)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return current_provider, available, active, False
|
|
|
|
|
if current_provider == "browser-use":
|
|
|
|
|
provider_available = managed_browser_available or direct_browser_use
|
2026-03-31 09:29:43 +09:00
|
|
|
available = bool(browser_local_available and provider_available)
|
|
|
|
|
managed = bool(
|
|
|
|
|
browser_tool_enabled
|
|
|
|
|
and browser_local_available
|
|
|
|
|
and managed_browser_available
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
and not direct_browser_use
|
2026-03-31 09:29:43 +09:00
|
|
|
)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return current_provider, available, active, managed
|
2026-04-06 14:05:26 -07:00
|
|
|
if current_provider == "firecrawl":
|
|
|
|
|
available = bool(browser_local_available and direct_firecrawl)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return current_provider, available, active, False
|
2026-03-31 09:29:43 +09:00
|
|
|
if current_provider == "camofox":
|
|
|
|
|
return current_provider, False, False, False
|
|
|
|
|
|
|
|
|
|
current_provider = "local"
|
|
|
|
|
available = bool(browser_local_available)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return current_provider, available, active, False
|
|
|
|
|
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
if managed_browser_available or direct_browser_use:
|
2026-03-31 09:29:43 +09:00
|
|
|
available = bool(browser_local_available)
|
|
|
|
|
managed = bool(
|
|
|
|
|
browser_tool_enabled
|
|
|
|
|
and browser_local_available
|
|
|
|
|
and managed_browser_available
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
and not direct_browser_use
|
2026-03-31 09:29:43 +09:00
|
|
|
)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
return "browser-use", available, active, managed
|
|
|
|
|
|
|
|
|
|
if direct_browserbase:
|
|
|
|
|
available = bool(browser_local_available)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return "browserbase", available, active, False
|
2026-03-31 09:29:43 +09:00
|
|
|
|
|
|
|
|
available = bool(browser_local_available)
|
|
|
|
|
active = bool(browser_tool_enabled and available)
|
|
|
|
|
return "local", available, active, False
|
|
|
|
|
|
|
|
|
|
|
2026-03-26 15:27:27 -07:00
|
|
|
def get_nous_subscription_features(
|
|
|
|
|
config: Optional[Dict[str, object]] = None,
|
|
|
|
|
) -> NousSubscriptionFeatures:
|
|
|
|
|
if config is None:
|
|
|
|
|
config = load_config() or {}
|
|
|
|
|
config = dict(config)
|
|
|
|
|
model_cfg = _model_config_dict(config)
|
|
|
|
|
provider_is_nous = str(model_cfg.get("provider") or "").strip().lower() == "nous"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
nous_status = get_nous_auth_status()
|
|
|
|
|
except Exception:
|
|
|
|
|
nous_status = {}
|
|
|
|
|
|
2026-03-30 13:28:10 +09:00
|
|
|
managed_tools_flag = managed_nous_tools_enabled()
|
2026-03-26 15:27:27 -07:00
|
|
|
nous_auth_present = bool(nous_status.get("logged_in"))
|
|
|
|
|
subscribed = provider_is_nous or nous_auth_present
|
|
|
|
|
|
|
|
|
|
web_tool_enabled = _toolset_enabled(config, "web")
|
|
|
|
|
image_tool_enabled = _toolset_enabled(config, "image_gen")
|
|
|
|
|
tts_tool_enabled = _toolset_enabled(config, "tts")
|
|
|
|
|
browser_tool_enabled = _toolset_enabled(config, "browser")
|
|
|
|
|
modal_tool_enabled = _toolset_enabled(config, "terminal")
|
|
|
|
|
|
2026-03-31 09:29:43 +09:00
|
|
|
web_cfg = config.get("web") if isinstance(config.get("web"), dict) else {}
|
|
|
|
|
tts_cfg = config.get("tts") if isinstance(config.get("tts"), dict) else {}
|
|
|
|
|
browser_cfg = config.get("browser") if isinstance(config.get("browser"), dict) else {}
|
|
|
|
|
terminal_cfg = config.get("terminal") if isinstance(config.get("terminal"), dict) else {}
|
|
|
|
|
|
|
|
|
|
web_backend = str(web_cfg.get("backend") or "").strip().lower()
|
|
|
|
|
tts_provider = str(tts_cfg.get("provider") or "edge").strip().lower()
|
|
|
|
|
browser_provider_explicit = "cloud_provider" in browser_cfg
|
2026-03-26 15:27:27 -07:00
|
|
|
browser_provider = normalize_browser_cloud_provider(
|
2026-03-31 09:29:43 +09:00
|
|
|
browser_cfg.get("cloud_provider") if browser_provider_explicit else None
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
terminal_backend = (
|
2026-03-31 09:29:43 +09:00
|
|
|
str(terminal_cfg.get("backend") or "local").strip().lower()
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
modal_mode = normalize_modal_mode(
|
2026-03-31 09:29:43 +09:00
|
|
|
terminal_cfg.get("modal_mode")
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
# use_gateway flags — when True, the user explicitly opted into the
|
|
|
|
|
# Tool Gateway via `hermes model`, so direct credentials should NOT
|
|
|
|
|
# prevent gateway routing.
|
2026-04-26 11:28:42 +03:00
|
|
|
web_use_gateway = _uses_gateway(web_cfg)
|
|
|
|
|
tts_use_gateway = _uses_gateway(tts_cfg)
|
|
|
|
|
browser_use_gateway = _uses_gateway(browser_cfg)
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
image_gen_cfg = config.get("image_gen") if isinstance(config.get("image_gen"), dict) else {}
|
2026-04-26 11:28:42 +03:00
|
|
|
image_use_gateway = _uses_gateway(image_gen_cfg)
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
|
2026-03-31 08:48:54 +09:00
|
|
|
direct_exa = bool(get_env_value("EXA_API_KEY"))
|
2026-03-26 15:27:27 -07:00
|
|
|
direct_firecrawl = bool(get_env_value("FIRECRAWL_API_KEY") or get_env_value("FIRECRAWL_API_URL"))
|
|
|
|
|
direct_parallel = bool(get_env_value("PARALLEL_API_KEY"))
|
|
|
|
|
direct_tavily = bool(get_env_value("TAVILY_API_KEY"))
|
2026-04-21 01:59:15 -07:00
|
|
|
direct_fal = fal_key_is_configured()
|
2026-03-26 15:27:27 -07:00
|
|
|
direct_openai_tts = bool(resolve_openai_audio_api_key())
|
|
|
|
|
direct_elevenlabs = bool(get_env_value("ELEVENLABS_API_KEY"))
|
2026-03-31 09:29:43 +09:00
|
|
|
direct_camofox = bool(get_env_value("CAMOFOX_URL"))
|
2026-03-26 15:27:27 -07:00
|
|
|
direct_browserbase = bool(get_env_value("BROWSERBASE_API_KEY") and get_env_value("BROWSERBASE_PROJECT_ID"))
|
|
|
|
|
direct_browser_use = bool(get_env_value("BROWSER_USE_API_KEY"))
|
|
|
|
|
direct_modal = has_direct_modal_credentials()
|
|
|
|
|
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
# When use_gateway is set, suppress direct credentials for managed detection
|
|
|
|
|
if web_use_gateway:
|
|
|
|
|
direct_firecrawl = False
|
|
|
|
|
direct_exa = False
|
|
|
|
|
direct_parallel = False
|
|
|
|
|
direct_tavily = False
|
|
|
|
|
if image_use_gateway:
|
|
|
|
|
direct_fal = False
|
|
|
|
|
if tts_use_gateway:
|
|
|
|
|
direct_openai_tts = False
|
|
|
|
|
direct_elevenlabs = False
|
|
|
|
|
if browser_use_gateway:
|
|
|
|
|
direct_browser_use = False
|
|
|
|
|
direct_browserbase = False
|
|
|
|
|
|
2026-03-30 13:28:10 +09:00
|
|
|
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")
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
managed_browser_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("browser-use")
|
2026-03-30 13:28:10 +09:00
|
|
|
managed_modal_available = managed_tools_flag and nous_auth_present and is_managed_tool_gateway_ready("modal")
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_state = resolve_modal_backend_state(
|
|
|
|
|
modal_mode,
|
|
|
|
|
has_direct=direct_modal,
|
|
|
|
|
managed_ready=managed_modal_available,
|
|
|
|
|
)
|
2026-03-26 15:27:27 -07:00
|
|
|
|
|
|
|
|
web_managed = web_backend == "firecrawl" and managed_web_available and not direct_firecrawl
|
|
|
|
|
web_active = bool(
|
|
|
|
|
web_tool_enabled
|
|
|
|
|
and (
|
|
|
|
|
web_managed
|
2026-03-31 08:48:54 +09:00
|
|
|
or (web_backend == "exa" and direct_exa)
|
2026-03-26 15:27:27 -07:00
|
|
|
or (web_backend == "firecrawl" and direct_firecrawl)
|
|
|
|
|
or (web_backend == "parallel" and direct_parallel)
|
|
|
|
|
or (web_backend == "tavily" and direct_tavily)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
web_available = bool(
|
2026-03-31 08:48:54 +09:00
|
|
|
managed_web_available or direct_exa or direct_firecrawl or direct_parallel or direct_tavily
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
image_managed = image_tool_enabled and managed_image_available and not direct_fal
|
|
|
|
|
image_active = bool(image_tool_enabled and (image_managed or direct_fal))
|
|
|
|
|
image_available = bool(managed_image_available or direct_fal)
|
|
|
|
|
|
|
|
|
|
tts_current_provider = tts_provider or "edge"
|
|
|
|
|
tts_managed = (
|
|
|
|
|
tts_tool_enabled
|
|
|
|
|
and tts_current_provider == "openai"
|
|
|
|
|
and managed_tts_available
|
|
|
|
|
and not direct_openai_tts
|
|
|
|
|
)
|
|
|
|
|
tts_available = bool(
|
|
|
|
|
tts_current_provider in {"edge", "neutts"}
|
|
|
|
|
or (tts_current_provider == "openai" and (managed_tts_available or direct_openai_tts))
|
|
|
|
|
or (tts_current_provider == "elevenlabs" and direct_elevenlabs)
|
2026-04-06 19:04:00 +01:00
|
|
|
or (tts_current_provider == "mistral" and bool(get_env_value("MISTRAL_API_KEY")))
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
tts_active = bool(tts_tool_enabled and tts_available)
|
|
|
|
|
|
|
|
|
|
browser_local_available = _has_agent_browser()
|
2026-03-31 09:29:43 +09:00
|
|
|
(
|
|
|
|
|
browser_current_provider,
|
|
|
|
|
browser_available,
|
|
|
|
|
browser_active,
|
|
|
|
|
browser_managed,
|
|
|
|
|
) = _resolve_browser_feature_state(
|
|
|
|
|
browser_tool_enabled=browser_tool_enabled,
|
|
|
|
|
browser_provider=browser_provider,
|
|
|
|
|
browser_provider_explicit=browser_provider_explicit,
|
|
|
|
|
browser_local_available=browser_local_available,
|
|
|
|
|
direct_camofox=direct_camofox,
|
|
|
|
|
direct_browserbase=direct_browserbase,
|
|
|
|
|
direct_browser_use=direct_browser_use,
|
2026-04-06 14:05:26 -07:00
|
|
|
direct_firecrawl=direct_firecrawl,
|
2026-03-31 09:29:43 +09:00
|
|
|
managed_browser_available=managed_browser_available,
|
2026-03-26 15:27:27 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if terminal_backend != "modal":
|
|
|
|
|
modal_managed = False
|
|
|
|
|
modal_available = True
|
|
|
|
|
modal_active = bool(modal_tool_enabled)
|
|
|
|
|
modal_direct_override = False
|
2026-03-31 08:48:54 +09:00
|
|
|
elif modal_state["selected_backend"] == "managed":
|
|
|
|
|
modal_managed = bool(modal_tool_enabled)
|
|
|
|
|
modal_available = True
|
|
|
|
|
modal_active = bool(modal_tool_enabled)
|
|
|
|
|
modal_direct_override = False
|
|
|
|
|
elif modal_state["selected_backend"] == "direct":
|
|
|
|
|
modal_managed = False
|
|
|
|
|
modal_available = True
|
|
|
|
|
modal_active = bool(modal_tool_enabled)
|
|
|
|
|
modal_direct_override = bool(modal_tool_enabled)
|
2026-03-26 15:27:27 -07:00
|
|
|
elif modal_mode == "managed":
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_managed = False
|
2026-03-26 15:27:27 -07:00
|
|
|
modal_available = bool(managed_modal_available)
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_active = False
|
2026-03-26 15:27:27 -07:00
|
|
|
modal_direct_override = False
|
|
|
|
|
elif modal_mode == "direct":
|
|
|
|
|
modal_managed = False
|
|
|
|
|
modal_available = bool(direct_modal)
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_active = False
|
|
|
|
|
modal_direct_override = False
|
2026-03-26 15:27:27 -07:00
|
|
|
else:
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_managed = False
|
2026-03-26 15:27:27 -07:00
|
|
|
modal_available = bool(managed_modal_available or direct_modal)
|
2026-03-31 08:48:54 +09:00
|
|
|
modal_active = False
|
|
|
|
|
modal_direct_override = False
|
2026-03-26 15:27:27 -07:00
|
|
|
|
|
|
|
|
tts_explicit_configured = False
|
|
|
|
|
raw_tts_cfg = config.get("tts")
|
|
|
|
|
if isinstance(raw_tts_cfg, dict) and "provider" in raw_tts_cfg:
|
|
|
|
|
tts_explicit_configured = tts_provider not in {"", "edge"}
|
|
|
|
|
|
|
|
|
|
features = {
|
|
|
|
|
"web": NousFeatureState(
|
|
|
|
|
key="web",
|
|
|
|
|
label="Web tools",
|
|
|
|
|
included_by_default=True,
|
|
|
|
|
available=web_available,
|
|
|
|
|
active=web_active,
|
|
|
|
|
managed_by_nous=web_managed,
|
|
|
|
|
direct_override=web_active and not web_managed,
|
|
|
|
|
toolset_enabled=web_tool_enabled,
|
|
|
|
|
current_provider=web_backend or "",
|
|
|
|
|
explicit_configured=bool(web_backend),
|
|
|
|
|
),
|
|
|
|
|
"image_gen": NousFeatureState(
|
|
|
|
|
key="image_gen",
|
|
|
|
|
label="Image generation",
|
|
|
|
|
included_by_default=True,
|
|
|
|
|
available=image_available,
|
|
|
|
|
active=image_active,
|
|
|
|
|
managed_by_nous=image_managed,
|
|
|
|
|
direct_override=image_active and not image_managed,
|
|
|
|
|
toolset_enabled=image_tool_enabled,
|
|
|
|
|
current_provider="FAL" if direct_fal else ("Nous Subscription" if image_managed else ""),
|
|
|
|
|
explicit_configured=direct_fal,
|
|
|
|
|
),
|
|
|
|
|
"tts": NousFeatureState(
|
|
|
|
|
key="tts",
|
|
|
|
|
label="OpenAI TTS",
|
|
|
|
|
included_by_default=True,
|
|
|
|
|
available=tts_available,
|
|
|
|
|
active=tts_active,
|
|
|
|
|
managed_by_nous=tts_managed,
|
|
|
|
|
direct_override=tts_active and not tts_managed,
|
|
|
|
|
toolset_enabled=tts_tool_enabled,
|
|
|
|
|
current_provider=_tts_label(tts_current_provider),
|
|
|
|
|
explicit_configured=tts_explicit_configured,
|
|
|
|
|
),
|
|
|
|
|
"browser": NousFeatureState(
|
|
|
|
|
key="browser",
|
|
|
|
|
label="Browser automation",
|
|
|
|
|
included_by_default=True,
|
|
|
|
|
available=browser_available,
|
|
|
|
|
active=browser_active,
|
|
|
|
|
managed_by_nous=browser_managed,
|
|
|
|
|
direct_override=browser_active and not browser_managed,
|
|
|
|
|
toolset_enabled=browser_tool_enabled,
|
|
|
|
|
current_provider=_browser_label(browser_current_provider),
|
2026-03-31 09:29:43 +09:00
|
|
|
explicit_configured=browser_provider_explicit,
|
2026-03-26 15:27:27 -07:00
|
|
|
),
|
|
|
|
|
"modal": NousFeatureState(
|
|
|
|
|
key="modal",
|
|
|
|
|
label="Modal execution",
|
|
|
|
|
included_by_default=False,
|
|
|
|
|
available=modal_available,
|
|
|
|
|
active=modal_active,
|
|
|
|
|
managed_by_nous=modal_managed,
|
|
|
|
|
direct_override=terminal_backend == "modal" and modal_direct_override,
|
|
|
|
|
toolset_enabled=modal_tool_enabled,
|
|
|
|
|
current_provider="Modal" if terminal_backend == "modal" else terminal_backend or "local",
|
|
|
|
|
explicit_configured=terminal_backend == "modal",
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NousSubscriptionFeatures(
|
|
|
|
|
subscribed=subscribed,
|
|
|
|
|
nous_auth_present=nous_auth_present,
|
|
|
|
|
provider_is_nous=provider_is_nous,
|
|
|
|
|
features=features,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_nous_managed_defaults(
|
|
|
|
|
config: Dict[str, object],
|
|
|
|
|
*,
|
|
|
|
|
enabled_toolsets: Optional[Iterable[str]] = None,
|
|
|
|
|
) -> set[str]:
|
2026-03-30 13:28:10 +09:00
|
|
|
if not managed_nous_tools_enabled():
|
|
|
|
|
return set()
|
|
|
|
|
|
2026-03-26 15:27:27 -07:00
|
|
|
features = get_nous_subscription_features(config)
|
|
|
|
|
if not features.provider_is_nous:
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
selected_toolsets = set(enabled_toolsets or ())
|
|
|
|
|
changed: set[str] = set()
|
|
|
|
|
|
|
|
|
|
web_cfg = config.get("web")
|
|
|
|
|
if not isinstance(web_cfg, dict):
|
|
|
|
|
web_cfg = {}
|
|
|
|
|
config["web"] = web_cfg
|
|
|
|
|
|
|
|
|
|
tts_cfg = config.get("tts")
|
|
|
|
|
if not isinstance(tts_cfg, dict):
|
|
|
|
|
tts_cfg = {}
|
|
|
|
|
config["tts"] = tts_cfg
|
|
|
|
|
|
|
|
|
|
browser_cfg = config.get("browser")
|
|
|
|
|
if not isinstance(browser_cfg, dict):
|
|
|
|
|
browser_cfg = {}
|
|
|
|
|
config["browser"] = browser_cfg
|
|
|
|
|
|
|
|
|
|
if "web" in selected_toolsets and not features.web.explicit_configured and not (
|
|
|
|
|
get_env_value("PARALLEL_API_KEY")
|
|
|
|
|
or get_env_value("TAVILY_API_KEY")
|
|
|
|
|
or get_env_value("FIRECRAWL_API_KEY")
|
|
|
|
|
or get_env_value("FIRECRAWL_API_URL")
|
|
|
|
|
):
|
|
|
|
|
web_cfg["backend"] = "firecrawl"
|
|
|
|
|
changed.add("web")
|
|
|
|
|
|
|
|
|
|
if "tts" in selected_toolsets and not features.tts.explicit_configured and not (
|
|
|
|
|
resolve_openai_audio_api_key()
|
|
|
|
|
or get_env_value("ELEVENLABS_API_KEY")
|
|
|
|
|
):
|
|
|
|
|
tts_cfg["provider"] = "openai"
|
|
|
|
|
changed.add("tts")
|
|
|
|
|
|
|
|
|
|
if "browser" in selected_toolsets and not features.browser.explicit_configured and not (
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
get_env_value("BROWSER_USE_API_KEY")
|
|
|
|
|
or get_env_value("BROWSERBASE_API_KEY")
|
2026-03-26 15:27:27 -07:00
|
|
|
):
|
feat: switch managed browser provider from Browserbase to Browser Use (#5750)
* feat: switch managed browser provider from Browserbase to Browser Use
The Nous subscription tool gateway now routes browser automation through
Browser Use instead of Browserbase. This commit:
- Adds managed Nous gateway support to BrowserUseProvider (idempotency
keys, X-BB-API-Key auth header, external_call_id persistence)
- Removes managed gateway support from BrowserbaseProvider (now
direct-only via BROWSERBASE_API_KEY/BROWSERBASE_PROJECT_ID)
- Updates browser_tool.py fallback: prefers Browser Use over Browserbase
- Updates nous_subscription.py: gateway vendor 'browser-use', auto-config
sets cloud_provider='browser-use' for new subscribers
- Updates tools_config.py: Nous Subscription entry now uses Browser Use
- Updates setup.py, cli.py, status.py, prompt_builder.py display strings
- Updates all affected tests to match new behavior
Browserbase remains fully functional for users with direct API credentials.
The change only affects the managed/subscription path.
* chore: remove redundant Browser Use hint from system prompt
* fix: upgrade Browser Use provider to v3 API
- Base URL: api/v2 -> api/v3 (v2 is legacy)
- Unified all endpoints to use native Browser Use paths:
- POST /browsers (create session, returns cdpUrl)
- PATCH /browsers/{id} with {action: stop} (close session)
- Removed managed-mode branching that used Browserbase-style
/v1/sessions paths — v3 gateway now supports /browsers directly
- Removed unused managed_mode variable in close_session
* fix(browser-use): use X-Browser-Use-API-Key header for managed mode
The managed gateway expects X-Browser-Use-API-Key, not X-BB-API-Key
(which is a Browserbase-specific header). Using the wrong header caused
a 401 AUTH_ERROR on every managed-mode browser session create.
Simplified _headers() to always use X-Browser-Use-API-Key regardless
of direct vs managed mode.
* fix(nous_subscription): browserbase explicit provider is direct-only
Since managed Nous gateway now routes through Browser Use, the
browserbase explicit provider path should not check managed_browser_available
(which resolves against the browser-use gateway). Simplified to direct-only
with managed=False.
* fix(browser-use): port missing improvements from PR #5605
- CDP URL normalization: resolve HTTP discovery URLs to websocket after
cloud provider create_session() (prevents agent-browser failures)
- Managed session payload: send timeout=5 and proxyCountryCode=us for
gateway-backed sessions (prevents billing overruns)
- Update prompt builder, browser_close schema, and module docstring to
replace remaining Browserbase references with Browser Use
- Dynamic /browser status detection via _get_cloud_provider() instead
of hardcoded env var checks (future-proof for new providers)
- Rename post_setup key from 'browserbase' to 'agent_browser'
- Update setup hint to mention Browser Use alongside Browserbase
- Add tests: CDP normalization, browserbase direct-only guard,
managed browser-use gateway, direct browserbase fallback
---------
Co-authored-by: rob-maron <132852777+rob-maron@users.noreply.github.com>
2026-04-07 22:40:22 +10:00
|
|
|
browser_cfg["cloud_provider"] = "browser-use"
|
2026-03-26 15:27:27 -07:00
|
|
|
changed.add("browser")
|
|
|
|
|
|
2026-04-21 01:59:15 -07:00
|
|
|
if "image_gen" in selected_toolsets and not fal_key_is_configured():
|
2026-03-26 15:27:27 -07:00
|
|
|
changed.add("image_gen")
|
|
|
|
|
|
|
|
|
|
return changed
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tool Gateway offer — single Y/n prompt after model selection
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
_GATEWAY_TOOL_LABELS = {
|
|
|
|
|
"web": "Web search & extract (Firecrawl)",
|
|
|
|
|
"image_gen": "Image generation (FAL)",
|
|
|
|
|
"tts": "Text-to-speech (OpenAI TTS)",
|
|
|
|
|
"browser": "Browser automation (Browser Use)",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_gateway_direct_credentials() -> Dict[str, bool]:
|
|
|
|
|
"""Return a dict of tool_key -> has_direct_credentials."""
|
|
|
|
|
return {
|
|
|
|
|
"web": bool(
|
|
|
|
|
get_env_value("FIRECRAWL_API_KEY")
|
|
|
|
|
or get_env_value("FIRECRAWL_API_URL")
|
|
|
|
|
or get_env_value("PARALLEL_API_KEY")
|
|
|
|
|
or get_env_value("TAVILY_API_KEY")
|
|
|
|
|
or get_env_value("EXA_API_KEY")
|
|
|
|
|
),
|
2026-04-21 01:59:15 -07:00
|
|
|
"image_gen": fal_key_is_configured(),
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
"tts": bool(
|
|
|
|
|
resolve_openai_audio_api_key()
|
|
|
|
|
or get_env_value("ELEVENLABS_API_KEY")
|
|
|
|
|
),
|
|
|
|
|
"browser": bool(
|
|
|
|
|
get_env_value("BROWSER_USE_API_KEY")
|
|
|
|
|
or (get_env_value("BROWSERBASE_API_KEY") and get_env_value("BROWSERBASE_PROJECT_ID"))
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_GATEWAY_DIRECT_LABELS = {
|
|
|
|
|
"web": "Firecrawl/Exa/Parallel/Tavily key",
|
|
|
|
|
"image_gen": "FAL key",
|
|
|
|
|
"tts": "OpenAI/ElevenLabs key",
|
|
|
|
|
"browser": "Browser Use/Browserbase key",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ALL_GATEWAY_KEYS = ("web", "image_gen", "tts", "browser")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_gateway_eligible_tools(
|
|
|
|
|
config: Optional[Dict[str, object]] = None,
|
|
|
|
|
) -> tuple[list[str], list[str], list[str]]:
|
|
|
|
|
"""Return (unconfigured, has_direct, already_managed) tool key lists.
|
|
|
|
|
|
|
|
|
|
- unconfigured: tools with no direct credentials (easy switch)
|
|
|
|
|
- has_direct: tools where the user has their own API keys
|
|
|
|
|
- already_managed: tools already routed through the gateway
|
|
|
|
|
|
|
|
|
|
All lists are empty when the user is not a paid Nous subscriber or
|
|
|
|
|
is not using Nous as their provider.
|
|
|
|
|
"""
|
|
|
|
|
if not managed_nous_tools_enabled():
|
|
|
|
|
return [], [], []
|
|
|
|
|
|
|
|
|
|
if config is None:
|
|
|
|
|
config = load_config() or {}
|
|
|
|
|
|
|
|
|
|
# Quick provider check without the heavy get_nous_subscription_features call
|
|
|
|
|
model_cfg = config.get("model")
|
|
|
|
|
if not isinstance(model_cfg, dict) or str(model_cfg.get("provider") or "").strip().lower() != "nous":
|
|
|
|
|
return [], [], []
|
|
|
|
|
|
|
|
|
|
direct = _get_gateway_direct_credentials()
|
|
|
|
|
|
|
|
|
|
# Check which tools the user has explicitly opted into the gateway for.
|
|
|
|
|
# This is distinct from managed_by_nous which fires implicitly when
|
|
|
|
|
# no direct keys exist — we only skip the prompt for tools where
|
|
|
|
|
# use_gateway was explicitly set.
|
|
|
|
|
opted_in = {
|
2026-04-26 11:28:42 +03:00
|
|
|
"web": _uses_gateway(config.get("web")),
|
|
|
|
|
"image_gen": _uses_gateway(config.get("image_gen")),
|
|
|
|
|
"tts": _uses_gateway(config.get("tts")),
|
|
|
|
|
"browser": _uses_gateway(config.get("browser")),
|
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)
2026-04-16 01:59:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unconfigured: list[str] = []
|
|
|
|
|
has_direct: list[str] = []
|
|
|
|
|
already_managed: list[str] = []
|
|
|
|
|
for key in _ALL_GATEWAY_KEYS:
|
|
|
|
|
if opted_in.get(key):
|
|
|
|
|
already_managed.append(key)
|
|
|
|
|
elif direct.get(key):
|
|
|
|
|
has_direct.append(key)
|
|
|
|
|
else:
|
|
|
|
|
unconfigured.append(key)
|
|
|
|
|
return unconfigured, has_direct, already_managed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_gateway_defaults(
|
|
|
|
|
config: Dict[str, object],
|
|
|
|
|
tool_keys: list[str],
|
|
|
|
|
) -> set[str]:
|
|
|
|
|
"""Apply Tool Gateway config for the given tool keys.
|
|
|
|
|
|
|
|
|
|
Sets ``use_gateway: true`` in each tool's config section so the
|
|
|
|
|
runtime prefers the gateway even when direct API keys are present.
|
|
|
|
|
|
|
|
|
|
Returns the set of tools that were actually changed.
|
|
|
|
|
"""
|
|
|
|
|
changed: set[str] = set()
|
|
|
|
|
|
|
|
|
|
web_cfg = config.get("web")
|
|
|
|
|
if not isinstance(web_cfg, dict):
|
|
|
|
|
web_cfg = {}
|
|
|
|
|
config["web"] = web_cfg
|
|
|
|
|
|
|
|
|
|
tts_cfg = config.get("tts")
|
|
|
|
|
if not isinstance(tts_cfg, dict):
|
|
|
|
|
tts_cfg = {}
|
|
|
|
|
config["tts"] = tts_cfg
|
|
|
|
|
|
|
|
|
|
browser_cfg = config.get("browser")
|
|
|
|
|
if not isinstance(browser_cfg, dict):
|
|
|
|
|
browser_cfg = {}
|
|
|
|
|
config["browser"] = browser_cfg
|
|
|
|
|
|
|
|
|
|
if "web" in tool_keys:
|
|
|
|
|
web_cfg["backend"] = "firecrawl"
|
|
|
|
|
web_cfg["use_gateway"] = True
|
|
|
|
|
changed.add("web")
|
|
|
|
|
|
|
|
|
|
if "tts" in tool_keys:
|
|
|
|
|
tts_cfg["provider"] = "openai"
|
|
|
|
|
tts_cfg["use_gateway"] = True
|
|
|
|
|
changed.add("tts")
|
|
|
|
|
|
|
|
|
|
if "browser" in tool_keys:
|
|
|
|
|
browser_cfg["cloud_provider"] = "browser-use"
|
|
|
|
|
browser_cfg["use_gateway"] = True
|
|
|
|
|
changed.add("browser")
|
|
|
|
|
|
|
|
|
|
if "image_gen" in tool_keys:
|
|
|
|
|
image_cfg = config.get("image_gen")
|
|
|
|
|
if not isinstance(image_cfg, dict):
|
|
|
|
|
image_cfg = {}
|
|
|
|
|
config["image_gen"] = image_cfg
|
|
|
|
|
image_cfg["use_gateway"] = True
|
|
|
|
|
changed.add("image_gen")
|
|
|
|
|
|
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def prompt_enable_tool_gateway(config: Dict[str, object]) -> set[str]:
|
|
|
|
|
"""If eligible tools exist, prompt the user to enable the Tool Gateway.
|
|
|
|
|
|
|
|
|
|
Uses prompt_choice() with a description parameter so the curses TUI
|
|
|
|
|
shows the tool context alongside the choices.
|
|
|
|
|
|
|
|
|
|
Returns the set of tools that were enabled, or empty set if the user
|
|
|
|
|
declined or no tools were eligible.
|
|
|
|
|
"""
|
|
|
|
|
unconfigured, has_direct, already_managed = get_gateway_eligible_tools(config)
|
|
|
|
|
if not unconfigured and not has_direct:
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.setup import prompt_choice
|
|
|
|
|
except Exception:
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
# Build description lines showing full status of all gateway tools
|
|
|
|
|
desc_parts: list[str] = [
|
|
|
|
|
"",
|
|
|
|
|
" The Tool Gateway gives you access to web search, image generation,",
|
|
|
|
|
" text-to-speech, and browser automation through your Nous subscription.",
|
|
|
|
|
" No need to sign up for separate API keys — just pick the tools you want.",
|
|
|
|
|
"",
|
|
|
|
|
]
|
|
|
|
|
if already_managed:
|
|
|
|
|
for k in already_managed:
|
|
|
|
|
desc_parts.append(f" ✓ {_GATEWAY_TOOL_LABELS[k]} — using Tool Gateway")
|
|
|
|
|
if unconfigured:
|
|
|
|
|
for k in unconfigured:
|
|
|
|
|
desc_parts.append(f" ○ {_GATEWAY_TOOL_LABELS[k]} — not configured")
|
|
|
|
|
if has_direct:
|
|
|
|
|
for k in has_direct:
|
|
|
|
|
desc_parts.append(f" ○ {_GATEWAY_TOOL_LABELS[k]} — using {_GATEWAY_DIRECT_LABELS[k]}")
|
|
|
|
|
|
|
|
|
|
# Build short choice labels — detail is in the description above
|
|
|
|
|
choices: list[str] = []
|
|
|
|
|
choice_keys: list[str] = [] # maps choice index -> action
|
|
|
|
|
|
|
|
|
|
if unconfigured and has_direct:
|
|
|
|
|
choices.append("Enable for all tools (existing keys kept, not used)")
|
|
|
|
|
choice_keys.append("all")
|
|
|
|
|
|
|
|
|
|
choices.append("Enable only for tools without existing keys")
|
|
|
|
|
choice_keys.append("unconfigured")
|
|
|
|
|
|
|
|
|
|
choices.append("Skip")
|
|
|
|
|
choice_keys.append("skip")
|
|
|
|
|
|
|
|
|
|
elif unconfigured:
|
|
|
|
|
choices.append("Enable Tool Gateway")
|
|
|
|
|
choice_keys.append("unconfigured")
|
|
|
|
|
|
|
|
|
|
choices.append("Skip")
|
|
|
|
|
choice_keys.append("skip")
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
choices.append("Enable Tool Gateway (existing keys kept, not used)")
|
|
|
|
|
choice_keys.append("all")
|
|
|
|
|
|
|
|
|
|
choices.append("Skip")
|
|
|
|
|
choice_keys.append("skip")
|
|
|
|
|
|
|
|
|
|
description = "\n".join(desc_parts) if desc_parts else None
|
|
|
|
|
# Default to "Enable" when user has no direct keys (new user),
|
|
|
|
|
# default to "Skip" when they have existing keys to preserve.
|
|
|
|
|
default_idx = 0 if not has_direct else len(choices) - 1
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
idx = prompt_choice(
|
|
|
|
|
"Your Nous subscription includes the Tool Gateway.",
|
|
|
|
|
choices,
|
|
|
|
|
default_idx,
|
|
|
|
|
description=description,
|
|
|
|
|
)
|
|
|
|
|
except (KeyboardInterrupt, EOFError, OSError, SystemExit):
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
action = choice_keys[idx]
|
|
|
|
|
if action == "skip":
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
if action == "all":
|
|
|
|
|
# Apply to switchable tools + ensure already-managed tools also
|
|
|
|
|
# have use_gateway persisted in config for consistency.
|
|
|
|
|
to_apply = list(_ALL_GATEWAY_KEYS)
|
|
|
|
|
else:
|
|
|
|
|
to_apply = unconfigured
|
|
|
|
|
|
|
|
|
|
changed = apply_gateway_defaults(config, to_apply)
|
|
|
|
|
if changed:
|
|
|
|
|
from hermes_cli.config import save_config
|
|
|
|
|
save_config(config)
|
|
|
|
|
# Only report the tools that actually switched (not already-managed ones)
|
|
|
|
|
newly_switched = changed - set(already_managed)
|
|
|
|
|
for key in sorted(newly_switched):
|
|
|
|
|
label = _GATEWAY_TOOL_LABELS.get(key, key)
|
|
|
|
|
print(f" ✓ {label}: enabled via Nous subscription")
|
|
|
|
|
if already_managed and not newly_switched:
|
|
|
|
|
print(" (all tools already using Tool Gateway)")
|
|
|
|
|
return changed
|