@wendellresearch-ai/benchwarmers 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,13 +12,22 @@ A tile takes the shape of what its provider says:
12
12
  rate-limit headers the built-in client saw (metrics/quota.py)
13
13
  pool one allowance the models share: OpenRouter's free requests a
14
14
  day, a bar per key, counted from the usage ledger
15
- models nothing stated: each bench model's last day, answered or not
15
+ models nothing stated: the provider's last day of calls, all together
16
16
 
17
- REFRESH NEVER SPENDS QUOTA. Two providers can be asked what is left for free,
18
- and are, when Refresh is pressed:
17
+ and any of them its day and month caps (caps): yours, else a limit its replies
18
+ state with nothing left said, counted from the ledger. A model is named only
19
+ where a limit is its own. Nothing comes from a provider's docs: they change
20
+ unseen.
21
+
22
+ ONE REFRESH, THREE WAYS (how_refreshed). Three providers can be asked what is
23
+ left for free, and are:
19
24
 
20
25
  OpenRouter GET /key on a direct OpenRouter endpoint: free tier or
21
- not (50 or 1,000 free requests a day), and its credits
26
+ not, and its credits (the free requests' limit comes
27
+ from its 429s, never its docs)
28
+ xKiro GET /v1/usage: free-model tokens left today (to midnight
29
+ UTC) and a plan's spend windows; the key is a direct
30
+ endpoint's, else its Open WebUI connection's (_key_for)
22
31
  CLI Proxy API its management route `requests/api-call`, which calls the
23
32
  credential's own quota endpoint with the credential's
24
33
  token, as its console does (Google's retrieveUserQuotaSummary,
@@ -27,10 +36,10 @@ and are, when Refresh is pressed:
27
36
  (endpoints.admin_key, as metrics/cliproxy.py); the proxy is
28
37
  on the LAN and calls only providers the credential uses.
29
38
 
30
- Every other provider's Refresh re-reads the bench's own records. `check`
31
- is the one that asks: a 16-token call to one of its models, whose reply
32
- headers quota.py records. Offered only where the provider has sent limit
33
- headers before, since elsewhere the reply would say nothing.
39
+ Where a provider has sent limit headers before (Groq; Routeway, through Open
40
+ WebUI too, since the bench streams), Refresh is `check`: a 16-token call to
41
+ one of its models, whose reply headers quota.py records. Everywhere else it
42
+ re-reads the bench's own records, since a call would say nothing.
34
43
 
35
44
  Live reads are kept in runs/quota-live.json, newest per tag.
36
45
  """
@@ -65,6 +74,23 @@ CODEX_URL = "https://chatgpt.com/backend-api/wham/usage"
65
74
  CODEX_HEAD = {"Authorization": "Bearer $TOKEN$", "Content-Type": "application/json",
66
75
  "User-Agent": "codex-tui/0.149.1"}
67
76
  WINDOW_NAMES = {"5h": "5 hours", "weekly": "week", "daily": "day", "monthly": "month"}
77
+ # Free to ask: no tokens, outside the rate limits (docs.xkiro.com/api/usage, read 2026-10-29).
78
+ XKIRO_HOST = "api.xkiro.com"
79
+ XKIRO_USAGE = "https://api.xkiro.com/v1/usage"
80
+ # A key may read its own usage with completions permission alone; the org's
81
+ # needs manage (docs.requesty.ai/features/key-management-api, tried 2026-10-30).
82
+ REQUESTY_HOST = "router.requesty.ai"
83
+ REQUESTY_KEYS = "https://api-v2.requesty.ai/v1/manage/apikey"
84
+ REQUESTY_MODELS = "https://router.requesty.ai/v1/models" # public; a free model is priced at zero
85
+ # The one figure from a provider's published text, not its replies: the file
86
+ # build.nvidia.com reads for its "Your API Rate Limit" badge, the same for
87
+ # everyone. Watched (watch_nvidia): a change or a failure to read it is an alert.
88
+ NVIDIA_COPY = "https://build.nvidia.com/api/runtime/content/copy.yaml"
89
+ # A day or month cap is drawn counted from the ledger, as pacing.py counts:
90
+ # yours from Endpoints (enforced), else one a provider states in its replies
91
+ # without saying what is left (Cohere's x-endpoint-monthly-call-limit; shown,
92
+ # never enforced). Never one from a provider's docs: they change unseen.
93
+ COUNTED = {"rpd": ("requests", "day"), "tpd": ("tokens", "day"), "rpmo": ("requests", "month")}
68
94
 
69
95
 
70
96
  def _iso(t: float | None) -> str | None:
@@ -118,6 +144,24 @@ def codex_groups(body: dict) -> list[dict]:
118
144
  return [{"label": "", "windows": wins}] if wins else []
119
145
 
120
146
 
147
+ def xkiro_groups(body: dict, now: float) -> list[dict]:
148
+ """/v1/usage -> the free models' tokens today (to midnight UTC), then the
149
+ plan's spend windows (USD, as strings), where there is a plan."""
150
+ out, ft = [], body.get("free_tokens") or {}
151
+ if ft.get("limit_per_day"):
152
+ lim, rem = int(ft["limit_per_day"]), int(ft.get("remaining") or 0)
153
+ out.append({"label": "free models", "windows": [
154
+ {"label": "tokens · day", "left": round(100 * rem / lim), "remaining": rem, "of": lim,
155
+ "reset_at": _iso(_utc_midnight(now) + 86400)}]})
156
+ wins = [{"label": _seconds_name(w.get("window_sec")) + " · spend",
157
+ "left": round(100 * float(w.get("remaining_usd") or 0) / float(w["cap_usd"])),
158
+ "reset_at": _iso(now + float(w.get("resets_in_sec") or 0))}
159
+ for w in body.get("windows") or [] if isinstance(w, dict) and float(w.get("cap_usd") or 0) > 0]
160
+ if wins:
161
+ out.append({"label": "plan", "windows": wins})
162
+ return out
163
+
164
+
121
165
  # ---- live reads --------------------------------------------------------------
122
166
 
123
167
  def _get_json(url: str, headers: dict, data: bytes | None = None, timeout: int = 20) -> dict:
@@ -147,6 +191,106 @@ def read_openrouter() -> dict:
147
191
  "limit": data.get("limit"), "remaining": data.get("limit_remaining"), "usage": data.get("usage")}
148
192
 
149
193
 
194
+ def _key_for(host: str) -> str:
195
+ """A key for `host`: a direct endpoint's, else the Open WebUI connection's
196
+ (read with the hub's admin key, as owui.py reads its connections)."""
197
+ import endpoints
198
+ for name, entry in endpoints.load().items():
199
+ if (urllib.parse.urlsplit(entry.get("base", "")).hostname or "") == host and not endpoints.hub(name):
200
+ if endpoints.key(name):
201
+ return endpoints.key(name)
202
+ import owui
203
+ cfg = owui._call("/openai/config")
204
+ for url, key in zip(cfg.get("OPENAI_API_BASE_URLS") or [], cfg.get("OPENAI_API_KEYS") or []):
205
+ if (urllib.parse.urlsplit(url).hostname or "") == host and key:
206
+ return key
207
+ raise ValueError(f"no key for {host}: neither an endpoint nor an Open WebUI connection has one")
208
+
209
+
210
+ def read_xkiro() -> dict:
211
+ """Only the figures are kept: the reply also names the account's owner."""
212
+ try: # Cloudflare refuses Python's own User-Agent (1010)
213
+ body = _get_json(XKIRO_USAGE, {"Authorization": f"Bearer {_key_for(XKIRO_HOST)}", "User-Agent": "benchwarmers"})
214
+ except urllib.error.HTTPError as exc:
215
+ raise ValueError(f"xKiro answered {exc.code} on /v1/usage") from None
216
+ return {"groups": xkiro_groups(body, time.time()), "plan": str(body.get("plan") or ""), "notes": []}
217
+
218
+
219
+ def requesty_free(usage: dict, models: dict) -> int:
220
+ """Requests to free models in a usage reply's (first) period. Its
221
+ model_used drops the provider (`muse-glimmer-30b` for nvidia/muse-glimmer-30b)."""
222
+ free = {str(m.get("id", "")).split("/")[-1] for m in models.get("data") or []
223
+ if not float(m.get("input_price") or 0) and not float(m.get("output_price") or 0)}
224
+ day = next(iter((usage.get("usage") or {}).values()), {})
225
+ return sum(r.get("total_requests") or 0 for r in day.get("grouped_data") or []
226
+ if str((r.get("group_by_values") or {}).get("model_used", "")).split("/")[-1] in free)
227
+
228
+
229
+ def read_requesty() -> dict:
230
+ """Requesty's own count of this key's free-model requests since midnight
231
+ UTC (its usage API, whose periods are UTC dates). Its free limit is not
232
+ reported anywhere: only a 429 that states one gives the pool its `of`."""
233
+ head = {"Authorization": f"Bearer {_key_for(REQUESTY_HOST)}", "User-Agent": "benchwarmers"}
234
+ try:
235
+ me = _get_json(REQUESTY_KEYS + "/self", head)
236
+ start = dt.datetime.fromtimestamp(_utc_midnight(time.time()), UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
237
+ usage = _get_json(f"{REQUESTY_KEYS}/{me['id']}/usage?start={start}&resolution=day&group_by=model_used", head)
238
+ models = _get_json(REQUESTY_MODELS, {"User-Agent": "benchwarmers"})
239
+ except urllib.error.HTTPError as exc:
240
+ raise ValueError(f"Requesty answered {exc.code} on its usage API") from None
241
+ return {"free_used": requesty_free(usage, models), "notes": []}
242
+
243
+
244
+ def nvidia_figures(body: dict) -> dict:
245
+ """copy.yaml (JSON in fact) -> {rpd, rpm_text}; ValueError when its
246
+ rateLimits or a day's number is missing: the file changed shape."""
247
+ import re
248
+ rl = body.get("rateLimits") if isinstance(body, dict) else None
249
+ if not isinstance(rl, dict):
250
+ raise ValueError("it no longer has rateLimits")
251
+ m = re.search(r"([\d,]+)\s+requests?\s+(?:per|a)\s+day", str(rl.get("requestsPerDay") or ""))
252
+ if not m:
253
+ raise ValueError(f"its requestsPerDay no longer says a number a day: {str(rl.get('requestsPerDay'))[:60]!r}")
254
+ return {"rpd": int(m.group(1).replace(",", "")), "rpm_text": str(rl.get("requestsPerMinute") or "")}
255
+
256
+
257
+ def _nvidia_on_bench() -> bool:
258
+ return any("nvidia" in (t + " " + _host(t)).lower() for t in _tags())
259
+
260
+
261
+ def watch_nvidia() -> dict:
262
+ """Read NVIDIA's stated figures and keep the last good read (LIVE
263
+ "_nvidia"). A change raises an alert you dismiss; a failure (moved,
264
+ broken, a new shape) raises one that clears once it reads again. Run
265
+ daily by the catalog job and by the tile's Refresh; nothing without an
266
+ NVIDIA connection on the bench."""
267
+ import alerts
268
+ prev = locked.read_json(LIVE).get("_nvidia") or {}
269
+ if not _nvidia_on_bench():
270
+ return prev
271
+ try:
272
+ try:
273
+ body = _get_json(NVIDIA_COPY, {"User-Agent": "benchwarmers"})
274
+ except urllib.error.HTTPError as exc:
275
+ raise ValueError(f"build.nvidia.com answered {exc.code}") from None
276
+ got = dict(nvidia_figures(body), at=_iso(time.time()))
277
+ except (ValueError, OSError) as exc:
278
+ since = f" (last read {prev['at'][:10]})" if prev.get("at") else ""
279
+ alerts.raise_alert("limits:nvidia:unread", "limits", f"NVIDIA's limits file could not be read: {exc}",
280
+ action=f"It may have moved from {NVIDIA_COPY}. The NVIDIA tile keeps the figures"
281
+ f" it read before{since}.")
282
+ locked.update(LIVE, lambda d: d.setdefault("_nvidia", {}).update(broken=str(exc)), ensure_ascii=False)
283
+ return dict(prev, broken=str(exc))
284
+ alerts.clear("limits:nvidia:unread")
285
+ if prev.get("rpd") and (prev["rpd"], prev.get("rpm_text")) != (got["rpd"], got["rpm_text"]):
286
+ alerts.raise_alert("limits:nvidia:changed", "limits",
287
+ f"NVIDIA changed its stated limits: {prev['rpd']:,} a day, {prev.get('rpm_text')!r}"
288
+ f" -> {got['rpd']:,} a day, {got['rpm_text']!r}",
289
+ action="The NVIDIA tile counts against the new figures.", dismiss=True)
290
+ locked.update(LIVE, lambda d: d.update({"_nvidia": got}), ensure_ascii=False)
291
+ return got
292
+
293
+
150
294
  def _cpa_hub() -> tuple[str, str]:
151
295
  """(management base, key) of the CLI Proxy API hub; ValueError when there is none."""
152
296
  import endpoints
@@ -235,21 +379,72 @@ def read_cliproxy(tag: str, sibling_vendors: set[str]) -> dict:
235
379
 
236
380
 
237
381
  def refresh(tag: str) -> dict:
238
- """Ask the provider when it can be asked for free, keep the answer;
239
- {"ok", "how": "live"|"records", "note"}."""
240
- if tag == "openrouter" and _openrouter_endpoint():
382
+ """The tile's one Refresh, which picks its own way (how_refreshed): ask the
383
+ provider when it can be asked for free; else, where it states limits in its
384
+ replies, one 16-token call (check); else the bench's own records.
385
+ {"ok", "how": "live"|"check"|"records", "note"}."""
386
+ how = how_refreshed(tag)
387
+ if "nvidia" in (tag + " " + _host(tag)).lower():
388
+ got = watch_nvidia()
389
+ return {"ok": True, "how": "live", "note": f"NVIDIA's limits file could not be read: {got['broken']};"
390
+ " showing the figures read before" if got.get("broken") else ""}
391
+ if how == "check":
392
+ r = check(tag)
393
+ if r.get("ok"):
394
+ return dict(r, how="check")
395
+ return {"ok": True, "how": "records", "note": "; ".join(r.get("errors") or []) + "; shown from the bench's own records"}
396
+ if how == "records":
397
+ return {"ok": True, "how": "records",
398
+ "note": "this provider states no limits to ask about: shown from the bench's own records"}
399
+ if tag == "openrouter":
241
400
  got = read_openrouter()
242
- elif tag.split("/")[0] == "cli-proxy-api":
401
+ elif _host(tag) == XKIRO_HOST:
402
+ got = read_xkiro()
403
+ elif _host(tag) == REQUESTY_HOST:
404
+ got = read_requesty()
405
+ else:
243
406
  vendors = {t.split("/", 1)[1] for t in _tags() if t.startswith("cli-proxy-api/")}
244
407
  got = read_cliproxy(tag, vendors)
245
- else:
246
- return {"ok": True, "how": "records",
247
- "note": "this provider has no free quota read: shown from the bench's own records"}
248
408
  got["at"] = _iso(time.time())
249
409
  locked.update(LIVE, lambda d: d.update({tag: got}), ensure_ascii=False)
250
410
  return {"ok": True, "how": "live", "note": "; ".join(got.get("notes") or [])}
251
411
 
252
412
 
413
+ def about(tag: str) -> dict:
414
+ """How a tile's provider is reached, for its Models popup: {connection,
415
+ routes: [{via, url}]}, a direct endpoint by that name and each Open WebUI
416
+ connection named so (scripts/provider_tags.py's names). URLs only:
417
+ /openai/config returns Open WebUI's keys too, never read here."""
418
+ import endpoints
419
+ host, routes = tag.split("/")[0], []
420
+ if host in endpoints.load() and not endpoints.hub(host):
421
+ routes.append({"via": f"its own endpoint, {host}", "url": endpoints.base(host)})
422
+ hub = endpoints.find_hub("openwebui")
423
+ if hub:
424
+ sys.path.insert(0, str(ROOT / "scripts"))
425
+ import provider_tags
426
+ urls = provider_tags.get(hub, "/openai/config").get("OPENAI_API_BASE_URLS", [])
427
+ routes += [{"via": f"an Open WebUI connection on {hub}", "url": url}
428
+ for name, url in zip(provider_tags.connection_names(hub), urls) if name == host]
429
+ return {"connection": tag, "routes": routes}
430
+
431
+
432
+ def _host(tag: str) -> str:
433
+ """The connection a tile's models are called through (route.connection_of)."""
434
+ import route
435
+ aliases = _tags().get(tag) or []
436
+ return route.connection_of(aliases[0]) if aliases else tag
437
+
438
+
439
+ def how_refreshed(tag: str) -> str:
440
+ """"live" (a free read), "check" (one short call whose headers say what is
441
+ left) or "records" (nothing to ask: the bench's own records)."""
442
+ if (tag.split("/")[0] == "cli-proxy-api" or tag == "openrouter" and _openrouter_endpoint()
443
+ or _host(tag) in (XKIRO_HOST, REQUESTY_HOST) or "nvidia" in (tag + " " + _host(tag)).lower()):
444
+ return "live"
445
+ return "check" if _headers_seen(tag) else "records"
446
+
447
+
253
448
  def check(tag: str) -> dict:
254
449
  """One 16-token call to a model on `tag` that is free to call; the
255
450
  built-in client records what its reply headers say (quota.observe)."""
@@ -312,10 +507,41 @@ def _model_rows(aliases, rows, resting, limits) -> list[dict]:
312
507
 
313
508
 
314
509
  def _limit_text(lim: dict) -> str:
315
- words = {"rpm": "requests a minute", "rpd": "requests a day", "tpm": "tokens a minute", "tpd": "tokens a day"}
510
+ words = {"rpm": "requests a minute", "rpd": "requests a day", "tpm": "tokens a minute", "tpd": "tokens a day",
511
+ "rpmo": "requests a calendar month"}
316
512
  return ", ".join(f"{v:,} {words[k]}" for k, v in lim.items() if k in words and v)
317
513
 
318
514
 
515
+ def caps(host: str, entered: dict, stated: dict, day_rows: list, month_rows, now: float) -> list[dict]:
516
+ """The connection's day and month caps as groups of windows, each counted
517
+ from the bench's ledger since midnight UTC or the month's start (what a
518
+ provider's calendar counts): yours, else `stated` ({axis: (limit, source,
519
+ detail)}: what its replies say with nothing left given, or NVIDIA's site).
520
+ `month_rows` is called only when a month cap needs it."""
521
+ mine = {k: (v, "your cap", "Set on Endpoints, Rate limits per connection; the bench keeps to it.")
522
+ for k, v in (entered.get(host) or {}).items() if k in COUNTED and v}
523
+ for axis, (n, source, detail) in stated.items():
524
+ mine.setdefault(axis, (n, source, detail + " Shown, not enforced: set it on Endpoints to have the bench"
525
+ " keep to it."))
526
+ by: dict[str, list] = {}
527
+ about: dict[str, str] = {}
528
+ for axis, (n, source, detail) in mine.items():
529
+ about[source] = detail
530
+ metric, window = COUNTED[axis]
531
+ start, end = ((_utc_midnight(now), _utc_midnight(now) + 86400) if window == "day"
532
+ else (__import__("ledger").month_start(now), __import__("ledger").next_month(now)))
533
+ rows = [r for r in (day_rows if window == "day" else month_rows())
534
+ if r.get("connection") == host and r["t"] >= start]
535
+ used = sum((r.get("attempts") or 0) if metric == "requests"
536
+ else (r.get("tokens_in") or 0) + (r.get("tokens_out") or 0) for r in rows)
537
+ left = max(0, n - used)
538
+ by.setdefault(source, []).append({"label": f"{metric} · {window}", "left": round(100 * left / n),
539
+ "remaining": left, "of": n, "reset_at": _iso(end)})
540
+ return [{"label": f"{source} · counted by the bench", "title": about[source] + " Counted from the bench's"
541
+ " own calls: calls made elsewhere with the same key are not in it.", "windows": w}
542
+ for source, w in by.items()]
543
+
544
+
319
545
  def tiles(now: float | None = None) -> list[dict]:
320
546
  """Every tile, in the saved order (out first, then soonest reset, for new ones)."""
321
547
  import catalog
@@ -330,6 +556,12 @@ def tiles(now: float | None = None) -> list[dict]:
330
556
  utc_rows = [r for r in day_rows if r["t"] >= _utc_midnight(now)]
331
557
  entered = catalog.connection_limits()
332
558
  regs = __import__("endpoints").registrations()
559
+ month: list = [] # this month's ledger, read once if a month cap asks
560
+
561
+ def month_rows() -> list:
562
+ if not month:
563
+ month.append(list(ledger.rows(ledger.month_start(now))))
564
+ return month[0]
333
565
  out = []
334
566
  for tag, aliases in tags.items():
335
567
  host = route.connection_of(aliases[0])
@@ -338,22 +570,48 @@ def tiles(now: float | None = None) -> list[dict]:
338
570
  for a in aliases}
339
571
  limits = {a: t for a, t in limits.items() if t}
340
572
  rows = [r for r in day_rows if r.get("alias") in mine]
573
+ # What the replies' headers said, kept a day past each reset (quota.py).
574
+ # A limit with nothing left said (Cohere's month) is a cap to count.
575
+ seen_recs = [r for a, by in (locked.read_json(quota.FILE).get(host) or {}).items()
576
+ if a in mine and isinstance(by, dict) for r in by.values()
577
+ if isinstance(r, dict) and r.get("limit") and r.get("window") != "retry-after"]
578
+ stated: dict[str, tuple] = {}
579
+ for r in seen_recs:
580
+ if r.get("remaining") is None and r["metric"] == "requests" and r.get("window") in ("day", "month"):
581
+ axis = "rpd" if r["window"] == "day" else "rpmo"
582
+ if r["limit"] > (stated.get(axis) or (0,))[0]:
583
+ stated[axis] = (r["limit"], "its replies", "The provider's replies state this limit but not"
584
+ " what is left.")
585
+ nv = (live.get("_nvidia") or {}) if "nvidia" in (host + " " + tag).lower() else {}
586
+ if nv.get("rpd"):
587
+ stated.setdefault("rpd", (nv["rpd"], "NVIDIA's site", "The figure build.nvidia.com shows every account"
588
+ f" (not this key's own), read {str(nv.get('at'))[:10]}."))
341
589
  t = {"id": tag, "tag": tag, "host": host, "bench": len(aliases), "kind": "models", "groups": [],
342
- "pool": [], "notes": [], "conn_limit": _limit_text(entered.get(host) or {}),
590
+ "pool": [], "notes": [], # a day or month cap is a meter (caps), a minute one is said
591
+ "conn_limit": _limit_text({k: v for k, v in (entered.get(host) or {}).items() if k in ("rpm", "tpm")}),
592
+ "caps": caps(host, entered, stated, day_rows, month_rows, now),
343
593
  "models": _model_rows(aliases, rows, resting, limits),
344
- "refresh": "records", "check": False, "plan": "", "name": ""}
594
+ "refresh": how_refreshed(tag), "plan": "", "name": ""}
345
595
  got = live.get(tag) or {}
346
- if tag.split("/")[0] == "cli-proxy-api":
347
- t["refresh"] = "live"
596
+ if tag.split("/")[0] == "cli-proxy-api" or host == XKIRO_HOST:
348
597
  if got.get("groups"):
349
598
  t.update(kind="groups", groups=got["groups"], name=got.get("name", ""), plan=got.get("plan", ""))
350
599
  elif tag == "openrouter":
351
- t["refresh"] = "live" if _openrouter_endpoint() else "records"
352
600
  t["kind"] = "pool"
353
601
  t["pool"] = _openrouter_pool(aliases, utc_rows, regs, got, learned, now)
354
- if t["kind"] == "models": # the headers' windows, when any is in force
355
- recs = [r for r in quota.live(host, now=now) if r.get("alias") in mine
356
- and r.get("window") != "retry-after" and r.get("limit")]
602
+ elif host == REQUESTY_HOST and "free_used" in got and (_ts(got.get("at")) or 0) >= _utc_midnight(now):
603
+ # Requesty's count; the limit only as a 429 stated it (learned).
604
+ of = min([v for v in (((learned.get(a) or {}).get("limits") or {}).get("rpd", {}).get("value")
605
+ for a in aliases) if v] or [0]) or None
606
+ used = got["free_used"]
607
+ t["kind"] = "pool"
608
+ t["pool"] = [{"label": "free requests today · this key", "used": used, "of": of,
609
+ "left": max(0, round(100 * (of - used) / of)) if of else None, "reset_at": None}]
610
+ if t["kind"] == "models": # the headers' windows, as last seen
611
+ # A window past its reset has filled again: its limit is known, all
612
+ # of it left, the next reset not.
613
+ recs = [r if (_ts(r.get("reset_at")) or 0) > now else dict(r, remaining=r["limit"], reset_at=None)
614
+ for r in seen_recs if r.get("remaining") is not None]
357
615
  by: dict[str, list] = {}
358
616
  for r in recs:
359
617
  by.setdefault(r["alias"], []).append(
@@ -366,26 +624,35 @@ def tiles(now: float | None = None) -> list[dict]:
366
624
  seen = [_ts(r.get("observed_at")) or 0 for r in recs]
367
625
  if seen:
368
626
  got = {"at": _iso(max(seen)), "how": "seen"}
369
- t["check"] = t["refresh"] == "records" and _headers_seen(tag)
627
+ # Where the tile's figures come from, said on it: exact (an endpoint,
628
+ # the replies' headers) or a stated limit counted by the bench.
629
+ t["source"] = ((["its reply headers" if got.get("how") == "seen" else "its quota endpoint"] if t["groups"] else [])
630
+ + ([("its usage endpoint" if host == REQUESTY_HOST
631
+ else "its stated daily limit, counted by the bench")] if t["pool"] else [])
632
+ + [g["label"] for g in t["caps"]]) or ["nothing stated"]
370
633
  last = max((r["t"] for r in rows if r.get("status") == "ok"), default=None)
371
634
  t["fresh"] = ({"how": "live", "at": got["at"]} if got.get("at") and got.get("how") != "seen"
372
635
  else {"how": "seen", "at": got["at"]} if got.get("at")
373
636
  else {"how": "answered", "at": _iso(last)} if last else {"how": "none", "at": None})
374
637
  t["live"] = sum(1 for a in aliases if a not in resting)
375
- resets = [_ts(w.get("reset_at")) for g in t["groups"] for w in g["windows"]] \
376
- + [_ts(p.get("reset_at")) for p in t["pool"]]
638
+ windows = [w for g in t["groups"] + t["caps"] for w in g["windows"]]
639
+ resets = [_ts(w.get("reset_at")) for w in windows] + [_ts(p.get("reset_at")) for p in t["pool"]]
377
640
  resets = [r for r in resets if r and r > now]
378
641
  t["next_reset"] = _iso(min(resets)) if resets else None
379
- t["out"] = (t["live"] == 0 or any(w["left"] <= 0 for g in t["groups"] for w in g["windows"])
642
+ t["out"] = (t["live"] == 0 or any(w["left"] <= 0 for w in windows)
380
643
  or any(p.get("left") == 0 for p in t["pool"]))
381
- t["notes"] = list(got.get("notes") or [])
644
+ t["notes"] = list(got.get("notes") or []) \
645
+ + ([f"NVIDIA's site: {nv['rpm_text']}, varying by model and traffic."] if nv.get("rpm_text") else []) \
646
+ + ([f"NVIDIA's limits file could not be read ({nv['broken']}); these are the figures read"
647
+ f" {str(nv.get('at'))[:10]}."] if nv.get("broken") else [])
382
648
  out.append(t)
383
649
  return ordered(out)
384
650
 
385
651
 
386
652
  def _openrouter_pool(aliases, utc_rows, regs, got, learned, now) -> list[dict]:
387
653
  """A bar per OpenRouter key the bench uses: free-model requests since
388
- 00:00 UTC, the bench's own count, against 50 or 1,000 a day."""
654
+ 00:00 UTC, the bench's own count, against the day's limit OpenRouter
655
+ stated (a 429's), else none."""
389
656
  import endpoints
390
657
  import quota
391
658
  seen_429 = locked.read_json(quota.FILE).get("openrouter") or {}
@@ -396,14 +663,13 @@ def _openrouter_pool(aliases, utc_rows, regs, got, learned, now) -> list[dict]:
396
663
  for ep, eps in sorted(by_ep.items()):
397
664
  used = sum(r.get("attempts") or 1 for r in utc_rows
398
665
  if r.get("alias") in eps and str(r.get("served", "")).endswith(":free"))
399
- # What OpenRouter stated wins (learned, else the last 429's, kept a day
400
- # past reset); the key's free tier only guesses, and was seen wrong.
666
+ # Only what OpenRouter stated (learned, else the last 429's, kept a day
667
+ # past reset): its docs' 50-or-1,000 by tier changes unseen, and the
668
+ # tier was seen wrong.
401
669
  stated = [((learned.get(a) or {}).get("limits") or {}).get("rpd", {}).get("value") for a in eps]
402
670
  stated += [r.get("limit") for a in eps for r in (seen_429.get(a) or {}).values()
403
671
  if isinstance(r, dict) and r.get("window") in ("day", "unknown")]
404
672
  limit = min([s for s in stated if s] or [0]) or None
405
- if not limit and got.get("endpoint") == ep:
406
- limit = 50 if got.get("free_tier") else 1000
407
673
  via = "direct key" if not endpoints.hub(ep) else f"via {ep}"
408
674
  bar = {"label": f"free requests today · {via}", "used": used, "of": limit,
409
675
  "left": max(0, round(100 * (limit - used) / limit)) if limit else None,
@@ -447,6 +713,72 @@ def _selftest() -> None:
447
713
  assert c == [{"label": "", "windows": [{"label": "month", "left": 0,
448
714
  "reset_at": "2026-11-26T15:38:32+00:00"}]}], c
449
715
  assert codex_groups({}) == [] and antigravity_groups({}) == []
716
+ now = 1793306000.0 # 2026-10-29 ~19:13 UTC
717
+ xk = xkiro_groups({"free_tokens": {"used_today": 125000, "limit_per_day": 500000, "remaining": 375000},
718
+ "windows": [{"kind": "short", "window_sec": 18000, "spent_usd": "1.500000",
719
+ "cap_usd": "6.000000", "remaining_usd": "4.500000", "resets_in_sec": 600}]}, now)
720
+ assert xk[0]["windows"][0] == {"label": "tokens · day", "left": 75, "remaining": 375000, "of": 500000,
721
+ "reset_at": "2026-10-30T00:00:00+00:00"}, xk
722
+ assert xk[1]["windows"][0]["label"] == "5 hours · spend" and xk[1]["windows"][0]["left"] == 75, xk
723
+ assert xkiro_groups({"free_tokens": {"limit_per_day": None}, "windows": []}, now) == []
724
+ rq = {"usage": {"2026-10-30": {"total_requests": 10, "grouped_data": [
725
+ {"group_by_values": {"model_used": "muse-glimmer-30b"}, "total_requests": 2},
726
+ {"group_by_values": {"model_used": "nemotron-3-nano-omni-30b-a3b-reasoning"}, "total_requests": 7},
727
+ {"group_by_values": {"model_used": "some-paid-model"}, "total_requests": 1}]}}}
728
+ ms = {"data": [{"id": "nvidia/muse-glimmer-30b", "input_price": 0, "output_price": 0},
729
+ {"id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", "input_price": 0, "output_price": 0},
730
+ {"id": "lab/some-paid-model", "input_price": 0.000001, "output_price": 0.000002}]}
731
+ assert requesty_free(rq, ms) == 9 and requesty_free({}, ms) == 0
732
+ # Caps: yours beat a stated one on the same axis; counted since midnight UTC or the month's start.
733
+ mid = _utc_midnight(now)
734
+ day = [{"connection": "router.requesty.ai", "t": mid + 60, "attempts": 2},
735
+ {"connection": "router.requesty.ai", "t": mid - 60, "attempts": 5}, # yesterday
736
+ {"connection": "api.cohere.ai", "t": mid + 60, "attempts": 1, "tokens_in": 10, "tokens_out": 5},
737
+ {"connection": "router.requesty.ai", "t": mid + 90, "attempts": 0}] # refused: not sent
738
+ said = (200, "its replies", "Stated.")
739
+ g = caps("router.requesty.ai", {}, {"rpd": said}, day, lambda: [], now)
740
+ assert len(g) == 1 and g[0]["windows"][0] == {"label": "requests · day", "left": 99, "remaining": 198,
741
+ "of": 200, "reset_at": _iso(mid + 86400)}, g
742
+ assert g[0]["label"] == "its replies · counted by the bench" and "not enforced" in g[0]["title"], g
743
+ g = caps("router.requesty.ai", {"router.requesty.ai": {"rpd": 10, "rpm": 5}}, {"rpd": said}, day, lambda: [], now)
744
+ assert g[0]["label"] == "your cap · counted by the bench" and g[0]["windows"] == [
745
+ {"label": "requests · day", "left": 80, "remaining": 8, "of": 10, "reset_at": _iso(mid + 86400)}], g
746
+ asked = []
747
+ g = caps("api.cohere.ai", {}, {"rpmo": (1000, "its replies", "")}, day, lambda: asked.append(1) or day, now)
748
+ assert asked and g[0]["windows"][0]["remaining"] == 999 and g[0]["windows"][0]["label"] == "requests · month", g
749
+ assert caps("nvidia-api", {}, {}, day, lambda: 1 / 0, now) == [] # no cap: no ledger read
750
+ # NVIDIA's file: read, a change alerts (to dismiss), a move or a new shape alerts and keeps the last read.
751
+ nv = {"rateLimits": {"requestsPerMinute": "Up to 40 rpm", "requestsPerDay": "10,000 requests per day"}}
752
+ assert nvidia_figures(nv) == {"rpd": 10000, "rpm_text": "Up to 40 rpm"}
753
+ import tempfile
754
+ import alerts as al
755
+ g, seen = globals(), []
756
+ saved = (g["LIVE"], g["_get_json"], g["_nvidia_on_bench"], al.raise_alert, al.clear)
757
+ try:
758
+ with tempfile.TemporaryDirectory() as tmp:
759
+ g["LIVE"], g["_nvidia_on_bench"] = pathlib.Path(tmp) / "live.json", lambda: True
760
+ al.raise_alert = lambda key, *a, **k: seen.append(("raise", key, k.get("dismiss")))
761
+ al.clear = lambda key: seen.append(("clear", key))
762
+ g["_get_json"] = lambda url, head: nv
763
+ assert watch_nvidia()["rpd"] == 10000 and not [s for s in seen if s[0] == "raise"], seen
764
+ nv = {"rateLimits": {"requestsPerMinute": "Up to 30 rpm", "requestsPerDay": "5,000 requests per day"}}
765
+ seen.clear()
766
+ assert watch_nvidia()["rpd"] == 5000 and ("raise", "limits:nvidia:changed", True) in seen, seen
767
+
768
+ def gone(url, head):
769
+ raise urllib.error.HTTPError(url, 404, "Not Found", {}, None)
770
+ g["_get_json"] = gone
771
+ seen.clear()
772
+ r = watch_nvidia()
773
+ assert r["rpd"] == 5000 and "404" in r["broken"] and ("raise", "limits:nvidia:unread", None) in seen, r
774
+ assert locked.read_json(g["LIVE"])["_nvidia"]["rpd"] == 5000 # the last good read stays
775
+ g["_get_json"] = lambda url, head: {"somethingElse": {}}
776
+ assert "rateLimits" in watch_nvidia()["broken"]
777
+ g["_get_json"] = lambda url, head: nv
778
+ seen.clear()
779
+ assert "broken" not in watch_nvidia() and ("clear", "limits:nvidia:unread") in seen, seen
780
+ finally:
781
+ g["LIVE"], g["_get_json"], g["_nvidia_on_bench"], al.raise_alert, al.clear = saved
450
782
  assert _seconds_name(18000) == "5 hours" and _seconds_name(7200) == "2 hours"
451
783
  assert _utc_midnight(1793306000) == dt.datetime(2026, 10, 29, tzinfo=UTC).timestamp()
452
784
  assert _limit_text({"rpm": 20, "tpd": 200000}) == "20 requests a minute, 200,000 tokens a day"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendellresearch-ai/benchwarmers",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Self-hosted panel gateway for LLMs: ask a collection of models at once, keep every answer side by side, rotate explorer models in, and let your verdicts pick the next panel.",
5
5
  "mcpName": "io.github.WendellResearch-AI/benchwarmers",
6
6
  "license": "MIT",
@@ -19,6 +19,14 @@ THE RULES (decided 2026-10-15)
19
19
  reason -- a rate limit that never lifts is a quota or a dead model.
20
20
  `mistral-medium-3-5` answered 429 to 11 calls out of 11.
21
21
  - NEVER on a rate limit or timeout alone inside those 7 days.
22
+ - OUT OF QUOTA IS NOT FAILING (2026-11-02): a call the bench held back because
23
+ a limit is spent until a stated reset, or an account-wide quota, says
24
+ nothing about the model. Its days pause the clock, and while it is the
25
+ latest word there is no countdown. `gpt-5.6-luna` sat on "retires in 3
26
+ days" while CLI Proxy API's codex sign-in was out until 26 November and the
27
+ bench had not sent it a call since 28 October. A refusal that names a short
28
+ wait and is refused again after it still counts: that is a model that keeps
29
+ failing, not one that is tapped out.
22
30
 
23
31
  "No answer" means from ANY source: the perf probe, a useful offload, or a
24
32
  suite output that was a real answer. A model that answers in the suite resets
@@ -68,6 +76,16 @@ def account_quota(detail: str) -> bool:
68
76
  return any(marker in detail for marker in ACCOUNT_QUOTAS)
69
77
 
70
78
 
79
+ HELD = "not sent; paced by the bench" # metrics/pacing.py's refusal
80
+
81
+
82
+ def tapped_out(detail: str) -> bool:
83
+ """Out of quota right now, so the model could not be tried: the bench held
84
+ the call back (a limit spent until its reset, the provider's or a cap you
85
+ set), or the account's shared quota is spent (account_quota)."""
86
+ return HELD in detail or account_quota(detail)
87
+
88
+
71
89
  def plan(today: dt.date, models: dict[str, str], live: set[str],
72
90
  first_missing: dict[str, str], rows: list[dict],
73
91
  answers: dict[str, dt.date], restored: dict[str, dt.date],
@@ -87,15 +105,18 @@ def plan(today: dt.date, models: dict[str, str], live: set[str],
87
105
  down: dict[str, bool] = {}
88
106
  candidates: list[tuple[str, str, str]] = [] # (alias, bucket, why)
89
107
  for alias, served in models.items():
90
- mine = [r for r in rows if r["alias"] == alias]
108
+ mine = sorted((r for r in rows if r["alias"] == alias), key=lambda r: r["date"]) # [-1] is the latest word
91
109
  last_ok = max([r["date"] for r in mine if r["answered"]]
92
110
  + ([answers[alias]] if alias in answers else []),
93
111
  default=None)
94
112
  floor = max(d for d in (last_ok, restored.get(alias), dt.date.min) if d)
95
113
  # A refused key (`signin`) is the endpoint's, fixed by signing in again.
96
114
  fails = sorted((r for r in mine if not r["answered"] and r["failure"] and r["failure"] != "signin"
97
- and r["date"] > floor and not account_quota(r["detail"])),
115
+ and r["date"] > floor and not tapped_out(r["detail"])),
98
116
  key=lambda r: r["date"])
117
+ quiet = {r["date"] for r in mine if not r["answered"] and r["date"] > floor
118
+ and tapped_out(r["detail"])}
119
+ resting = bool(mine) and not mine[-1]["answered"] and tapped_out(mine[-1]["detail"])
99
120
  missing_since = None
100
121
  if served not in live and served in first_missing:
101
122
  missing_since = max(dt.date.fromisoformat(first_missing[served]),
@@ -107,8 +128,10 @@ def plan(today: dt.date, models: dict[str, str], live: set[str],
107
128
  why = (f"{latest['failure']}: {latest['detail']}".rstrip(": ")
108
129
  if latest else "")
109
130
  other_days = {r["date"] for r in fails if r["failure"] == "other"}
110
- span = (today - fails[0]["date"]).days if fails else 0
111
131
  fail_days = {r["date"] for r in fails}
132
+ # Days it was out of quota, and nothing else, do not count: the clock pauses.
133
+ span = ((today - fails[0]["date"]).days
134
+ - len({q for q in quiet - fail_days if q > fails[0]["date"]})) if fails else 0
112
135
 
113
136
  # A CONNECTION PREFIX ADDED IN OPEN WEBUI renames every model on it
114
137
  # (`codestral-latest` -> `llm7.codestral-latest`), and the old names
@@ -146,7 +169,7 @@ def plan(today: dt.date, models: dict[str, str], live: set[str],
146
169
  left.append((OTHER_DAYS - len(other_days), why))
147
170
  if fails:
148
171
  left.append((BACKSTOP_DAYS - span, why))
149
- if left:
172
+ if left and not resting: # resting: the Quota page says when it is back
150
173
  days, reason = min(left)
151
174
  out["pending"].append((alias, f"{max(days, 1)}|{reason}"))
152
175
 
@@ -351,6 +374,23 @@ def _selfcheck() -> None:
351
374
  detail="429 Rate limit exceeded: free-models-per-day") for n in (8, 5, 0)]
352
375
  p = plan(d, {"quota": "v/quota"}, {"v/quota"}, {}, shared, {}, {}, {"v/quota": "v"})
353
376
  assert not p["retire"] and not p["pending"], p
377
+ # held back by the bench for days (its codex sign-in spent until a reset): no countdown
378
+ held = [dict(row("luna", n, "capacity"), detail=f"rate limit ({HELD}): 0 left") for n in (9, 5, 0)]
379
+ p = plan(d, {"luna": "c/luna"}, {"c/luna"}, {}, held, {}, {}, {"c/luna": "c"})
380
+ assert not p["retire"] and not p["pending"], p
381
+ # real failures, then out of quota for a week: those days pause the clock
382
+ paused = ([row("p", n, "capacity") for n in (10, 9, 8, 0)]
383
+ + [dict(row("p", n, "capacity"), detail=HELD) for n in range(1, 8)])
384
+ p = plan(d, {"p": "c/p"}, {"c/p"}, {}, paused, {}, {}, {"c/p": "c"})
385
+ assert not p["retire"] and dict(p["pending"])["p"].startswith("4|"), p
386
+ # ...and while it is still out of quota, no countdown is shown at all
387
+ p = plan(d, {"p": "c/p"}, {"c/p"}, {}, paused[:3] + paused[4:], {}, {}, {"c/p": "c"})
388
+ assert not p["retire"] and not p["pending"], p
389
+ # a short stated wait, refused again day after day, is a model that keeps failing
390
+ busy = [dict(row("u", n, "capacity"), detail="429 allows 1 request(s) every 1 min, retry in 60s")
391
+ for n in (8, 5, 0)]
392
+ p = plan(d, {"u": "v/u"}, {"v/u"}, {}, busy, {}, {}, {"v/u": "v"})
393
+ assert {a for a, _ in p["retire"]} == {"u"}, p
354
394
  # A refused key is the endpoint's, never the model's (suite/run.py SIGNIN)
355
395
  from run import failure_class
356
396
  assert failure_class("Error: Error code: 401 - {'error': 'invalid key'}") == "signin"