c06a550b73680289d5a94de7a412356d4040297d
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b0a96cbbd7 |
gateway, provider, gui: a Sub2API key given 5h/1d/7d limits is weighed and rested by its own windows, like an account (#1004)
* gateway: a Sub2API key out of its 5h/1d/7d limit ("…限额已用完") rests as out of quota, not a 1-minute rate limit
Sub2API refuses a key that has spent the 5-hour, day or 7-day limit its
owner gave it with 429 "api key 7天限额已用完" (or 日限额, 5小时限额),
typed rate_limit_exceeded. It matched rateWords and not plannedWords
("限额" is not "额度"), so failure() said rate: the key rested a minute,
was tried again, refused again, and sank, for the days its 7-day window
had left. plannedWords now has 限额已用完, so it is failQuota.
The relay's other refusals keep their classes, fixed by the test: the
pool out of accounts (v0.1.149's 503 "No available accounts": other;
later versions' 429 "All available accounts are currently
rate-limited": rate), its upstream's rate limit (rate), the key's own
total quota API_KEY_QUOTA_EXHAUSTED (quota) and the wallet's 403
INSUFFICIENT_BALANCE (credit).
Verified: TestSub2APIKeyLimitIsQuota, with Sub2API v0.1.149's message
bodies. Without the change it fails: "429 {…api key 7天限额已用完…}:
rate, want quota", and the same for 日限额 and 5小时限额.
* provider: a Sub2API key's /v1/usage shows its 5 hours / 1 day / 7 days windows instead of nothing at "remaining"
A Sub2API key its owner gave a 5-hour, day or 7-day spending limit
answers /v1/usage in quota_limited mode with rate_limits — each
window's limit, used and reset_at in dollars — and, without a total
quota of its own, no "remaining". With the Balance URL set to it, the
key's card said `nothing at "remaining" in the reply`.
readSub2APIKeyLimits reads rate_limits into the windows the cards
already name 5 hours, 1 day and 7 days, in USD. A window not started
(used 0, no reset_at) is taken to renew a span from now; a window with
a limit of 0 is none. The card shows them, beside a balance field the
user wrote; with none, Check balance and `magpie provider show` give
what the fullest window has left. A wallet key's reply is unchanged.
KeyAllowance gives routing the same windows, per provider and key: read
behind the request, never waited for, at most once a minute (once for
any number of asks at a time); a failed read keeps the last; after a
restart it is seeded from the card's last reading in quotas.json;
StaleKeyAllowance has it read again at the next ask. ForgetBalances (a
key or Balance URL changed) has every key read again, a read begun
before dropped, the last reading standing until then. Only keys whose
Balance URL is a Sub2API /v1/usage are asked: any other key, and every
account, costs nothing here.
Verified with real bytes of a limited key's reply, its key removed
(testdata/sub2api_usage_key_limits*.json): TestReadSub2APIKeyLimits,
TestSub2APIKeyLimitsOnItsCard, TestKeyAllowance*. Each fails without
its part, e.g. the card without the windows: `Error:nothing at
"remaining" in the reply`; KeyAllowance waiting for the read: "waited
30.0s"; asking every time: "asked 11 times"; a failed read asked again
at once: "asked 22 times after failing"; not seeded: "seeded = [],
false"; any key asked: "asked /api/usage/token"; ForgetBalances
dropping what was read: "after ForgetBalances, and a read begun before
it: []"; leaving a read under way loading: "the key's windows were
never read"; a stale-generation read landing: it replaced 95%; a limit
of 0 taken: "a day's limit of 0 = [{… Used:100 …}]"; Balance from the
first window only: "$300.00" for $80 left.
* gateway, gui: keys whose windows magpie reads are weighed and rested by them, like accounts
Smart, least-used and pace routing weighed only subscriptions by their
windows: weighRouted skipped every key (c.p.Account == nil), so a
Sub2API key beside Codex accounts in a group went by its place in the
order, and, refused for its 7-day limit, rested a quarter of an hour at
a time while the window had days to run.
A key whose own windows magpie reads (KeyAllowance: a Sub2API key given
a 5h/1d/7d limit, its Balance URL /v1/usage) is now weighed as an
account is: fine, low (90%) or spent (98%), the one renewing soonest
first, in a provider and in a group alike, and its trace row carries
them. Out of quota it rests till the window it filled renews, by
candidate.full; when its windows as last read aren't full it rests
quotaRest, and either way they are read again (staleKeyAllowance).
Every other failure of a key backs off as before, never to the window:
the relay out of accounts for everyone (503/429) says nothing of the
key's own spend. capHeld and pooled stay accounts' only. Keys magpie
reads no windows of are ordered exactly as before.
The Routing page tells such a key as it does an account: its row's bar,
used share and renewal, why it went first by its allowance, "Every key
and account here is at 90% or more", and the Smart header. A key not
read yet beside one read is told as weighed with it, not "keys go in
their order" or by its tokens. New strings in en, zh, ja and de.
Other users of the relay draining its accounts don't show in a key's
windows, which count only its spend: they show as the pool's 503/429,
which back off and fall over to the next key, account or fallback.
Verified: TestKeysWeighedByTheirWindows, TestGroupWeighsKeyWindows,
TestKeyOutOfItsWindowRestsUntilReset, TestKeyPoolErrorDoesNotRestForWindow;
and routing-key-windows.test.cjs in Chromium and WebKit (en/zh/ja/de).
Without the change they fail: "smart: kw#a kw#b kw#c"; "the key renewing
first: codex sub2api"; "full: rests 14m59.99s by quota"; "read again:
[]"; backing off to the window: "pool empty: rests 71h59m59.99s by
window (other)"; and on the page "relay-a goes first: keys go in their
order, those that suit the request first." where all are at 90% or
more, or a key not read yet beside one read.
* gateway, provider: a Sub2API key out of its limit is back once its windows read short of full, not only when the window renews
A key refused for its 5h/1d/7d limit rests till the window it filled
renews: days, for the week. Its windows are read again behind the next
requests (a minute apart) and by its card, but nothing lifted the rest
when they came back short of full, so the owner raising the key's limit
in the relay's panel, or resetting its usage, left the key out till the
week turned, though the relay would take it at once. Accounts have this
already: a Codex reset spent tells OnRenewed, and the gateway lifts the
account's rests (renewed).
A key's reading, routing's or its card's, now tells OnRenewed the same
way, as agent "" and the key's KeyAllowanceID, when a window it was full
in (Allowance.Full at SpentShareOf its routing, as candidate.full counts
it) is full no more, or full till sooner. Its first reading, a failed
read, or one that finds it fuller tells nothing. The gateway notes the
key's KeyAllowanceID on its rest out of quota only, so renewed lifts
that rest and no other: the relay out of accounts for everyone (503)
still backs off, and another key still out of its week stays out.
Where nothing weighs the key by its windows, nothing read them again
either: an ordered group weighs each member's keys alone, so a member
with one key, like a provider with one key, never asks for them. After
the one read its 429 starts, the key sat out its week on that reading.
restingOf, which plans a resting key behind the rest, now asks for the
windows of a key resting out of them (agent "" and a user on its rest),
so the next request planned past a minute reads them, behind it, and a
reading short of full brings the key back there too. No card is read
for it, and a key resting for any other reason asks nothing.
Verified: TestKeyAllowanceRenewed (provider) and
TestKeyBackOnceItsWindowIsNotFull (gateway, end to end through the key
cards' reads and restAfter), each failing with a piece of the change
undone: never telling, "its week's limit raised to $1,000: told []" and
"a, its limit raised: still rests by window till …"; telling on any
change, "its first reading, out of its week: told [/sub2api#…]"; a
failed read telling, "a read that failed: told [… …]"; the card not
telling, "its usage reset, read on its card: told [/sub2api#…]"; the
rest not noted, "a, its limit raised: still rests by window"; every
rest of a key noted, "c, the relay out of accounts: …, false"; renewed
not matching the key, "b, still out of its week: …, false". And
TestRestingKeyReadAgainInAnOrderedGroup (gateway: an ordered group, its
first member one key on a fake /v1/usage, no card read after the 429),
failing without restingOf's ask: "its limit raised: still rests by
window till …, its windows read 1 times".
Tried on a real Sub2API v0.1.149 relay, in a sandbox HOME, its key's 7d
limit lowered under its spend for the test and then restored, with
magpie serve on a group ordered: the key, then a local stand-in. The
key answered 429 "api key 7天限额已用完"; it rested by window till its
7d reset, 2026-10-13 00:00 +08:00, and the stand-in served. Planned
again a minute later, still over, it still rested. With the limit back
at $800, the next request was planned with it resting, the stand-in
serving, and read its windows; the one after went to the key, 200.
|
||
|
|
8005f17551 |
provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in (#966)
* provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in — magpie becomes its provider only while the Codex app itself holds the account magpie made itself Codex's provider (model_provider = "magpie", #540) whenever any of the active ChatGPT account's windows read 100%. A Pro account at 100% on its week with credits left keeps working in the Codex app: the backend answers every turn (about 490 200s in one day's usage log at 100%), yet magpie moved Codex off it, and as a provider without requires_openai_auth the app lost its ChatGPT state ("Sign in to ChatGPT to start a durable thread", no remote control). CodexUsedUp now reads codexHeld, which mirrors the Codex app's own composer block (ChatGPT.app webview bundle, hP and its helpers): the backend says the account isn't allowed (rate_limit.allowed = false), it has no credits (has_credits / unlimited), and it isn't a workspace still within its overage (overage_limit_reached = false, no spend cap, an ordinary rate_limit_reached). Window percent never decides it. Where the app goes by what /wham/usage can't show (a statsig gate, the gpt-reserve fallback, Team's reserve experiment that ignores overage), it counts as held: a wrong move costs ChatGPT extras, a missed one every turn. A reading kept through a failed read (keepLast) stops saying held once its used-up window has started again, as its usage does. NextLogin's account rotation and its thresholds are unchanged. A user stuck on the old move (codex.out = 1) is put back beside the sign-in by the next Sync, as #540 already does once the account has room. Verification: - TestCodexUsedUpIsWhatTheAppHolds (real /wham/usage replies, ids replaced) fails on the old trigger: 7 subcases, "CodexUsedUp = true, want false". Each workspace guard (spend cap, reason, overage, plan list, Team reserve) was mutated and fails its own subcase. - TestCodexHeldKeptOnlyTillTheReset fails without the quota_last change. - The real Pro account (7-day window 100%, ~28k credits, allowed false): CodexUsedUp true before, false after. - go vet ./...; go build (darwin), GOOS=linux -tags nogui, GOOS=windows; go test -tags nogui ./... under a sandbox HOME; -race -count=20 on the new and related Codex tests. - Not run: a live Codex app turn on an account truly held (none at hand), Windows/WSL runtime, GUI suites (no GUI change). * provider: a Codex account at a spend cap or past its overage is held, credits or not; limit_reached counts as not allowed codexHeld let credits through before the spend cap, so a Team seat at its cap (spend_control.reached, allowed false) with has_credits read not held and magpie left Codex on an account the backend refuses. The app's readiness check (26.930.61225, app-primary) returns limited on spend_control.reached or credits.overage_limit_reached before it looks at credits, and treats rate_limit.limit_reached like allowed false; held now reads the same. A spend cap with the rate limit still allowed (a workspace's included usage) stays not held. |
||
|
|
b00387f9db | provider: a Command Code Go account lists the models Command Code has for Go, not ten (01huadalang on Discord: command 账号登录只有十个模型,正确的应该是几十个) — for Go, magpie never asked for a list and showed a table of 10, on the word that Command Code has none; its Provider API's list answers without a key (86 models), and its CLI (1.73.0) offers Go every model its picker shows but the premium ones and those it blocks for Go. A Go account now fetches that list without its key, less the 30 refused, and saves it (56 today); the table, the CLI's whole Go set (57), stands in until then or when the fetch fails. Requests still go to /alpha/generate. TestCommandCodeGoModels fails without the change (10 models). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider pass. |