fix(free-tier): the desktop card body leaves the "To sign in" tail off; the button is the door

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Robin Fernandes
2026-09-15 22:44:33 +10:00
committed by kshitij
parent 16def7b8cc
commit 2e3b8ecc82
5 changed files with 35 additions and 22 deletions
+2 -1
View File
@@ -264,7 +264,8 @@ def nous_rate_limit_guard(
"failed": True,
"error": _nous_msg,
# The free tier's card body and its sign-in door (agent/error_surface.py).
**({"free_tier": {"kind": "rate_limited", "message": _nous_msg}} if _welcome else {}),
**({"free_tier": {"kind": "rate_limited", "message": anon_auth.FREE_TIER_RATE_LIMIT_CARD.format(
reset=anon_auth.friendly_wait(_nous_remaining))}} if _welcome else {}),
}, FailoverReason.rate_limit.value, True))
except Exception:
pass # Never let rate guard break the agent loop
+9 -5
View File
@@ -706,7 +706,7 @@ def _print_nonretryable_auth_guidance(
_vlines(agent, " • Check credits: https://openrouter.ai/settings/credits")
def _welcome_tier_guidance(classified: Any, *, model: Any, in_chat: bool) -> str:
def _welcome_tier_guidance(classified: Any, *, model: Any, in_chat: bool, door: bool = True) -> str:
"""Copy for a Nous free-tier refusal the classifier parsed (``welcome_refusal`` /
``welcome_route`` in ``error_context``); empty for every other error."""
ctx = getattr(classified, "error_context", None) or {}
@@ -715,8 +715,8 @@ def _welcome_tier_guidance(classified: Any, *, model: Any, in_chat: bool) -> str
return ""
from hermes_cli.anon_auth import welcome_refusal_copy, welcome_route_refusal_copy
if refusal:
return welcome_refusal_copy(refusal, model=str(model or ""), in_chat=in_chat)
return welcome_route_refusal_copy(str(route), in_chat=in_chat)
return welcome_refusal_copy(refusal, model=str(model or ""), in_chat=in_chat, door=door)
return welcome_route_refusal_copy(str(route), in_chat=in_chat, door=door)
def _welcome_surface_kind(classified: Any) -> str:
@@ -881,7 +881,9 @@ def nonretryable_client_error_result(
"failure_retryable": bool(classified.retryable),
})
if _welcome_hint:
_stamp_free_tier(result, _welcome_surface_kind(classified), _final_response)
# The card form: the desktop renders the sign-in as a button, so no "To sign in" tail.
_stamp_free_tier(result, _welcome_surface_kind(classified),
_welcome_tier_guidance(classified, model=model, in_chat=True, door=False))
return result
@@ -1011,7 +1013,9 @@ def max_retries_exhausted_result(
"billing_block": _billing_block,
})
if _free_tier_kind:
_stamp_free_tier(result, _free_tier_kind, _final_response)
_stamp_free_tier(result, _free_tier_kind, (
_welcome_tier_guidance(classified, model=model, in_chat=True, door=False)
if _welcome_hint else _final_response))
return result
+14 -11
View File
@@ -675,11 +675,13 @@ def parse_welcome_refusal(body: Any) -> Optional[Dict[str, Any]]:
"upgrade_url": upgrade_url if isinstance(upgrade_url, str) else ""}
def welcome_refusal_copy(refusal: Dict[str, Any], *, model: str = "", in_chat: bool = True) -> str:
def welcome_refusal_copy(refusal: Dict[str, Any], *, model: str = "", in_chat: bool = True, door: bool = True) -> str:
"""User copy for a structured welcome-tier refusal: what happened and the one way forward.
Never guest / anonymous / claim; ``in_chat`` picks ``/login`` over the terminal verb."""
signin = _SIGNIN_CHAT if in_chat else _SIGNIN_TERMINAL
Never guest / anonymous / claim; ``in_chat`` picks ``/login`` over the terminal verb.
``door=False`` leaves the "To sign in: …" tail off, for a surface that renders the sign-in as
a button beside the sentence (the desktop's error card)."""
signin = (_SIGNIN_CHAT if in_chat else _SIGNIN_TERMINAL) if door else ""
reason = str(refusal.get("reason") or "")
alternates = refusal.get("alternates") or []
serves = alternates[0] if alternates else GUEST_MODEL
@@ -690,17 +692,17 @@ def welcome_refusal_copy(refusal: Dict[str, Any], *, model: str = "", in_chat: b
return (f"{what} available without signing in, so Hermes uses {serves} for now. "
f"Sign in for more models. {signin}")
if reason == "feature_not_free":
return f"That isn't available without signing in. Sign in to use it, it's free. {signin}"
return f"That isn't available without signing in. Sign in to use it, it's free. {signin}".rstrip()
if reason == "at_capacity":
return ("Chatting without signing in is really busy right now. Sign in to skip the queue, "
f"it's free, or try again in {wait}. {signin}")
f"it's free, or try again in {wait}. {signin}").rstrip()
if reason == "admission_closed":
return ("Chatting without signing in is full right now. Sign in to keep going, "
f"it's free, or try again in {wait}. {signin}")
f"it's free, or try again in {wait}. {signin}").rstrip()
if reason == "rate_limited":
return (f"You've used up the allowance for chatting without signing in. It refreshes in {wait}. "
f"Sign in for a bigger allowance, it's free. {signin}")
return f"Hermes couldn't send that without signing in. Signing in is free. {signin}"
f"Sign in for a bigger allowance, it's free. {signin}").rstrip()
return f"Hermes couldn't send that without signing in. Signing in is free. {signin}".rstrip()
def welcome_route_refusal(status: Any, message: Any, base_url: Any = None) -> Optional[str]:
@@ -723,11 +725,11 @@ def welcome_route_refusal(status: Any, message: Any, base_url: Any = None) -> Op
return kind
def welcome_route_refusal_copy(kind: str, *, in_chat: bool = True) -> str:
def welcome_route_refusal_copy(kind: str, *, in_chat: bool = True, door: bool = True) -> str:
template = _WELCOME_ROUTE_COPY.get(kind) or "Hermes couldn't reach the free model on this route."
return template.format(
host=DEFAULT_NOUS_WELCOME_URL, signin=_SIGNIN_CHAT if in_chat else _SIGNIN_TERMINAL,
model_hint=_MODEL_HINT_CHAT if in_chat else _MODEL_HINT_TERMINAL)
host=DEFAULT_NOUS_WELCOME_URL, signin=(_SIGNIN_CHAT if in_chat else _SIGNIN_TERMINAL) if door else "",
model_hint=_MODEL_HINT_CHAT if in_chat else _MODEL_HINT_TERMINAL).rstrip()
def note_model_switch(agent: Any, headers: Any) -> Optional[str]:
@@ -1034,6 +1036,7 @@ from hermes_cli.anon_sign_in import ( # noqa: E402
Code as Code,
Completed as Completed,
Declined as Declined,
FREE_TIER_RATE_LIMIT_CARD as FREE_TIER_RATE_LIMIT_CARD,
FREE_TIER_RATE_LIMIT_CHAT as FREE_TIER_RATE_LIMIT_CHAT,
Failed as Failed,
LOGIN_BUSY_ELSEWHERE as LOGIN_BUSY_ELSEWHERE,
+4 -2
View File
@@ -47,9 +47,11 @@ LOGIN_STARTING = "Starting sign-in..."
LOGIN_DM_ONLY = "Sign in from a direct message with Hermes."
LOGIN_BUSY_ELSEWHERE = "Another sign-in is already running on this Hermes. Try again in a few minutes."
LOGIN_NOT_ALLOWED = "Only an operator of this Hermes can sign it in."
FREE_TIER_RATE_LIMIT_CHAT = (
# The card form (a surface with its own sign-in button) and the chat form (names /login).
FREE_TIER_RATE_LIMIT_CARD = (
"You've used up the allowance for chatting without signing in. It refreshes in {reset}. "
"Sign in for a bigger allowance, it's free: /login.")
"Sign in for a bigger allowance, it's free.")
FREE_TIER_RATE_LIMIT_CHAT = FREE_TIER_RATE_LIMIT_CARD[:-1] + ": /login."
def format_wait_line(expires_in: int) -> str:
+6 -3
View File
@@ -181,8 +181,11 @@ class TestTerminalResultsCarryTheFreeTierBlock:
self._terminal_agent(), err, classified, status_code=403, api_kwargs=None, api_messages=[],
messages=[], conversation_history=[], api_call_count=1, approx_tokens=10,
provider="nous", base_url=WELCOME, model="nous/welcome")
assert result["free_tier"] == {"kind": "disabled", "message": result["final_response"]}
# The chat text names /login; the card text (a button beside it) leaves that tail off.
assert "switched off" in result["final_response"] and "/login" in result["final_response"]
assert result["free_tier"]["kind"] == "disabled"
assert result["free_tier"]["message"] == result["final_response"].replace(" To sign in: /login.", "")
assert "/login" not in result["free_tier"]["message"]
assert result["error"] == "HTTP 403: no permissions" # the technical detail stays in the log line
def test_an_exhausted_capacity_refusal_is_stamped_at_capacity(self):
@@ -194,8 +197,8 @@ class TestTerminalResultsCarryTheFreeTierBlock:
api_kwargs=None, api_messages=[], messages=[], conversation_history=[], api_call_count=3,
approx_tokens=10, provider="nous", base_url=WELCOME, model="nous/welcome")
assert result["free_tier"]["kind"] == "at_capacity"
assert result["free_tier"]["message"] == result["final_response"]
assert "really busy" in result["final_response"]
assert "really busy" in result["free_tier"]["message"] and "/login" not in result["free_tier"]["message"]
assert "/login" in result["final_response"]
def test_a_spent_outage_on_the_welcome_host_is_stamped_outage(self):
from agent.turn_recovery import max_retries_exhausted_result