Владелец прислал анонс x.ai/news/grok-hermes: доступ к Grok даётся по подписке SuperGrok через OAuth, покупать кредиты API не требуется. Моя формулировка в интерфейсе утверждала обратное — что подписка кредиты не пополняет и это отдельный кошелёк. Это неверно, и оно уже попадало пользователю на экран. Установлено проверками: - cli-chat-proxy.grok.com/v1/models принимает наш OAuth-токен -> 200, отдаёт grok-4.6; - тот же прокси /chat/completions -> 426, требует версию Grok CLI не ниже 0.1.202, и она читается не из заголовков, которые я перебрал; - api.x.ai/v1/responses (путь из документации Hermes) -> 402 personal-team-blocked:spending-limit. При этом подключён ochenstarik@gmail.com, а подписка SuperGrok — на victor.trushenko@gmail.com. То есть отказ объясняется аккаунтом, а не природой подписки, и утверждать иное я не мог. Сообщение переписано на проверяемое: у аккаунта нет ни баланса, ни лимита трат, а доступ даёт подписка на ЭТОМ же аккаунте либо купленные кредиты. Само чтение биллинга остаётся — оно работает и показывает настоящие числа. Тесты: 422 passed, ruff чисто. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
3.4 KiB
Python
73 lines
3.4 KiB
Python
"""Квота Grok читается у провайдера, а не показывается как «Н/Д».
|
||
|
||
Владелец спросил: «где закончились лимиты?» — и показал экран, где
|
||
еженедельный лимит SuperGrok израсходован на 14%. Но вызовы через Hub
|
||
падали с 402.
|
||
|
||
Разгадка: это ДВА РАЗНЫХ кошелька. Подписка SuperGrok покрывает чат
|
||
grok.com, а программный доступ списывается с кредитов API. Их у аккаунта
|
||
ноль — подтверждено биллинговым эндпоинтом.
|
||
|
||
Раньше _collect_grok_quota строила пустые корзины и никуда не ходила,
|
||
поэтому Hub показывал «Н/Д» и объяснить ничего не мог.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
from antigravity_provider.router.quota_collector import AccountQuotaService
|
||
|
||
|
||
def _billing(prepaid: float, cap: float, used: float) -> MagicMock:
|
||
payload = {
|
||
"config": {
|
||
"currentPeriod": {"start": "2026-08-19T00:00:00+00:00", "end": "2026-08-26T00:00:00+00:00"},
|
||
"prepaidBalance": {"val": prepaid},
|
||
"onDemandCap": {"val": cap},
|
||
"onDemandUsed": {"val": used},
|
||
}
|
||
}
|
||
resp = MagicMock()
|
||
resp.__enter__.return_value.read.return_value = json.dumps(payload).encode("utf-8")
|
||
return resp
|
||
|
||
|
||
def _auth() -> dict:
|
||
return {"token": {"access_token": "tok"}}
|
||
|
||
|
||
def test_exhausted_account_explains_two_wallets():
|
||
"""Нулевой баланс объясняется, а не показывается прочерком."""
|
||
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
|
||
return_value=_billing(0, 0, 0)):
|
||
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
|
||
|
||
assert snap.source == "provider_api"
|
||
assert "SuperGrok" in (snap.unavailable_reason or ""), "не объяснено, откуда берётся доступ"
|
||
assert all(b.status == "exhausted" for b in snap.buckets)
|
||
|
||
|
||
def test_zero_cap_does_not_fabricate_percent():
|
||
"""При нулевом лимите доля не определена — процент выдумывать нельзя."""
|
||
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
|
||
return_value=_billing(0, 0, 0)):
|
||
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
|
||
|
||
on_demand = next(b for b in snap.buckets if b.id == "grok.on_demand")
|
||
assert on_demand.remaining_percent is None
|
||
assert on_demand.used_percent is None
|
||
assert on_demand.limit_absolute == 0
|
||
|
||
|
||
def test_account_with_credits_reports_real_percent():
|
||
"""Когда лимит есть, доля считается по измеренным значениям."""
|
||
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
|
||
return_value=_billing(50, 200, 50)):
|
||
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
|
||
|
||
on_demand = next(b for b in snap.buckets if b.id == "grok.on_demand")
|
||
assert on_demand.used_percent == 25.0
|
||
assert on_demand.remaining_percent == 75.0
|
||
assert snap.unavailable_reason is None
|