Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""Tests for the Anthropic-subscription branch of
|
||||
``agent.conversation_loop._billing_or_entitlement_message``.
|
||||
|
||||
Regression context: Anthropic Claude Pro/Max OAuth subscriptions surface
|
||||
exhaustion of the metered "extra usage" bucket as a hard HTTP 400
|
||||
("You're out of extra usage. Add more at claude.ai/settings/usage..."),
|
||||
which classifies as ``FailoverReason.billing``. The generic billing
|
||||
guidance ("add credits with that provider") is wrong for a subscription —
|
||||
the user waits for the cycle reset or switches to an API key. This branch
|
||||
gives Anthropic-specific, actionable guidance (folds in PR #40073's UX).
|
||||
|
||||
#82154 adds the ``unverified`` axis: the same 400 body is also returned when
|
||||
Anthropic's server-side content filter rejects part of the request, so an
|
||||
unverified billing verdict must hedge and name the other cause, while a
|
||||
confirmed verdict keeps the assertive wording.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent.conversation_loop import _billing_or_entitlement_message
|
||||
|
||||
|
||||
def test_anthropic_subscription_exhausted_guidance():
|
||||
"""Anthropic billing guidance points at the exact settings page and
|
||||
the cycle-reset option, not the generic 'add credits' line."""
|
||||
msg = _billing_or_entitlement_message(
|
||||
capability="model access",
|
||||
provider="anthropic",
|
||||
base_url="https://api.anthropic.com",
|
||||
model="claude-opus-4-7",
|
||||
)
|
||||
assert "claude.ai/settings/usage" in msg
|
||||
# Must mention the subscription cycle reset (not generic 'add credits').
|
||||
assert "reset" in msg.lower()
|
||||
# Must still offer the provider-switch escape hatch.
|
||||
assert "/model" in msg
|
||||
# Model name should be interpolated.
|
||||
assert "claude-opus-4-7" in msg
|
||||
|
||||
|
||||
def test_non_anthropic_billing_guidance_unaffected():
|
||||
"""A non-Anthropic provider keeps the generic billing guidance and does
|
||||
NOT get the Anthropic-specific claude.ai settings link."""
|
||||
msg = _billing_or_entitlement_message(
|
||||
capability="model access",
|
||||
provider="openrouter",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model="anthropic/claude-opus-4.7",
|
||||
)
|
||||
assert "claude.ai/settings/usage" not in msg
|
||||
# Generic path still surfaces the OpenRouter credits link.
|
||||
assert "openrouter.ai/settings/credits" in msg
|
||||
|
||||
|
||||
# ── #82154: an UNVERIFIED billing 400 is not proof of a billing problem ──────
|
||||
# Anthropic returns the same "out of extra usage" body when its server-side
|
||||
# content filter rejects part of the request on a subscription OAuth token.
|
||||
# Asserting exhaustion outright cost one reporter three debugging sessions and
|
||||
# sent them at the billing page. When the classifier marks the verdict
|
||||
# unverified, the guidance must hedge and name the other cause.
|
||||
|
||||
|
||||
def _anthropic_msg(*, unverified: bool) -> str:
|
||||
return _billing_or_entitlement_message(
|
||||
capability="model access",
|
||||
provider="anthropic",
|
||||
base_url="https://api.anthropic.com",
|
||||
model="claude-opus-5",
|
||||
unverified=unverified,
|
||||
)
|
||||
|
||||
|
||||
def test_unverified_guidance_names_the_content_filter_alternative():
|
||||
msg = _anthropic_msg(unverified=True).lower()
|
||||
assert "content filter" in msg
|
||||
# Must give the operator a way to tell the two apart, not just hedge.
|
||||
assert "still shows quota remaining" in msg
|
||||
assert "system prompt" in msg
|
||||
|
||||
|
||||
def test_unverified_guidance_does_not_assert_exhaustion_as_fact():
|
||||
"""The opening line must hedge. 'is exhausted' is the claim that misdirected
|
||||
diagnosis; 'may be exhausted' keeps the billing lead without asserting it."""
|
||||
first_line = _anthropic_msg(unverified=True).splitlines()[0].lower()
|
||||
assert "may be exhausted" in first_line
|
||||
assert "is exhausted" not in first_line
|
||||
|
||||
|
||||
def test_unverified_guidance_warns_about_the_cached_exhaustion_replay():
|
||||
"""After a failure the credential is latched exhausted and the stored error
|
||||
is replayed without issuing a request — so a real fix looks like it didn't
|
||||
work. Point at the reset before the user concludes that."""
|
||||
msg = _anthropic_msg(unverified=True)
|
||||
assert "hermes auth reset anthropic" in msg
|
||||
assert "without contacting the API" in msg
|
||||
|
||||
|
||||
def test_unverified_guidance_keeps_the_billing_remedies():
|
||||
"""The caveats are additive — the billing remedies stay available."""
|
||||
msg = _anthropic_msg(unverified=True)
|
||||
assert "https://claude.ai/settings/usage" in msg
|
||||
assert "reset" in msg.lower()
|
||||
assert "/model" in msg
|
||||
assert "claude-opus-5" in msg
|
||||
|
||||
|
||||
def test_confirmed_guidance_stays_assertive_without_the_caveat():
|
||||
"""A CONFIRMED billing verdict (e.g. a real 402) must not be diluted by
|
||||
content-filter lore that only applies to the ambiguous 400 body."""
|
||||
msg = _anthropic_msg(unverified=False)
|
||||
first_line = msg.splitlines()[0].lower()
|
||||
assert "is exhausted" in first_line
|
||||
assert "may be exhausted" not in first_line
|
||||
lowered = msg.lower()
|
||||
assert "content filter" not in lowered
|
||||
assert "hermes auth reset" not in lowered
|
||||
|
||||
|
||||
def test_content_filter_caveat_is_anthropic_only():
|
||||
"""A generic provider must not inherit Anthropic-specific classifier lore,
|
||||
even when the verdict is marked unverified."""
|
||||
msg = _billing_or_entitlement_message(
|
||||
capability="model access",
|
||||
provider="openrouter",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model="anthropic/claude-opus-4.7",
|
||||
unverified=True,
|
||||
).lower()
|
||||
assert "content filter" not in msg
|
||||
assert "hermes auth reset" not in msg
|
||||
Reference in New Issue
Block a user