From 34a598372c94d7869896a5409fd57cda0d83fb67 Mon Sep 17 00:00:00 2001 From: yetone Date: Fri, 9 Oct 2026 11:51:55 +0800 Subject: [PATCH] providers, gateway, gui, cli: each usage window of an account can have a cap of its own beside the account's (willz on Discord) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit willz asked for a cap per window: a friend's account should stop at 50% of its five hours, while its week has its own 40%. Until now an account had one usage cap, the same share of every window. What the user sees: the account's pill still sets the cap for all its windows and stays the default. Each window's meter on the account's row is now a button: a click opens the app's own menu headed "Cap for " with the account's cap, No cap on this window, 50–90%, and Other… typed in place. A window with its own cap says it beside its figure ("Cap 50%", or "No cap"), its mark sits there, and the account's pill says "Caps by window" when only windows have caps. The account is held while any window is past its own cap, and the row's note, the gateway's 429 and the Routing trace name that window's cap. CLI: magpie provider account-cap --window "5 hours" 50 (none: no cap on that window; default: back to the account's), and `account-cap ` lists the overrides. How: Provider.AccountWindowCaps (providers.json, by account and the window's capId, its name in lower case; 1–99, or 100 for none) beside AccountCaps. provider.WindowCaps gives a window its share (its own, else the account's); Allowance.CapHeld and capReached judge each window by its own share and report the fullest holding window's cap. provider.HoldShare becomes HoldCaps, used by gateway capHeld (plan and group filtering, cappedError, trace) and codex_backend's route-or-relay choice; codex_switch's NextLogin moves the agent off at per-window caps. QuotaWindow carries capId; Limit.name is exported as Limit.Name. A config with no window caps reads and routes exactly as before (TestSetWindowCap reads an old one). The meter is a span with role=button, Enter/Space: a