fix(tools): reuse subscription features for toolset listing
This commit is contained in:
parent
a1b3ce6bfa
commit
16bd5d23b4
|
|
@ -26,6 +26,7 @@ from hermes_cli.config import (
|
|||
from hermes_cli.colors import Colors, color
|
||||
from hermes_cli.nous_subscription import (
|
||||
MANAGED_FEATURE_COVERAGE_CATEGORY,
|
||||
NousSubscriptionFeatures,
|
||||
apply_nous_managed_defaults,
|
||||
get_nous_subscription_features,
|
||||
)
|
||||
|
|
@ -2618,6 +2619,7 @@ def _toolset_has_keys(
|
|||
config: dict = None,
|
||||
*,
|
||||
force_fresh: bool = False,
|
||||
features: Optional[NousSubscriptionFeatures] = None,
|
||||
) -> bool:
|
||||
"""Check if a toolset's required API keys are configured."""
|
||||
if config is None:
|
||||
|
|
@ -2633,7 +2635,10 @@ def _toolset_has_keys(
|
|||
return False
|
||||
|
||||
if ts_key in {"web", "image_gen", "video_gen", "tts", "stt", "browser"}:
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
if features is None:
|
||||
features = get_nous_subscription_features(
|
||||
config, force_fresh=force_fresh
|
||||
)
|
||||
feature = features.features.get(ts_key)
|
||||
if feature and (feature.available or feature.managed_by_nous):
|
||||
return True
|
||||
|
|
@ -2641,7 +2646,12 @@ def _toolset_has_keys(
|
|||
# Check TOOL_CATEGORIES first (provider-aware)
|
||||
cat = TOOL_CATEGORIES.get(ts_key)
|
||||
if cat:
|
||||
for provider in _visible_providers(cat, config, force_fresh=force_fresh):
|
||||
for provider in _visible_providers(
|
||||
cat,
|
||||
config,
|
||||
force_fresh=force_fresh,
|
||||
features=features,
|
||||
):
|
||||
env_vars = provider.get("env_vars", [])
|
||||
if not env_vars:
|
||||
return True # No-key provider (e.g. Local Browser, Edge TTS)
|
||||
|
|
@ -3076,6 +3086,7 @@ def _visible_providers(
|
|||
config: dict,
|
||||
*,
|
||||
force_fresh: bool = False,
|
||||
features: Optional[NousSubscriptionFeatures] = None,
|
||||
) -> list[dict]:
|
||||
"""Return provider entries visible for the current auth/config state.
|
||||
|
||||
|
|
@ -3085,7 +3096,8 @@ def _visible_providers(
|
|||
login + entitlement check (see ``_configure_provider``); the row only
|
||||
*activates* the gateway once paid access is confirmed.
|
||||
"""
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
if features is None:
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
acct = features.account_info
|
||||
# Pool-only users (entitled to managed tools via the free tool pool but with
|
||||
# no paid access) get image gen but NOT video gen — the pool doesn't fund
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ async def get_toolsets(profile: Optional[str] = None):
|
|||
_get_platform_tools,
|
||||
_toolset_configuration_platform,
|
||||
_toolset_has_keys,
|
||||
get_nous_subscription_features,
|
||||
gui_toolset_label,
|
||||
)
|
||||
from hermes_cli.platforms import platform_label
|
||||
|
|
@ -77,6 +78,7 @@ async def get_toolsets(profile: Optional[str] = None):
|
|||
)
|
||||
for platform in target_platforms
|
||||
}
|
||||
features = get_nous_subscription_features(config)
|
||||
result = []
|
||||
for name, label, desc in toolset_rows:
|
||||
try:
|
||||
|
|
@ -104,7 +106,7 @@ async def get_toolsets(profile: Optional[str] = None):
|
|||
),
|
||||
"enabled": is_enabled,
|
||||
"available": is_enabled,
|
||||
"configured": _toolset_has_keys(name, config),
|
||||
"configured": _toolset_has_keys(name, config, features=features),
|
||||
"tools": tools,
|
||||
})
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.nous_account import NousPortalAccountInfo
|
||||
from hermes_cli.nous_account import NousPortalAccountInfo, NousToolAccessInfo
|
||||
from hermes_cli.nous_subscription import NousSubscriptionFeatures
|
||||
from hermes_cli.tools_config import (
|
||||
_DEFAULT_OFF_TOOLSETS,
|
||||
_RECENTLY_SHIPPED_TOOLSETS,
|
||||
|
|
@ -544,6 +545,74 @@ def _fake_features(*, logged_in: bool, paid: bool = True):
|
|||
return SimpleNamespace(nous_auth_present=logged_in, account_info=account)
|
||||
|
||||
|
||||
def test_visible_providers_reuses_logged_out_feature_snapshot(monkeypatch):
|
||||
import hermes_cli.tools_config as tools_config
|
||||
|
||||
account = NousPortalAccountInfo(
|
||||
logged_in=False,
|
||||
source="none",
|
||||
fresh=False,
|
||||
paid_service_access=None,
|
||||
)
|
||||
features = NousSubscriptionFeatures(
|
||||
subscribed=False,
|
||||
nous_auth_present=False,
|
||||
provider_is_nous=False,
|
||||
features={},
|
||||
account_info=account,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tools_config,
|
||||
"get_nous_subscription_features",
|
||||
lambda *args, **kwargs: pytest.fail("feature snapshot was resolved again"),
|
||||
)
|
||||
|
||||
providers = _visible_providers(
|
||||
TOOL_CATEGORIES["image_gen"], {}, features=features
|
||||
)
|
||||
|
||||
assert any(
|
||||
provider.get("managed_nous_feature") == "image_gen"
|
||||
for provider in providers
|
||||
)
|
||||
|
||||
|
||||
def test_visible_providers_reuses_pool_video_feature_snapshot(monkeypatch):
|
||||
import hermes_cli.tools_config as tools_config
|
||||
|
||||
account = NousPortalAccountInfo(
|
||||
logged_in=True,
|
||||
source="jwt",
|
||||
fresh=False,
|
||||
paid_service_access=False,
|
||||
tool_access=NousToolAccessInfo(
|
||||
enabled=True,
|
||||
coverage={"fal-video": False},
|
||||
),
|
||||
)
|
||||
features = NousSubscriptionFeatures(
|
||||
subscribed=True,
|
||||
nous_auth_present=True,
|
||||
provider_is_nous=False,
|
||||
features={},
|
||||
account_info=account,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tools_config,
|
||||
"get_nous_subscription_features",
|
||||
lambda *args, **kwargs: pytest.fail("feature snapshot was resolved again"),
|
||||
)
|
||||
|
||||
providers = _visible_providers(
|
||||
TOOL_CATEGORIES["video_gen"], {}, features=features
|
||||
)
|
||||
|
||||
assert not any(
|
||||
provider.get("managed_nous_feature") == "video_gen"
|
||||
for provider in providers
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
# ── Windows console-flash guard for post-setup subprocess spawns ──────────────
|
||||
|
|
|
|||
|
|
@ -1948,6 +1948,36 @@ class TestNewEndpoints:
|
|||
config["platform_toolsets"]["discord"]
|
||||
)
|
||||
|
||||
def test_toolsets_resolve_subscription_features_once(self, monkeypatch):
|
||||
import hermes_cli.tools_config as tools_config
|
||||
from hermes_cli.nous_subscription import NousSubscriptionFeatures
|
||||
|
||||
calls = 0
|
||||
features = NousSubscriptionFeatures(
|
||||
subscribed=False,
|
||||
nous_auth_present=False,
|
||||
provider_is_nous=False,
|
||||
features={},
|
||||
account_info=None,
|
||||
)
|
||||
|
||||
def resolve_features(config, *, force_fresh=False):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return features
|
||||
|
||||
monkeypatch.setattr(
|
||||
tools_config,
|
||||
"get_nous_subscription_features",
|
||||
resolve_features,
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/tools/toolsets")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()
|
||||
assert calls == 1
|
||||
|
||||
|
||||
def test_get_toolset_config_returns_provider_matrix(self):
|
||||
"""GET .../config returns provider rows with structured env_vars."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue