fix(cli): coerce use_gateway config flags in tool routing
This commit is contained in:
parent
36b13709f5
commit
f66ebe64e8
4 changed files with 84 additions and 9 deletions
|
|
@ -9,6 +9,7 @@ from typing import Dict, Iterable, Optional, Set
|
||||||
from hermes_cli.auth import get_nous_auth_status
|
from hermes_cli.auth import get_nous_auth_status
|
||||||
from hermes_cli.config import get_env_value, load_config
|
from hermes_cli.config import get_env_value, load_config
|
||||||
from tools.managed_tool_gateway import is_managed_tool_gateway_ready
|
from tools.managed_tool_gateway import is_managed_tool_gateway_ready
|
||||||
|
from utils import is_truthy_value
|
||||||
from tools.tool_backend_helpers import (
|
from tools.tool_backend_helpers import (
|
||||||
fal_key_is_configured,
|
fal_key_is_configured,
|
||||||
has_direct_modal_credentials,
|
has_direct_modal_credentials,
|
||||||
|
|
@ -25,6 +26,13 @@ _DEFAULT_PLATFORM_TOOLSETS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class NousFeatureState:
|
class NousFeatureState:
|
||||||
key: str
|
key: str
|
||||||
|
|
@ -262,11 +270,11 @@ def get_nous_subscription_features(
|
||||||
# use_gateway flags — when True, the user explicitly opted into the
|
# use_gateway flags — when True, the user explicitly opted into the
|
||||||
# Tool Gateway via `hermes model`, so direct credentials should NOT
|
# Tool Gateway via `hermes model`, so direct credentials should NOT
|
||||||
# prevent gateway routing.
|
# prevent gateway routing.
|
||||||
web_use_gateway = bool(web_cfg.get("use_gateway"))
|
web_use_gateway = _uses_gateway(web_cfg)
|
||||||
tts_use_gateway = bool(tts_cfg.get("use_gateway"))
|
tts_use_gateway = _uses_gateway(tts_cfg)
|
||||||
browser_use_gateway = bool(browser_cfg.get("use_gateway"))
|
browser_use_gateway = _uses_gateway(browser_cfg)
|
||||||
image_gen_cfg = config.get("image_gen") if isinstance(config.get("image_gen"), dict) else {}
|
image_gen_cfg = config.get("image_gen") if isinstance(config.get("image_gen"), dict) else {}
|
||||||
image_use_gateway = bool(image_gen_cfg.get("use_gateway"))
|
image_use_gateway = _uses_gateway(image_gen_cfg)
|
||||||
|
|
||||||
direct_exa = bool(get_env_value("EXA_API_KEY"))
|
direct_exa = bool(get_env_value("EXA_API_KEY"))
|
||||||
direct_firecrawl = bool(get_env_value("FIRECRAWL_API_KEY") or get_env_value("FIRECRAWL_API_URL"))
|
direct_firecrawl = bool(get_env_value("FIRECRAWL_API_KEY") or get_env_value("FIRECRAWL_API_URL"))
|
||||||
|
|
@ -601,10 +609,10 @@ def get_gateway_eligible_tools(
|
||||||
# no direct keys exist — we only skip the prompt for tools where
|
# no direct keys exist — we only skip the prompt for tools where
|
||||||
# use_gateway was explicitly set.
|
# use_gateway was explicitly set.
|
||||||
opted_in = {
|
opted_in = {
|
||||||
"web": bool((config.get("web") if isinstance(config.get("web"), dict) else {}).get("use_gateway")),
|
"web": _uses_gateway(config.get("web")),
|
||||||
"image_gen": bool((config.get("image_gen") if isinstance(config.get("image_gen"), dict) else {}).get("use_gateway")),
|
"image_gen": _uses_gateway(config.get("image_gen")),
|
||||||
"tts": bool((config.get("tts") if isinstance(config.get("tts"), dict) else {}).get("use_gateway")),
|
"tts": _uses_gateway(config.get("tts")),
|
||||||
"browser": bool((config.get("browser") if isinstance(config.get("browser"), dict) else {}).get("use_gateway")),
|
"browser": _uses_gateway(config.get("browser")),
|
||||||
}
|
}
|
||||||
|
|
||||||
unconfigured: list[str] = []
|
unconfigured: list[str] = []
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,46 @@ def test_get_nous_subscription_features_requires_agent_browser_for_browserbase(m
|
||||||
assert features.browser.active is False
|
assert features.browser.active is False
|
||||||
assert features.browser.managed_by_nous is False
|
assert features.browser.managed_by_nous is False
|
||||||
assert features.browser.current_provider == "Browserbase"
|
assert features.browser.current_provider == "Browserbase"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_nous_subscription_features_does_not_treat_quoted_false_as_gateway_opt_in(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: {"logged_in": True})
|
||||||
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
||||||
|
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)
|
||||||
|
monkeypatch.setattr(ns, "is_managed_tool_gateway_ready", lambda vendor: vendor == "firecrawl")
|
||||||
|
|
||||||
|
features = ns.get_nous_subscription_features(
|
||||||
|
{"web": {"backend": "exa", "use_gateway": "false"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
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_gateway_eligible_tools_ignores_quoted_false_opt_in(monkeypatch):
|
||||||
|
monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
ns,
|
||||||
|
"_get_gateway_direct_credentials",
|
||||||
|
lambda: {"web": True, "image_gen": False, "tts": False, "browser": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
unconfigured, has_direct, already_managed = ns.get_gateway_eligible_tools(
|
||||||
|
{
|
||||||
|
"model": {"provider": "nous"},
|
||||||
|
"web": {"use_gateway": "false"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "web" in has_direct
|
||||||
|
assert "web" not in already_managed
|
||||||
|
assert set(unconfigured) == {"image_gen", "tts", "browser"}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ from tools.tool_backend_helpers import (
|
||||||
managed_nous_tools_enabled,
|
managed_nous_tools_enabled,
|
||||||
normalize_browser_cloud_provider,
|
normalize_browser_cloud_provider,
|
||||||
normalize_modal_mode,
|
normalize_modal_mode,
|
||||||
|
prefers_gateway,
|
||||||
resolve_modal_backend_state,
|
resolve_modal_backend_state,
|
||||||
resolve_openai_audio_api_key,
|
resolve_openai_audio_api_key,
|
||||||
)
|
)
|
||||||
|
|
@ -189,6 +190,27 @@ class TestHasDirectModalCredentials:
|
||||||
assert has_direct_modal_credentials() is True
|
assert has_direct_modal_credentials() is True
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# prefers_gateway
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
class TestPrefersGateway:
|
||||||
|
"""Honor bool-ish config values for tool gateway routing."""
|
||||||
|
|
||||||
|
def test_returns_false_for_quoted_false(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"hermes_cli.config.load_config",
|
||||||
|
lambda: {"web": {"use_gateway": "false"}},
|
||||||
|
)
|
||||||
|
assert prefers_gateway("web") is False
|
||||||
|
|
||||||
|
def test_returns_true_for_quoted_true(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"hermes_cli.config.load_config",
|
||||||
|
lambda: {"web": {"use_gateway": "true"}},
|
||||||
|
)
|
||||||
|
assert prefers_gateway("web") is True
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# resolve_modal_backend_state
|
# resolve_modal_backend_state
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from utils import is_truthy_value
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_BROWSER_PROVIDER = "local"
|
_DEFAULT_BROWSER_PROVIDER = "local"
|
||||||
_DEFAULT_MODAL_MODE = "auto"
|
_DEFAULT_MODAL_MODE = "auto"
|
||||||
|
|
@ -115,7 +117,7 @@ def prefers_gateway(config_section: str) -> bool:
|
||||||
from hermes_cli.config import load_config
|
from hermes_cli.config import load_config
|
||||||
section = (load_config() or {}).get(config_section)
|
section = (load_config() or {}).get(config_section)
|
||||||
if isinstance(section, dict):
|
if isinstance(section, dict):
|
||||||
return bool(section.get("use_gateway"))
|
return is_truthy_value(section.get("use_gateway"), default=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue