resolveAntigravityForwardBaseURL() used ForwardBaseURLs() (which reorders the
daily/sandbox endpoint to the front) and returned the first entry by default —
daily-cloudcode-pa.sandbox.googleapis.com. Every Antigravity gateway request
was therefore sent to Google's sandbox endpoint, which rejects production OAuth
tokens: the account is benched with "OAuth 401: Invalid bearer token" (native
paths surface it as 502) and never recovers. The dashboard "test connection"
uses the production endpoint (antigravity.BaseURL), which is exactly why the
test succeeds while the gateway 401s.
Default to production (antigravity.BaseURLs[0] = cloudcode-pa.googleapis.com),
matching the OAuth/test path; the daily/sandbox endpoint is now opt-in via
GATEWAY_ANTIGRAVITY_FORWARD_BASE_URL=daily|sandbox.
Fixes#3611, #2962. Supersedes the token-refresh self-heal approach (that
premise — token staleness — was disproven: a 30s-old freshly-authorized token
also 401s on the gateway while working on the test path).
Verified on a live instance with a freshly-authorized Antigravity account:
/antigravity/v1/messages and /v1/messages now return real completions
(claude-sonnet-4-6, claude-opus-4-6); the same account/token 401/502'd before.
Anthropic OAuth/setup-token accounts now expose a Fable-only 7d window in
429 responses (anthropic-ratelimit-unified-7d_oi-*). Previously, a 429
triggered solely by this window fell through to the legacy path and marked
the whole account rate limited (picking the sooner 5h/7d reset) even though
5h/7d were still "allowed".
Backend:
- HandleUpstreamError: when only 7d_oi is rejected/exceeded, set a
model-level rate limit (scope "claude-fable-5") and skip account-level
SetRateLimited / session-window rewrite / temp-unsched entirely.
Real 5h/7d exhaustion keeps the exact legacy account-level behavior.
- modelRateLimitKeysForRequest: append the Fable family scope for any
mapped Anthropic model containing "fable", so claude-fable-5[1m] and
other variants are excluded from scheduling together.
- Passive sampling: collect 7d_oi utilization/reset into Extra
(passive_usage_7d_oi_*), including from the 429 response itself so the
usage bar doesn't freeze at a stale value during the limit window;
cleared on 5h window reset like the existing 7d keys.
- Usage API: new seven_day_fable window in UsageInfo. GetPassiveUsage
builds it from passive samples; active queries parse the upstream
seven_day_overage_included field (claim name from the
representative-claim header) and fall back to passive data when the
upstream usage API doesn't return it, so the active-query button keeps
the bar populated. seven_day_sonnet handling is preserved unchanged.
Frontend:
- /admin/accounts usage cell renders a "7d F" (amber) progress bar next to
the existing 5h / 7d / 7d S bars for Anthropic OAuth/setup-token accounts.
Members of the same ChatGPT team share chatgpt_account_id, so matching
imports by the account key first could overwrite another member's
account credentials. Identity keys are now ordered by strength
(user > email > access > account), and an account-key hit is rejected
when both sides carry different chatgpt_user_id values.
- Keep the account-key fallback when either side lacks a user id, so
legacy accounts without chatgpt_user_id are updated and backfilled
instead of duplicated
- Index all candidate accounts per shared key so a teammate's row can
no longer shadow a legacy account depending on row order
- Apply the same conflict check to in-batch dedup and emit a warning
when a legacy account is claimed via the shared account key
- Scope a 120s timeout to the Codex session import request instead of
raising the global client timeout
Follow-up fixes to #3569 based on code audit:
- Expose server_timezone / server_utc_offset in public settings (and the
__APP_CONFIG__ injection payload) and label every peak-window display
with the server UTC offset, so users don't misread the billing window
as browser-local time
- Unify CreateGroup/UpdateGroup peak-config sanitization via a single
NormalizePeakRateConfig chokepoint: non-subscription groups always get
peak fields cleared; unparseable window strings and negative
multipliers are scrubbed when peak is disabled
- Replace hot-path time.Parse in PeakMultiplierAt with a manual HH:MM
parser (accept set verified byte-for-byte identical to
time.Parse("15:04") by exhaustive fuzzing) and reuse it in validation
- Revert the zero-behavior CalculateCost indirection churn in
billing_service/gateway_service introduced by #3569
- Remove dead GetGroupPlatformMap and the duplicate deref helper in the
admin handler package
- Share frontend peak formatting via utils/peak-rate.ts, unify the ×N
label format, and move hardcoded Chinese tooltips to i18n keys
The Codex image_generation bridge block in Forward injects
tools:[image_generation] and tool_choice:"auto" (since #3498) into every
Codex request, including /responses/compact. The upstream compact endpoint
rejects tool_choice with 400 "Unknown parameter: 'tool_choice'", so every
remote compaction fails once the bridge is enabled. Guard the whole image
generation block with !isCompactRequest.
Fixes#3629