mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
fix(inventory): avoid fresh Nous tier checks in picker payloads
This commit is contained in:
parent
846821d8c0
commit
eb70ab894b
3 changed files with 106 additions and 4 deletions
|
|
@ -116,6 +116,7 @@ def build_models_payload(
|
|||
canonical_order: bool = False,
|
||||
pricing: bool = False,
|
||||
capabilities: bool = False,
|
||||
force_fresh_nous_tier: bool = False,
|
||||
max_models: int = 50,
|
||||
) -> dict:
|
||||
"""Build the ``{providers, model, provider}`` shape every consumer
|
||||
|
|
@ -139,6 +140,10 @@ def build_models_payload(
|
|||
``{model: {fast, reasoning}}`` so pickers can gate the model-options
|
||||
controls (fast toggle / reasoning) to what each model actually
|
||||
supports, instead of offering knobs the backend would reject.
|
||||
- ``force_fresh_nous_tier``: bypass the short Nous free-tier cache when
|
||||
selecting Portal-recommended Nous models and applying tier gating. Keep
|
||||
this false for UI picker opens; explicit auth/model flows can opt in
|
||||
when they need freshly-purchased credits to show up immediately.
|
||||
"""
|
||||
from hermes_cli.model_switch import list_authenticated_providers
|
||||
|
||||
|
|
@ -148,6 +153,7 @@ def build_models_payload(
|
|||
current_model=ctx.current_model,
|
||||
user_providers=ctx.user_providers,
|
||||
custom_providers=ctx.custom_providers,
|
||||
force_fresh_nous_tier=force_fresh_nous_tier,
|
||||
max_models=max_models,
|
||||
)
|
||||
|
||||
|
|
@ -158,7 +164,7 @@ def build_models_payload(
|
|||
if canonical_order:
|
||||
rows = _reorder_canonical(rows)
|
||||
if pricing:
|
||||
_apply_pricing(rows)
|
||||
_apply_pricing(rows, force_fresh_nous_tier=force_fresh_nous_tier)
|
||||
if capabilities:
|
||||
_apply_capabilities(rows)
|
||||
|
||||
|
|
@ -293,7 +299,11 @@ def _reorder_canonical(rows: list[dict]) -> list[dict]:
|
|||
return canon + extras
|
||||
|
||||
|
||||
def _apply_pricing(rows: list[dict]) -> None:
|
||||
def _apply_pricing(
|
||||
rows: list[dict],
|
||||
*,
|
||||
force_fresh_nous_tier: bool = False,
|
||||
) -> None:
|
||||
"""Enrich each provider row with per-model pricing + Nous tier gating.
|
||||
|
||||
Mutates ``rows`` in-place. For every row whose provider supports live
|
||||
|
|
@ -359,7 +369,9 @@ def _apply_pricing(rows: list[dict]) -> None:
|
|||
if slug == "nous":
|
||||
try:
|
||||
if nous_free_tier is None:
|
||||
nous_free_tier = check_nous_free_tier(force_fresh=True)
|
||||
nous_free_tier = check_nous_free_tier(
|
||||
force_fresh=force_fresh_nous_tier
|
||||
)
|
||||
row["free_tier"] = bool(nous_free_tier)
|
||||
if nous_free_tier:
|
||||
_selectable, unavailable = partition_nous_models_by_tier(
|
||||
|
|
|
|||
|
|
@ -1178,6 +1178,7 @@ def list_authenticated_providers(
|
|||
current_base_url: str = "",
|
||||
user_providers: dict = None,
|
||||
custom_providers: list | None = None,
|
||||
force_fresh_nous_tier: bool = False,
|
||||
max_models: int = 8,
|
||||
current_model: str = "",
|
||||
) -> List[dict]:
|
||||
|
|
@ -1197,6 +1198,9 @@ def list_authenticated_providers(
|
|||
- source: str — "built-in", "models.dev", "user-config"
|
||||
|
||||
Only includes providers that have API keys set or are user-defined endpoints.
|
||||
``force_fresh_nous_tier`` bypasses the short Nous tier cache for explicit
|
||||
account-sensitive flows. UI picker opens should leave it false so they do
|
||||
not block on fresh Portal/account checks every time.
|
||||
"""
|
||||
import os
|
||||
from agent.models_dev import (
|
||||
|
|
@ -1539,7 +1543,7 @@ def list_authenticated_providers(
|
|||
_portal = _st.get("portal_base_url", "") or ""
|
||||
except Exception:
|
||||
_portal = ""
|
||||
if _nous_free(force_fresh=True):
|
||||
if _nous_free(force_fresh=force_fresh_nous_tier):
|
||||
model_ids, _ = _union_free(model_ids, _pricing, _portal)
|
||||
else:
|
||||
model_ids, _ = _union_paid(model_ids, _pricing, _portal)
|
||||
|
|
|
|||
|
|
@ -141,6 +141,18 @@ def _list_auth_returning(rows: list[dict]):
|
|||
)
|
||||
|
||||
|
||||
def _nous_row(model: str = "openai/gpt-5.5") -> dict:
|
||||
return {
|
||||
"slug": "nous",
|
||||
"name": "Nous",
|
||||
"models": [model],
|
||||
"total_models": 1,
|
||||
"is_current": True,
|
||||
"is_user_defined": False,
|
||||
"source": "built-in",
|
||||
}
|
||||
|
||||
|
||||
def test_build_models_payload_returns_expected_shape():
|
||||
rows = [
|
||||
{"slug": "openrouter", "name": "OpenRouter", "models": ["m1"],
|
||||
|
|
@ -173,6 +185,80 @@ def test_build_models_payload_does_not_call_provider_model_ids():
|
|||
mock_pm.assert_not_called()
|
||||
|
||||
|
||||
def test_build_models_payload_uses_cached_nous_tier_by_default():
|
||||
"""Picker payloads should not force fresh Nous account checks.
|
||||
|
||||
Desktop/status picker opens are request/response UI paths. They can hit
|
||||
the short free-tier cache; explicit model/auth flows can still opt into a
|
||||
fresh account check when needed.
|
||||
"""
|
||||
ctx = _empty_ctx(provider="nous", model="openai/gpt-5.5")
|
||||
rows = [_nous_row()]
|
||||
with patch(
|
||||
"hermes_cli.model_switch.list_authenticated_providers",
|
||||
return_value=rows,
|
||||
) as mock_list:
|
||||
build_models_payload(ctx)
|
||||
|
||||
mock_list.assert_called_once()
|
||||
assert mock_list.call_args.kwargs["force_fresh_nous_tier"] is False
|
||||
|
||||
|
||||
def test_build_models_payload_can_force_fresh_nous_tier():
|
||||
ctx = _empty_ctx(provider="nous", model="openai/gpt-5.5")
|
||||
rows = [_nous_row()]
|
||||
with patch(
|
||||
"hermes_cli.model_switch.list_authenticated_providers",
|
||||
return_value=rows,
|
||||
) as mock_list:
|
||||
build_models_payload(ctx, force_fresh_nous_tier=True)
|
||||
|
||||
mock_list.assert_called_once()
|
||||
assert mock_list.call_args.kwargs["force_fresh_nous_tier"] is True
|
||||
|
||||
|
||||
def test_pricing_uses_cached_nous_tier_by_default():
|
||||
rows = [_nous_row()]
|
||||
ctx = _empty_ctx(provider="nous", model="openai/gpt-5.5")
|
||||
with (
|
||||
_list_auth_returning(rows),
|
||||
patch(
|
||||
"hermes_cli.models.get_pricing_for_provider",
|
||||
return_value={
|
||||
"openai/gpt-5.5": {
|
||||
"prompt": "0.000001",
|
||||
"completion": "0.000002",
|
||||
},
|
||||
},
|
||||
),
|
||||
patch("hermes_cli.models.check_nous_free_tier", return_value=False) as mock_free,
|
||||
):
|
||||
build_models_payload(ctx, pricing=True)
|
||||
|
||||
mock_free.assert_called_once_with(force_fresh=False)
|
||||
|
||||
|
||||
def test_pricing_can_force_fresh_nous_tier():
|
||||
rows = [_nous_row()]
|
||||
ctx = _empty_ctx(provider="nous", model="openai/gpt-5.5")
|
||||
with (
|
||||
_list_auth_returning(rows),
|
||||
patch(
|
||||
"hermes_cli.models.get_pricing_for_provider",
|
||||
return_value={
|
||||
"openai/gpt-5.5": {
|
||||
"prompt": "0.000001",
|
||||
"completion": "0.000002",
|
||||
},
|
||||
},
|
||||
),
|
||||
patch("hermes_cli.models.check_nous_free_tier", return_value=False) as mock_free,
|
||||
):
|
||||
build_models_payload(ctx, pricing=True, force_fresh_nous_tier=True)
|
||||
|
||||
mock_free.assert_called_once_with(force_fresh=True)
|
||||
|
||||
|
||||
def test_include_unconfigured_appends_canonical_skeletons():
|
||||
"""include_unconfigured=True adds CANONICAL_PROVIDERS rows that
|
||||
list_authenticated_providers didn't emit. Skeleton rows have empty
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue