Files
OmniRoute/config
John Costa 736ade4499 feat(sse): learn hard request caps from 429 bodies and pace under them (#13895)
* feat(sse): learn hard request caps from 429 bodies and pace under them

Providers such as TokenRouter reject bursts with prose like "Maximum 5
requests within 1 minutes" and send no rate-limit headers, so the limiter
never learned the ceiling and kept racing into it. Every 429 also evicts
the limiter, and the rebuilt one carried no pacing at all.

updateFromResponseBody now parses that phrasing (plus "N requests per
minute", "N requests per M seconds" and "N RPM") into a per-window cap,
applies it to the limiter as an empty reservoir that refills N every
window with calls spread window/N apart, and records it in
learnedRateLimits. getLimiter reapplies a learned cap whenever the limiter
is rebuilt, and loadPersistedLimits restores it after a restart, unless
the connection has an explicit RPM override.

Also pins the capacity-503 classification for TokenRouter's "system
cpu/disk overloaded" bodies as model_capacity with backoff, and prunes the
no-unused-vars allowlist entry for rateLimitManager.ts that this change
made obsolete.

Fixes #13594

* fix(sse): require a cap word before rpm figures and gate every cap path on the queue budget

`RPM_RE` accepted any "<n> rpm" so a usage readout like "current usage:
4 rpm" pinned the connection to one request every 15s. It now shares the
cap-word prefix and made/sent/used guard with the verbose pattern, so a
bare usage figure is not read as a ceiling.

The queue-budget refusal only guarded the learn path. A cap learned under
a generous maxWaitMs was still re-applied by getLimiter on every rebuild
and by loadPersistedLimits after a restart once the budget shrank, turning
every request into a local queue timeout. The check now lives in
capSettingsWithinBudget(), which all three paths call; a refused cap stays
recorded and is retried the next time the limiter is built.
2026-09-19 00:05:07 -03:00
..