dsh-agy 0.3.0 → 0.3.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.
- package/README.md +29 -5
- package/lib/{accounts-CkaHhb5H.mjs → accounts-CKWybAfc.mjs} +236 -35
- package/lib/accounts-CKWybAfc.mjs.map +1 -0
- package/lib/cli/index.mjs +5 -5
- package/lib/client.js +18 -2
- package/lib/{constants-Db4tmyfb.mjs → constants-BpyRirvk.mjs} +38 -2
- package/lib/constants-BpyRirvk.mjs.map +1 -0
- package/lib/{exchange-yZPIZumk.mjs → exchange-LzEyxve1.mjs} +2 -2
- package/lib/{exchange-yZPIZumk.mjs.map → exchange-LzEyxve1.mjs.map} +1 -1
- package/lib/{import-BaFULQnH.mjs → import-CotRHNDk.mjs} +3 -3
- package/lib/{import-BaFULQnH.mjs.map → import-CotRHNDk.mjs.map} +1 -1
- package/lib/index.mjs +2 -2
- package/lib/{models-BGuwl50d.mjs → models-Qn0-sh6X.mjs} +2 -2
- package/lib/{models-BGuwl50d.mjs.map → models-Qn0-sh6X.mjs.map} +1 -1
- package/lib/{plugin-common-B1MG00JT.mjs → plugin-common-B5dyfnMH.mjs} +4 -4
- package/lib/{plugin-common-B1MG00JT.mjs.map → plugin-common-B5dyfnMH.mjs.map} +1 -1
- package/lib/{quota-summary-5CypVffd.mjs → quota-summary-adID82I2.mjs} +9 -12
- package/lib/quota-summary-adID82I2.mjs.map +1 -0
- package/lib/web/plugin.mjs +6 -6
- package/package.json +6 -2
- package/lib/accounts-CkaHhb5H.mjs.map +0 -1
- package/lib/constants-Db4tmyfb.mjs.map +0 -1
- package/lib/quota-summary-5CypVffd.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ fingerprinting, and both CLI and web management.
|
|
|
20
20
|
rate limits, per-account cooldown to the real reset time, per-account device
|
|
21
21
|
fingerprints.
|
|
22
22
|
- **Inline Settings UI**: an Antigravity section inside DSH Settings with four
|
|
23
|
-
tabs — accounts (login, activation,
|
|
24
|
-
fingerprint and proxy management), models (per-model visibility), usage
|
|
23
|
+
tabs — accounts (login, activation, grouped 5-hour/weekly quota windows, test
|
|
24
|
+
calls, fingerprint and proxy management), models (per-model visibility), usage
|
|
25
25
|
(cumulative token and request statistics), and credentials (import/export).
|
|
26
26
|
No separate page: the surface only exists where DSH Settings does.
|
|
27
27
|
- **Model visibility toggles**: hide individual models from the DSH model
|
|
@@ -37,10 +37,14 @@ fingerprinting, and both CLI and web management.
|
|
|
37
37
|
|
|
38
38
|
## Screenshots
|
|
39
39
|
|
|
40
|
-
The Antigravity section inside DSH Settings
|
|
41
|
-
|
|
40
|
+
The Antigravity section inside DSH Settings. Account emails and project names are
|
|
41
|
+
redacted.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
| Accounts | Models |
|
|
44
|
+
|:---:|:---:|
|
|
45
|
+
|  |  |
|
|
46
|
+
| **Limits** — 5-hour and weekly windows | **Usage** — cumulative, per-model and per-account |
|
|
47
|
+
|  |  |
|
|
44
48
|
|
|
45
49
|
## Quickstart
|
|
46
50
|
|
|
@@ -193,6 +197,26 @@ reset with headroom left are used first ("use it or lose it"), near-exhausted
|
|
|
193
197
|
families are avoided, and exhausted families block the account until the real
|
|
194
198
|
reset time.
|
|
195
199
|
|
|
200
|
+
Each family is measured on **both** of its windows, because they refill on
|
|
201
|
+
different clocks:
|
|
202
|
+
|
|
203
|
+
| Window | Refills | Exhausted when | Blocks until |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| Rolling 5-hour | every 5 hours | below 15% left | the 5-hour reset |
|
|
206
|
+
| Weekly | every 7 days | 1% or less left | the **weekly** reset |
|
|
207
|
+
|
|
208
|
+
The two gates are independent. A family can sit at 90% of its 5-hour budget
|
|
209
|
+
while its week is spent — four 5-hour refills do not return a weekly budget —
|
|
210
|
+
so a week-exhausted account is rotated away instead of being selected and
|
|
211
|
+
failing. The thresholds are deliberately different (15% of five hours is ~45
|
|
212
|
+
minutes of runway; 1% of a week is ~1.7 hours): judging the 5-hour window too
|
|
213
|
+
late costs a wait of at most five hours, while judging the week too late parks
|
|
214
|
+
the account for days.
|
|
215
|
+
|
|
216
|
+
The weekly reading comes only from `retrieveUserQuotaSummary` — the per-model
|
|
217
|
+
`fetchAvailableModels` probe has no window field at all — and a probe that
|
|
218
|
+
reports neither window cannot erase a known-drained one.
|
|
219
|
+
|
|
196
220
|
429 (Too Many Requests) responses:
|
|
197
221
|
|
|
198
222
|
| Category | Behavior |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as setResolvedAgyVersion, a as AGY_PLATFORM_ENUM, b as
|
|
1
|
+
import { _ as setResolvedAgyVersion, a as AGY_PLATFORM_ENUM, b as QUOTA_WINDOWS, d as antigravityUserAgent, g as resolveAgyClientCredentials, l as OAUTH_TOKEN_URL, p as currentAgyVersion, t as AGY_CLIENT_ID, v as AgyAuthError, x as isProxyRouted, y as AgyPoolBlockedError } from "./constants-BpyRirvk.mjs";
|
|
2
2
|
import { a as probeFetch, l as redactCredentials, o as proxiedFetch, r as isProxyUnreachableError, t as accountFetch } from "./proxy-DfaL73mL.mjs";
|
|
3
3
|
import { i as generateAntigravityRequestId, r as deriveAntigravitySessionId } from "./identity-Ci8EGc1F.mjs";
|
|
4
4
|
import { i as parseRefreshParts, n as calculateTokenExpiry, r as formatRefreshParts, t as accessTokenExpired } from "./auth-DPPVFwio.mjs";
|
|
@@ -552,8 +552,6 @@ const BACKOFF_TIERS_MS = [
|
|
|
552
552
|
3e4,
|
|
553
553
|
6e4
|
|
554
554
|
];
|
|
555
|
-
/** Below this remaining fraction the account is treated as soft-quota-exhausted. */
|
|
556
|
-
const SOFT_QUOTA_THRESHOLD = .15;
|
|
557
555
|
function backoffFor(consecutiveFailures, maxJitterMs = 1e3) {
|
|
558
556
|
const index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_TIERS_MS.length - 1);
|
|
559
557
|
return (BACKOFF_TIERS_MS[index] ?? BACKOFF_TIERS_MS[BACKOFF_TIERS_MS.length - 1]) + Math.floor(Math.random() * maxJitterMs);
|
|
@@ -725,11 +723,28 @@ function pickNextAccountIndex(accounts, currentIndex, now = Date.now(), modelOrF
|
|
|
725
723
|
if (enabled.length === 0) return currentIndex;
|
|
726
724
|
return (enabled.find((e) => e.index > currentIndex) ?? enabled[0]).index;
|
|
727
725
|
}
|
|
728
|
-
/**
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Build the soft-quota cache TTL: short when low, long when healthy.
|
|
728
|
+
*
|
|
729
|
+
* Driven by the MOST pressured of the two windows, because they drain on
|
|
730
|
+
* different clocks: an account sitting at 90% of its 5-hour bucket but 2% of its
|
|
731
|
+
* week still has to be re-measured on the short interval, or a weekly exhaustion
|
|
732
|
+
* is discovered only when a request fails.
|
|
733
|
+
*
|
|
734
|
+
* `weeklyFraction` is the optional SECOND parameter, so the two fractions sit
|
|
735
|
+
* adjacent.
|
|
736
|
+
*
|
|
737
|
+
* Takes NO timestamp on purpose: deciding which fractions are still meaningful is
|
|
738
|
+
* `isQuotaStale`'s job, because a window whose reset has passed must not drive the
|
|
739
|
+
* interval either. It applies that guard before calling in here, so both
|
|
740
|
+
* arguments this function sees describe windows that still exist.
|
|
741
|
+
*/
|
|
742
|
+
function computeSoftQuotaCacheTtlMs(remainingFraction, weeklyFraction) {
|
|
743
|
+
if (typeof remainingFraction === "number" && remainingFraction < .15) return 6e4;
|
|
744
|
+
if (typeof weeklyFraction === "number" && weeklyFraction <= .01) return 6e4;
|
|
745
|
+
if (typeof remainingFraction !== "number" && typeof weeklyFraction !== "number") return 6e5;
|
|
746
|
+
if (typeof remainingFraction === "number" && remainingFraction < .5) return 3e5;
|
|
747
|
+
if (typeof weeklyFraction === "number" && weeklyFraction < .2) return 3e5;
|
|
733
748
|
return 9e5;
|
|
734
749
|
}
|
|
735
750
|
//#endregion
|
|
@@ -1065,12 +1080,149 @@ function earliestResetTime(a, b) {
|
|
|
1065
1080
|
return ta <= tb ? a : b;
|
|
1066
1081
|
}
|
|
1067
1082
|
/**
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1083
|
+
* The families a quota group's WINDOW BUCKET ID belongs to.
|
|
1084
|
+
*
|
|
1085
|
+
* `retrieveUserQuotaSummary` labels its buckets with upstream's own counters —
|
|
1086
|
+
* measured ids are `gemini-5h`, `gemini-weekly`, `3p-5h`, `3p-weekly` — which
|
|
1087
|
+
* is the same taxonomy `modelFamilyOf` mirrors. Reading the bucket id is
|
|
1088
|
+
* therefore a machine-readable lookup rather than a guess at a human label.
|
|
1089
|
+
*
|
|
1090
|
+
* `3p-` maps to TWO families on purpose, and that is not a shortcut: upstream
|
|
1091
|
+
* counts Claude and GPT under one third-party counter, so the weekly budget
|
|
1092
|
+
* belongs to both. No model-id prefix rule can recover that split, which is why
|
|
1093
|
+
* the mapping lives here rather than in `modelFamilyOf`.
|
|
1094
|
+
*/
|
|
1095
|
+
function familiesForBucketId(bucketId) {
|
|
1096
|
+
const id = bucketId.toLowerCase();
|
|
1097
|
+
if (id.startsWith("gemini-")) return ["google"];
|
|
1098
|
+
if (id.startsWith("3p-") || id.startsWith("third_party-")) return ["anthropic", "openai"];
|
|
1099
|
+
if (id.startsWith("claude-")) return ["anthropic"];
|
|
1100
|
+
if (id.startsWith("gpt-") || id.startsWith("openai-")) return ["openai"];
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* The families a group's LABEL covers, consulted only when no bucket id was
|
|
1105
|
+
* recognizable.
|
|
1106
|
+
*
|
|
1107
|
+
* Upstream's groups are its own taxonomy, not ours, and the measured labels are
|
|
1108
|
+
* `Gemini Models` and `Claude and GPT models` — the second spans two families,
|
|
1109
|
+
* so this is a 1-to-N mapping. It is a FALLBACK, not the primary rule: matching
|
|
1110
|
+
* words in a display string is the fragile half, and it survives only so that a
|
|
1111
|
+
* bucket id upstream renames still lands somewhere sensible.
|
|
1112
|
+
*
|
|
1113
|
+
* A label naming nothing we recognize maps to NO family deliberately.
|
|
1114
|
+
* Attributing an unknown group's budget to `google` would block healthy
|
|
1115
|
+
* accounts; leaving it unmapped only means that family stays unmeasured, which
|
|
1116
|
+
* is exactly the state before this function existed.
|
|
1117
|
+
*/
|
|
1118
|
+
function familiesForGroupName(name) {
|
|
1119
|
+
const label = name.toLowerCase();
|
|
1120
|
+
const families = [];
|
|
1121
|
+
if (label.includes("gemini") || label.includes("google")) families.push("google");
|
|
1122
|
+
const claude = label.includes("claude");
|
|
1123
|
+
const gpt = label.includes("gpt");
|
|
1124
|
+
if (claude && gpt) families.push("anthropic", "openai");
|
|
1125
|
+
else if (claude) families.push("anthropic");
|
|
1126
|
+
else if (gpt) families.push("openai");
|
|
1127
|
+
else if (families.length === 0 && (label.includes("3p") || label.includes("third"))) families.push("anthropic", "openai");
|
|
1128
|
+
return families;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Which of the two tracked windows an upstream token names, or `undefined` for a
|
|
1132
|
+
* token `CachedQuota` has no field for (`daily`, `monthly`).
|
|
1133
|
+
*
|
|
1134
|
+
* Read from `QUOTA_WINDOWS` (`types.ts`), the SAME table `adapter/quota-summary`
|
|
1135
|
+
* orders windows by. These were two independent tables once, and the gap between
|
|
1136
|
+
* them was a real defect: `daily` was orderable in the parser but unknown here, so
|
|
1137
|
+
* the group materialized an empty record that overwrote a live measurement. One
|
|
1138
|
+
* table makes that drift structurally impossible rather than merely tested for.
|
|
1139
|
+
*
|
|
1140
|
+
* Lookup is exact on the measured vocabulary (`5h`, `weekly`). A variant spelling
|
|
1141
|
+
* therefore classifies as unknown — which is safe, because the guard in
|
|
1142
|
+
* `ingestQuotaGroups` then skips the record rather than emptying it, and the
|
|
1143
|
+
* per-model probe still supplies the 5-hour reading.
|
|
1144
|
+
*/
|
|
1145
|
+
function windowKind(window) {
|
|
1146
|
+
return QUOTA_WINDOWS[window.toLowerCase()]?.kind;
|
|
1147
|
+
}
|
|
1148
|
+
/** The families one group belongs to: its bucket ids first, its label second. */
|
|
1149
|
+
function familiesForGroup(group) {
|
|
1150
|
+
const families = [];
|
|
1151
|
+
const add = (family) => {
|
|
1152
|
+
if (!families.includes(family)) families.push(family);
|
|
1153
|
+
};
|
|
1154
|
+
for (const window of group.windows) for (const family of familiesForBucketId(window.bucketId)) add(family);
|
|
1155
|
+
if (families.length === 0) for (const family of familiesForGroupName(group.name)) add(family);
|
|
1156
|
+
return families;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Aggregate the grouped `retrieveUserQuotaSummary` windows into per-family
|
|
1160
|
+
* records — the ONLY source of the weekly window.
|
|
1161
|
+
*
|
|
1162
|
+
* `fetchAvailableModels` cannot supply it: its `quotaInfo` carries exactly
|
|
1163
|
+
* `remainingFraction` and `resetTime`, with no window field at all, which is why
|
|
1164
|
+
* a weekly limit used to be invisible to rotation.
|
|
1165
|
+
*
|
|
1166
|
+
* Two groups can land on one family (upstream may split `3p` later), so each
|
|
1167
|
+
* window keeps the MOST pressured reading rather than letting the last group
|
|
1168
|
+
* win — the same rule the per-model merge below uses.
|
|
1169
|
+
*/
|
|
1170
|
+
function ingestQuotaGroups(groups) {
|
|
1171
|
+
const families = /* @__PURE__ */ new Map();
|
|
1172
|
+
for (const group of groups) {
|
|
1173
|
+
const keys = familiesForGroup(group);
|
|
1174
|
+
if (keys.length === 0) continue;
|
|
1175
|
+
let rolling;
|
|
1176
|
+
let weekly;
|
|
1177
|
+
for (const window of group.windows) {
|
|
1178
|
+
const kind = windowKind(window.window);
|
|
1179
|
+
if (kind === "rolling") rolling ??= window;
|
|
1180
|
+
else if (kind === "weekly") weekly ??= window;
|
|
1181
|
+
}
|
|
1182
|
+
for (const key of keys) {
|
|
1183
|
+
const current = families.get(key);
|
|
1184
|
+
const record = { ...current };
|
|
1185
|
+
if (typeof rolling?.remainingFraction === "number") {
|
|
1186
|
+
record.remainingFraction = current?.remainingFraction === void 0 ? rolling.remainingFraction : Math.min(current.remainingFraction, rolling.remainingFraction);
|
|
1187
|
+
const resetTime = earliestResetTime(current?.resetTime, rolling.resetTime ?? void 0);
|
|
1188
|
+
if (resetTime) record.resetTime = resetTime;
|
|
1189
|
+
}
|
|
1190
|
+
if (typeof weekly?.remainingFraction === "number") {
|
|
1191
|
+
record.weeklyFraction = current?.weeklyFraction === void 0 ? weekly.remainingFraction : Math.min(current.weeklyFraction, weekly.remainingFraction);
|
|
1192
|
+
const weeklyResetTime = earliestResetTime(current?.weeklyResetTime, weekly.resetTime ?? void 0);
|
|
1193
|
+
if (weeklyResetTime) record.weeklyResetTime = weeklyResetTime;
|
|
1194
|
+
}
|
|
1195
|
+
if (record.remainingFraction === void 0 && record.weeklyFraction === void 0 && !current) continue;
|
|
1196
|
+
families.set(key, record);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return Object.fromEntries(families);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Aggregate a `fetchAvailableModels` response, and when available the grouped
|
|
1203
|
+
* `retrieveUserQuotaSummary` windows, into per-family quota records.
|
|
1204
|
+
*
|
|
1205
|
+
* The two sources describe the same counters from different angles, so they are
|
|
1206
|
+
* merged rather than kept apart:
|
|
1207
|
+
* - per MODEL `quotaInfo.remainingFraction` is the rolling 5-hour counter, and
|
|
1208
|
+
* the family takes its most-pressured model (the bottleneck resets first);
|
|
1209
|
+
* - per GROUP summary windows add the weekly budget, which has no per-model
|
|
1210
|
+
* representation at all.
|
|
1211
|
+
*
|
|
1212
|
+
* `previous` is the account's existing cache, and it exists so a probe that
|
|
1213
|
+
* reports only one of the two windows cannot ERASE the other. The measured
|
|
1214
|
+
* reason: `fetchQuotaSummary` returns `[]` instead of throwing, so without the
|
|
1215
|
+
* carry-forward one timing-out endpoint would drop a known-drained weekly window
|
|
1216
|
+
* and put an exhausted account straight back into rotation.
|
|
1217
|
+
*
|
|
1218
|
+
* Carrying that value is safe next to a fresh 5-hour reading because it keeps its
|
|
1219
|
+
* own `weeklyResetTime`: once that moment passes, `isFamilyDrained` and
|
|
1220
|
+
* `parseFutureResetMs` both ignore the reading, so a stale weekly cannot outlive
|
|
1221
|
+
* the window it describes.
|
|
1071
1222
|
*/
|
|
1072
|
-
function ingestFamilyQuotas(discovered) {
|
|
1223
|
+
function ingestFamilyQuotas(discovered, groups, previous) {
|
|
1073
1224
|
const families = /* @__PURE__ */ new Map();
|
|
1225
|
+
if (groups && groups.length > 0) for (const [key, quota] of Object.entries(ingestQuotaGroups(groups))) families.set(key, { ...quota });
|
|
1074
1226
|
for (const [modelId, entry] of Object.entries(discovered.models ?? {})) {
|
|
1075
1227
|
const remaining = entry.quotaInfo?.remainingFraction;
|
|
1076
1228
|
if (typeof remaining !== "number" || !Number.isFinite(remaining)) continue;
|
|
@@ -1078,11 +1230,22 @@ function ingestFamilyQuotas(discovered) {
|
|
|
1078
1230
|
const current = families.get(key);
|
|
1079
1231
|
const resetTime = earliestResetTime(current?.resetTime, entry.quotaInfo?.resetTime);
|
|
1080
1232
|
families.set(key, {
|
|
1081
|
-
|
|
1233
|
+
...current,
|
|
1234
|
+
remainingFraction: current?.remainingFraction === void 0 ? remaining : Math.min(current.remainingFraction, remaining),
|
|
1082
1235
|
...resetTime ? { resetTime } : {},
|
|
1083
1236
|
modelCount: (current?.modelCount ?? 0) + 1
|
|
1084
1237
|
});
|
|
1085
1238
|
}
|
|
1239
|
+
if (previous) for (const [key, prev] of Object.entries(previous)) {
|
|
1240
|
+
const current = families.get(key);
|
|
1241
|
+
if (prev.weeklyFraction === void 0) continue;
|
|
1242
|
+
if (current?.weeklyFraction !== void 0) continue;
|
|
1243
|
+
families.set(key, {
|
|
1244
|
+
...current,
|
|
1245
|
+
weeklyFraction: prev.weeklyFraction,
|
|
1246
|
+
...current?.weeklyResetTime === void 0 && prev.weeklyResetTime !== void 0 ? { weeklyResetTime: prev.weeklyResetTime } : {}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1086
1249
|
return Object.fromEntries(families);
|
|
1087
1250
|
}
|
|
1088
1251
|
/** The quota record for one family, or the most-pressured family when the model is unknown. */
|
|
@@ -1099,7 +1262,8 @@ function familyQuotaFor(account, family) {
|
|
|
1099
1262
|
/** Whether the account's quota cache needs a refresh (missing, or past its health-based TTL). */
|
|
1100
1263
|
function isQuotaStale(account, now = Date.now()) {
|
|
1101
1264
|
if (!account.cachedQuota || !account.cachedQuotaUpdatedAt) return true;
|
|
1102
|
-
const
|
|
1265
|
+
const mostPressured = familyQuotaFor(account);
|
|
1266
|
+
const ttl = computeSoftQuotaCacheTtlMs(resetInPast(mostPressured?.resetTime, now) ? void 0 : mostPressured?.remainingFraction, resetInPast(mostPressured?.weeklyResetTime, now) ? void 0 : mostPressured?.weeklyFraction);
|
|
1103
1267
|
return now - account.cachedQuotaUpdatedAt > ttl;
|
|
1104
1268
|
}
|
|
1105
1269
|
/** How long a measured 5h/weekly window snapshot stays fresh. */
|
|
@@ -1125,20 +1289,46 @@ function isLimitsStale(account, now = Date.now()) {
|
|
|
1125
1289
|
if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt)) return true;
|
|
1126
1290
|
return now - updatedAt > LIMITS_CACHE_TTL_MS;
|
|
1127
1291
|
}
|
|
1128
|
-
/** Whether
|
|
1292
|
+
/** Whether a reset moment has already passed. An absent or malformed one has not. */
|
|
1293
|
+
function resetInPast(resetTime, now) {
|
|
1294
|
+
if (!resetTime) return false;
|
|
1295
|
+
const reset = Date.parse(resetTime);
|
|
1296
|
+
return !Number.isNaN(reset) && reset <= now;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Whether the requested family on this account is soft-quota-exhausted.
|
|
1300
|
+
*
|
|
1301
|
+
* BOTH windows are checked, because they refill on different clocks. The 5-hour
|
|
1302
|
+
* bucket can be nearly empty while the weekly budget is untouched, and the
|
|
1303
|
+
* reverse — a spent weekly budget stays spent across four 5-hour refills. Before
|
|
1304
|
+
* this, only the 5-hour reading was consulted, so an account whose week was over
|
|
1305
|
+
* kept being selected until a real request failed.
|
|
1306
|
+
*
|
|
1307
|
+
* A window whose reset has already passed is IGNORED rather than read: its
|
|
1308
|
+
* fraction describes a window that no longer exists, so the account stays
|
|
1309
|
+
* selectable until the next measurement replaces the stale value.
|
|
1310
|
+
*/
|
|
1129
1311
|
function isFamilyDrained(account, family, now = Date.now()) {
|
|
1130
1312
|
const quota = familyQuotaFor(account, family);
|
|
1131
|
-
if (!quota
|
|
1132
|
-
if (quota.resetTime)
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
return quota.remainingFraction < SOFT_QUOTA_THRESHOLD;
|
|
1313
|
+
if (!quota) return false;
|
|
1314
|
+
if (typeof quota.remainingFraction === "number" && !resetInPast(quota.resetTime, now) && quota.remainingFraction < .15) return true;
|
|
1315
|
+
if (typeof quota.weeklyFraction === "number" && !resetInPast(quota.weeklyResetTime, now) && quota.weeklyFraction <= .01) return true;
|
|
1316
|
+
return false;
|
|
1137
1317
|
}
|
|
1138
1318
|
/**
|
|
1139
1319
|
* Required drain rate: headroomFraction / remainingHours — how fast the
|
|
1140
1320
|
* family's remaining quota must be consumed to avoid expiring unused at its
|
|
1141
1321
|
* reset (mirrors AuthStorage.#computeWindowRequiredDrain with a daily window).
|
|
1322
|
+
*
|
|
1323
|
+
* DELIBERATELY FIVE-HOUR ONLY, and this is a decision rather than an omission.
|
|
1324
|
+
* The weekly window influences `blockedUntil` and `hot` instead of the ranking
|
|
1325
|
+
* ORDER, because a week is not a spend-by deadline: the `DAY_MS` clamp below
|
|
1326
|
+
* caps the horizon at 24h, so a weekly reset ~5 days out would report ~24h of
|
|
1327
|
+
* urgency for a budget that cannot be spent faster to any benefit — and every
|
|
1328
|
+
* family would then be ranked by a number that no longer distinguishes them.
|
|
1329
|
+
* Making this weekly-aware therefore means replacing that clamp with a horizon
|
|
1330
|
+
* the weekly window can actually express, not adding a second fraction to the
|
|
1331
|
+
* numerator.
|
|
1142
1332
|
*/
|
|
1143
1333
|
function requiredDrainFor(quota, now = Date.now()) {
|
|
1144
1334
|
const remaining = quota?.remainingFraction;
|
|
@@ -1174,13 +1364,17 @@ function rankPoolCandidates(entries, modelId, now = Date.now(), startIndex = 0)
|
|
|
1174
1364
|
const quota = familyQuotaFor(account, family);
|
|
1175
1365
|
const remaining = quota?.remainingFraction;
|
|
1176
1366
|
const used = typeof remaining === "number" ? Math.min(Math.max(1 - remaining, 0), 1) : void 0;
|
|
1367
|
+
const weeklyRemaining = quota?.weeklyFraction;
|
|
1368
|
+
const weeklyUsed = typeof weeklyRemaining === "number" ? Math.min(Math.max(1 - weeklyRemaining, 0), 1) : void 0;
|
|
1177
1369
|
let blockedUntil = null;
|
|
1178
1370
|
if (account.coolingDownUntil && account.coolingDownUntil > now) blockedUntil = account.coolingDownUntil;
|
|
1179
1371
|
const familyLimit = account.rateLimitResetTimes?.[familyKeyOf(modelId)];
|
|
1180
1372
|
if (familyLimit !== void 0 && familyLimit > now) blockedUntil = blockedUntil === null ? familyLimit : Math.max(blockedUntil, familyLimit);
|
|
1181
|
-
if (blockedUntil === null && quota
|
|
1182
|
-
const resetMs = parseFutureResetMs(quota.resetTime, now);
|
|
1373
|
+
if (blockedUntil === null && quota) {
|
|
1374
|
+
const resetMs = typeof remaining === "number" && remaining <= 0 ? parseFutureResetMs(quota.resetTime, now) : void 0;
|
|
1183
1375
|
if (resetMs !== void 0) blockedUntil = resetMs;
|
|
1376
|
+
const weeklyResetMs = typeof weeklyRemaining === "number" && weeklyRemaining <= 0 ? parseFutureResetMs(quota.weeklyResetTime, now) : void 0;
|
|
1377
|
+
if (weeklyResetMs !== void 0) blockedUntil = blockedUntil === null ? weeklyResetMs : Math.max(blockedUntil, weeklyResetMs);
|
|
1184
1378
|
}
|
|
1185
1379
|
return {
|
|
1186
1380
|
account,
|
|
@@ -1189,8 +1383,8 @@ function rankPoolCandidates(entries, modelId, now = Date.now(), startIndex = 0)
|
|
|
1189
1383
|
blockedUntil,
|
|
1190
1384
|
usedFraction: used,
|
|
1191
1385
|
requiredDrain: requiredDrainFor(quota, now),
|
|
1192
|
-
hot: used !== void 0 && used >= .85,
|
|
1193
|
-
measured: used !== void 0
|
|
1386
|
+
hot: used !== void 0 && used >= .85 || weeklyUsed !== void 0 && weeklyUsed >= .85,
|
|
1387
|
+
measured: used !== void 0 || weeklyUsed !== void 0
|
|
1194
1388
|
};
|
|
1195
1389
|
});
|
|
1196
1390
|
candidates.sort((left, right) => {
|
|
@@ -1644,7 +1838,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1644
1838
|
key,
|
|
1645
1839
|
ok: false
|
|
1646
1840
|
};
|
|
1647
|
-
const { fetchQuotaSummary } = await import("./quota-summary-
|
|
1841
|
+
const { fetchQuotaSummary } = await import("./quota-summary-adID82I2.mjs");
|
|
1648
1842
|
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1649
1843
|
const bounded = (input, init) => {
|
|
1650
1844
|
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
@@ -1716,16 +1910,19 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1716
1910
|
try {
|
|
1717
1911
|
const auth = await this.accessTokenFor(account);
|
|
1718
1912
|
if (!auth) return null;
|
|
1719
|
-
const { fetchAvailableModels } = await import("./models-
|
|
1913
|
+
const { fetchAvailableModels } = await import("./models-Qn0-sh6X.mjs").then((n) => n.r);
|
|
1914
|
+
const { fetchQuotaSummary } = await import("./quota-summary-adID82I2.mjs");
|
|
1720
1915
|
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1721
|
-
const
|
|
1916
|
+
const bounded = (input, init) => {
|
|
1722
1917
|
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1723
1918
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1724
1919
|
return routed(input, {
|
|
1725
1920
|
...init,
|
|
1726
1921
|
signal
|
|
1727
1922
|
});
|
|
1728
|
-
}
|
|
1923
|
+
};
|
|
1924
|
+
const [discovered, groups] = await Promise.all([fetchAvailableModels(auth.access, account.projectId, bounded), fetchQuotaSummary(auth.access, account.projectId, bounded)]);
|
|
1925
|
+
const quotas = ingestFamilyQuotas(discovered, groups, account.cachedQuota);
|
|
1729
1926
|
return {
|
|
1730
1927
|
key,
|
|
1731
1928
|
quotas,
|
|
@@ -1795,9 +1992,13 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1795
1992
|
const quotaExhausted = (account) => {
|
|
1796
1993
|
if (account.cooldownReason === "quota-exhausted" && (account.coolingDownUntil ?? 0) > now) return true;
|
|
1797
1994
|
const quota = familyQuotaFor(account, family);
|
|
1798
|
-
if (
|
|
1799
|
-
const
|
|
1800
|
-
|
|
1995
|
+
if (!quota) return false;
|
|
1996
|
+
const spent = (fraction, resetTime) => {
|
|
1997
|
+
if (typeof fraction !== "number" || fraction > 0 || !resetTime) return false;
|
|
1998
|
+
const resetAt = Date.parse(resetTime);
|
|
1999
|
+
return !Number.isNaN(resetAt) && resetAt > now;
|
|
2000
|
+
};
|
|
2001
|
+
return spent(quota.remainingFraction, quota.resetTime) || spent(quota.weeklyFraction, quota.weeklyResetTime);
|
|
1801
2002
|
};
|
|
1802
2003
|
const retryable = ranked.filter((candidate) => !quotaExhausted(candidate.account));
|
|
1803
2004
|
const blocked = retryable.length > 0 ? retryable : ranked;
|
|
@@ -1885,7 +2086,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1885
2086
|
}
|
|
1886
2087
|
const key = this.accountKey(picked.account);
|
|
1887
2088
|
if (!picked.account.projectId && !this.projectRetryFailed.has(key)) try {
|
|
1888
|
-
const { loadCodeAssist } = await import("./exchange-
|
|
2089
|
+
const { loadCodeAssist } = await import("./exchange-LzEyxve1.mjs").then((n) => n.n);
|
|
1889
2090
|
const { projectId } = await loadCodeAssist(auth.access, { proxyUrl: picked.account.proxy });
|
|
1890
2091
|
if (projectId) {
|
|
1891
2092
|
await this.store.mutate((s) => {
|
|
@@ -2041,7 +2242,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
2041
2242
|
try {
|
|
2042
2243
|
const account = session.account.email ?? session.account.id;
|
|
2043
2244
|
const { toAgyRequestBody } = await import("./translate-BQu8HLvB.mjs").then((n) => n.n);
|
|
2044
|
-
const { fetchAgyFirstOk } = await import("./constants-
|
|
2245
|
+
const { fetchAgyFirstOk } = await import("./constants-BpyRirvk.mjs").then((n) => n.f);
|
|
2045
2246
|
const { parseAgySse } = await import("./parse-Bs4rPkHA.mjs").then((n) => n.n);
|
|
2046
2247
|
const requestId = generateAntigravityRequestId();
|
|
2047
2248
|
const body = toAgyRequestBody({
|
|
@@ -2483,4 +2684,4 @@ var JsonAccountStore = class {
|
|
|
2483
2684
|
//#endregion
|
|
2484
2685
|
export { resolveMasterKeyCodec as _, generateFingerprint as a, describeFetchError as b, resolveAntigravityVersionBounded as c, MASTER_KEY_REF as d, createAesGcmCodec as f, resolveDshHome as g, persistMasterKey as h, isAgyDisabled as i, clearExpiredState as l, loadMasterKey as m, maskProxyUrl as n, recordFingerprintVersion as o, deriveKey as p, AgySessionManager as r, resolveAntigravityVersion as s, JsonAccountStore as t, pickProbeProxyUrl as u, classifyFetchError as v, isSessionAccumulationOverflow as x, classifyHttpError as y };
|
|
2485
2686
|
|
|
2486
|
-
//# sourceMappingURL=accounts-
|
|
2687
|
+
//# sourceMappingURL=accounts-CKWybAfc.mjs.map
|