dsh-agy 0.3.1 → 0.4.0
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-BN4h0AU4.mjs} +446 -63
- package/lib/accounts-BN4h0AU4.mjs.map +1 -0
- package/lib/cli/index.mjs +5 -5
- package/lib/client.js +25 -3
- 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-N7p9Cm-3.mjs} +58 -14
- package/lib/plugin-common-N7p9Cm-3.mjs.map +1 -0
- package/lib/{quota-summary-5CypVffd.mjs → quota-summary-adID82I2.mjs} +9 -12
- package/lib/quota-summary-adID82I2.mjs.map +1 -0
- package/lib/{translate-BQu8HLvB.mjs → translate-YhGDUuVa.mjs} +157 -23
- package/lib/translate-YhGDUuVa.mjs.map +1 -0
- package/lib/web/plugin.d.mts +28 -1
- package/lib/web/plugin.d.mts.map +1 -1
- package/lib/web/plugin.mjs +35 -19
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +9 -4
- package/lib/accounts-CkaHhb5H.mjs.map +0 -1
- package/lib/constants-Db4tmyfb.mjs.map +0 -1
- package/lib/plugin-common-B1MG00JT.mjs.map +0 -1
- package/lib/quota-summary-5CypVffd.mjs.map +0 -1
- package/lib/translate-BQu8HLvB.mjs.map +0 -1
|
@@ -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,195 @@ 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
|
+
* Window-aware as of #56: a bucket id resolves a family only when the window it
|
|
1096
|
+
* describes is one `CachedQuota` can hold AND the id agrees with the token (see
|
|
1097
|
+
* `agreedWindowKind`). The prefix and the window were two independent decisions
|
|
1098
|
+
* once, and an unrecognized window still claimed a family.
|
|
1099
|
+
*/
|
|
1100
|
+
function familiesForBucketId(bucketId, window) {
|
|
1101
|
+
const id = bucketId.toLowerCase();
|
|
1102
|
+
const families = [];
|
|
1103
|
+
if (id.startsWith("gemini-")) families.push("google");
|
|
1104
|
+
else if (id.startsWith("3p-") || id.startsWith("third_party-")) families.push("anthropic", "openai");
|
|
1105
|
+
else if (id.startsWith("claude-")) families.push("anthropic");
|
|
1106
|
+
else if (id.startsWith("gpt-") || id.startsWith("openai-")) families.push("openai");
|
|
1107
|
+
if (families.length === 0) return [];
|
|
1108
|
+
const declared = window === void 0 ? advertisedWindowOf(id) : window.toLowerCase();
|
|
1109
|
+
if (declared === void 0 || agreedWindowKind(id, declared) === void 0) return [];
|
|
1110
|
+
return families;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* The families a group's LABEL covers, consulted only when no bucket id was
|
|
1114
|
+
* recognizable.
|
|
1115
|
+
*
|
|
1116
|
+
* Upstream's groups are its own taxonomy, not ours, and the measured labels are
|
|
1117
|
+
* `Gemini Models` and `Claude and GPT models` — the second spans two families,
|
|
1118
|
+
* so this is a 1-to-N mapping. It is a FALLBACK, not the primary rule: matching
|
|
1119
|
+
* words in a display string is the fragile half, and it survives only so that a
|
|
1120
|
+
* bucket id upstream renames still lands somewhere sensible.
|
|
1121
|
+
*
|
|
1122
|
+
* A label naming nothing we recognize maps to NO family deliberately.
|
|
1123
|
+
* Attributing an unknown group's budget to `google` would block healthy
|
|
1124
|
+
* accounts; leaving it unmapped only means that family stays unmeasured, which
|
|
1125
|
+
* is exactly the state before this function existed.
|
|
1126
|
+
*/
|
|
1127
|
+
function familiesForGroupName(name) {
|
|
1128
|
+
const label = name.toLowerCase();
|
|
1129
|
+
const families = [];
|
|
1130
|
+
if (label.includes("gemini") || label.includes("google")) families.push("google");
|
|
1131
|
+
const claude = label.includes("claude");
|
|
1132
|
+
const gpt = label.includes("gpt");
|
|
1133
|
+
if (claude && gpt) families.push("anthropic", "openai");
|
|
1134
|
+
else if (claude) families.push("anthropic");
|
|
1135
|
+
else if (gpt) families.push("openai");
|
|
1136
|
+
return families;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Which of the two tracked windows an upstream token names, or `undefined` for a
|
|
1140
|
+
* token `CachedQuota` has no field for (`daily`, `monthly`).
|
|
1141
|
+
*
|
|
1142
|
+
* Read from `QUOTA_WINDOWS` (`types.ts`), the SAME table `adapter/quota-summary`
|
|
1143
|
+
* orders windows by. These were two independent tables once, and the gap between
|
|
1144
|
+
* them was a real defect: `daily` was orderable in the parser but unknown here, so
|
|
1145
|
+
* the group materialized an empty record that overwrote a live measurement. One
|
|
1146
|
+
* table makes that drift structurally impossible rather than merely tested for.
|
|
1147
|
+
*
|
|
1148
|
+
* Lookup is exact on the measured vocabulary (`5h`, `weekly`). A variant spelling
|
|
1149
|
+
* therefore classifies as unknown — which is safe, because the guard in
|
|
1150
|
+
* `ingestQuotaGroups` then skips the record rather than emptying it, and the
|
|
1151
|
+
* per-model probe still supplies the 5-hour reading.
|
|
1152
|
+
*/
|
|
1153
|
+
function windowKind(window) {
|
|
1154
|
+
return QUOTA_WINDOWS[window.toLowerCase()]?.kind;
|
|
1155
|
+
}
|
|
1156
|
+
/** The window token a bucket id advertises, when it ends with one upstream names. */
|
|
1157
|
+
function advertisedWindowOf(bucketId) {
|
|
1158
|
+
const id = bucketId.toLowerCase();
|
|
1159
|
+
const token = id.slice(id.lastIndexOf("-") + 1);
|
|
1160
|
+
return QUOTA_WINDOWS[token] ? token : void 0;
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* The field a window belongs in, decided from an AGREED (id, token) pair — or
|
|
1164
|
+
* `undefined` when the pair cannot be agreed.
|
|
1165
|
+
*
|
|
1166
|
+
* The family used to come from the bucket id's prefix while the field came from
|
|
1167
|
+
* the window token, as two independent decisions nothing checked against each
|
|
1168
|
+
* other. A bucket whose window the cache cannot hold still claimed the family,
|
|
1169
|
+
* and its reading was then filed by whichever field the TOKEN named. `gemini-weekly`
|
|
1170
|
+
* declaring `5h` is the shape that puts a multi-day weekly fraction into
|
|
1171
|
+
* `remainingFraction` (the 5-hour field), where `isFamilyDrained` and
|
|
1172
|
+
* `parseFutureResetMs` block the account for days on a window they misread.
|
|
1173
|
+
*
|
|
1174
|
+
* Refusing an unagreed pair is deliberately the strict option: the permissive
|
|
1175
|
+
* mapping is exactly the coupling that let two window vocabularies drift into
|
|
1176
|
+
* destroying a measurement. A bucket id that advertises no recognizable token is
|
|
1177
|
+
* held to the declared one alone, so a rename does not silently disable the cache.
|
|
1071
1178
|
*/
|
|
1072
|
-
function
|
|
1179
|
+
function agreedWindowKind(bucketId, window) {
|
|
1180
|
+
const declared = window.toLowerCase();
|
|
1181
|
+
const kind = windowKind(declared);
|
|
1182
|
+
if (kind === void 0) return void 0;
|
|
1183
|
+
const advertised = advertisedWindowOf(bucketId);
|
|
1184
|
+
if (advertised !== void 0 && advertised !== declared) return void 0;
|
|
1185
|
+
return kind;
|
|
1186
|
+
}
|
|
1187
|
+
/** The families one group belongs to: its bucket ids first, its label second. */
|
|
1188
|
+
function familiesForGroup(group) {
|
|
1189
|
+
const families = [];
|
|
1190
|
+
const add = (family) => {
|
|
1191
|
+
if (!families.includes(family)) families.push(family);
|
|
1192
|
+
};
|
|
1193
|
+
for (const window of group.windows) for (const family of familiesForBucketId(window.bucketId, window.window)) add(family);
|
|
1194
|
+
if (families.length === 0) for (const family of familiesForGroupName(group.name)) add(family);
|
|
1195
|
+
return families;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Aggregate the grouped `retrieveUserQuotaSummary` windows into per-family
|
|
1199
|
+
* records — the ONLY source of the weekly window.
|
|
1200
|
+
*
|
|
1201
|
+
* `fetchAvailableModels` cannot supply it: its `quotaInfo` carries exactly
|
|
1202
|
+
* `remainingFraction` and `resetTime`, with no window field at all, which is why
|
|
1203
|
+
* a weekly limit used to be invisible to rotation.
|
|
1204
|
+
*
|
|
1205
|
+
* Two groups can land on one family (upstream may split `3p` later), so the
|
|
1206
|
+
* family record keeps the MOST pressured reading of each window across those
|
|
1207
|
+
* groups (`Math.min` below) rather than letting the last group win — the same
|
|
1208
|
+
* rule the per-model merge uses.
|
|
1209
|
+
*
|
|
1210
|
+
* WITHIN one group the rule is deliberately different: `??=` keeps upstream's
|
|
1211
|
+
* FIRST bucket of each window, because a group is upstream's own 5h + weekly
|
|
1212
|
+
* pair and a duplicate would otherwise depend on upstream's ordering. "Most
|
|
1213
|
+
* pressured" therefore applies to two GROUPS landing on one family, not to two
|
|
1214
|
+
* buckets inside one group.
|
|
1215
|
+
*/
|
|
1216
|
+
function ingestQuotaGroups(groups) {
|
|
1073
1217
|
const families = /* @__PURE__ */ new Map();
|
|
1218
|
+
for (const group of groups) {
|
|
1219
|
+
const keys = familiesForGroup(group);
|
|
1220
|
+
if (keys.length === 0) continue;
|
|
1221
|
+
let rolling;
|
|
1222
|
+
let weekly;
|
|
1223
|
+
for (const window of group.windows) {
|
|
1224
|
+
const kind = agreedWindowKind(window.bucketId, window.window);
|
|
1225
|
+
if (kind === "rolling") rolling ??= window;
|
|
1226
|
+
else if (kind === "weekly") weekly ??= window;
|
|
1227
|
+
}
|
|
1228
|
+
for (const key of keys) {
|
|
1229
|
+
const current = families.get(key);
|
|
1230
|
+
const record = { ...current };
|
|
1231
|
+
if (typeof rolling?.remainingFraction === "number") {
|
|
1232
|
+
record.remainingFraction = current?.remainingFraction === void 0 ? rolling.remainingFraction : Math.min(current.remainingFraction, rolling.remainingFraction);
|
|
1233
|
+
const resetTime = earliestResetTime(current?.resetTime, rolling.resetTime ?? void 0);
|
|
1234
|
+
if (resetTime) record.resetTime = resetTime;
|
|
1235
|
+
}
|
|
1236
|
+
if (typeof weekly?.remainingFraction === "number") {
|
|
1237
|
+
record.weeklyFraction = current?.weeklyFraction === void 0 ? weekly.remainingFraction : Math.min(current.weeklyFraction, weekly.remainingFraction);
|
|
1238
|
+
const weeklyResetTime = earliestResetTime(current?.weeklyResetTime, weekly.resetTime ?? void 0);
|
|
1239
|
+
if (weeklyResetTime) record.weeklyResetTime = weeklyResetTime;
|
|
1240
|
+
}
|
|
1241
|
+
if (record.remainingFraction === void 0 && record.weeklyFraction === void 0 && !current) continue;
|
|
1242
|
+
families.set(key, record);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return Object.fromEntries(families);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Aggregate a `fetchAvailableModels` response, and when available the grouped
|
|
1249
|
+
* `retrieveUserQuotaSummary` windows, into per-family quota records.
|
|
1250
|
+
*
|
|
1251
|
+
* The two sources describe the same counters from different angles, so they are
|
|
1252
|
+
* merged rather than kept apart:
|
|
1253
|
+
* - per MODEL `quotaInfo.remainingFraction` is the rolling 5-hour counter, and
|
|
1254
|
+
* the family takes its most-pressured model (the bottleneck resets first);
|
|
1255
|
+
* - per GROUP summary windows add the weekly budget, which has no per-model
|
|
1256
|
+
* representation at all.
|
|
1257
|
+
*
|
|
1258
|
+
* `previous` is the account's existing cache, and it exists so a probe that
|
|
1259
|
+
* reports only one of the two windows cannot ERASE the other. The measured
|
|
1260
|
+
* reason: `fetchQuotaSummary` returns `[]` instead of throwing, so without the
|
|
1261
|
+
* carry-forward one timing-out endpoint would drop a known-drained weekly window
|
|
1262
|
+
* and put an exhausted account straight back into rotation.
|
|
1263
|
+
*
|
|
1264
|
+
* Carrying that value is safe next to a fresh 5-hour reading because it keeps its
|
|
1265
|
+
* own `weeklyResetTime`: once that moment passes, `isFamilyDrained` and
|
|
1266
|
+
* `parseFutureResetMs` both ignore the reading, so a stale weekly cannot outlive
|
|
1267
|
+
* the window it describes.
|
|
1268
|
+
*/
|
|
1269
|
+
function ingestFamilyQuotas(discovered, groups, previous) {
|
|
1270
|
+
const families = /* @__PURE__ */ new Map();
|
|
1271
|
+
if (groups && groups.length > 0) for (const [key, quota] of Object.entries(ingestQuotaGroups(groups))) families.set(key, { ...quota });
|
|
1074
1272
|
for (const [modelId, entry] of Object.entries(discovered.models ?? {})) {
|
|
1075
1273
|
const remaining = entry.quotaInfo?.remainingFraction;
|
|
1076
1274
|
if (typeof remaining !== "number" || !Number.isFinite(remaining)) continue;
|
|
@@ -1078,28 +1276,89 @@ function ingestFamilyQuotas(discovered) {
|
|
|
1078
1276
|
const current = families.get(key);
|
|
1079
1277
|
const resetTime = earliestResetTime(current?.resetTime, entry.quotaInfo?.resetTime);
|
|
1080
1278
|
families.set(key, {
|
|
1081
|
-
|
|
1279
|
+
...current,
|
|
1280
|
+
remainingFraction: current?.remainingFraction === void 0 ? remaining : Math.min(current.remainingFraction, remaining),
|
|
1082
1281
|
...resetTime ? { resetTime } : {},
|
|
1083
1282
|
modelCount: (current?.modelCount ?? 0) + 1
|
|
1084
1283
|
});
|
|
1085
1284
|
}
|
|
1285
|
+
if (previous) for (const [key, prev] of Object.entries(previous)) {
|
|
1286
|
+
const current = families.get(key);
|
|
1287
|
+
if (prev.weeklyFraction === void 0) continue;
|
|
1288
|
+
if (current?.weeklyFraction !== void 0) continue;
|
|
1289
|
+
families.set(key, {
|
|
1290
|
+
...current,
|
|
1291
|
+
weeklyFraction: prev.weeklyFraction,
|
|
1292
|
+
...current?.weeklyResetTime === void 0 && prev.weeklyResetTime !== void 0 ? { weeklyResetTime: prev.weeklyResetTime } : {}
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1086
1295
|
return Object.fromEntries(families);
|
|
1087
1296
|
}
|
|
1088
|
-
/**
|
|
1089
|
-
|
|
1297
|
+
/**
|
|
1298
|
+
* The lowest fraction a record reports across its LIVE windows — its overall
|
|
1299
|
+
* pressure.
|
|
1300
|
+
*
|
|
1301
|
+
* A window whose reset has already passed describes a window that no longer
|
|
1302
|
+
* exists, so it does not count — the same rule `isQuotaStale` applies when it
|
|
1303
|
+
* sizes the TTL from the selected record. Scoring a dead window would let one
|
|
1304
|
+
* family's expired weekly reading win the selection and represent the account,
|
|
1305
|
+
* masking another family's live pressure (issue #69: a dead weekly at 0.004
|
|
1306
|
+
* beat a live 0.2 and stretched the refresh TTL from 5 to 15 minutes).
|
|
1307
|
+
*
|
|
1308
|
+
* `undefined` means the record carries no usable LIVE reading at all, which is
|
|
1309
|
+
* deliberately different from a measured zero: only a real number can size a
|
|
1310
|
+
* refresh interval.
|
|
1311
|
+
*/
|
|
1312
|
+
function pressureScore(entry, now) {
|
|
1313
|
+
const fractions = [];
|
|
1314
|
+
if (typeof entry.remainingFraction === "number" && Number.isFinite(entry.remainingFraction) && !resetInPast(entry.resetTime, now)) fractions.push(entry.remainingFraction);
|
|
1315
|
+
if (typeof entry.weeklyFraction === "number" && Number.isFinite(entry.weeklyFraction) && !resetInPast(entry.weeklyResetTime, now)) fractions.push(entry.weeklyFraction);
|
|
1316
|
+
return fractions.length === 0 ? void 0 : Math.min(...fractions);
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* The quota record for one family, or the most-pressured family when the model
|
|
1320
|
+
* is unknown.
|
|
1321
|
+
*
|
|
1322
|
+
* "Most pressured" compares BOTH windows and reports the record whose LOWEST
|
|
1323
|
+
* window is the lowest of all of them. That matters because `isQuotaStale` sizes
|
|
1324
|
+
* the refresh TTL from whichever record this returns: a family at 0.9 / weekly
|
|
1325
|
+
* 0.004 is more pressured than one at 0.2, and comparing the 5-hour fraction
|
|
1326
|
+
* alone picked the 0.2 record — so the weekly-driven 60s TTL was never applied
|
|
1327
|
+
* to the family actually being requested and a spent week was discovered late.
|
|
1328
|
+
*
|
|
1329
|
+
* A record carrying ONLY a weekly reading participates for the same reason: #48
|
|
1330
|
+
* produces that shape legitimately (the anthropic/openai entries of a
|
|
1331
|
+
* Gemini-only pool), and the old `typeof entry.remainingFraction !== 'number'`
|
|
1332
|
+
* guard skipped it outright, leaving the week invisible to TTL sizing.
|
|
1333
|
+
*
|
|
1334
|
+
* Selection considers LIVE windows only (`pressureScore` drops a window whose
|
|
1335
|
+
* reset has passed) — but the with-family path returns the record as-is either
|
|
1336
|
+
* way, because its consumers guard each window individually.
|
|
1337
|
+
*
|
|
1338
|
+
* @param account - the account whose cache to read.
|
|
1339
|
+
* @param family - the exact family's record; omit to select the most pressured.
|
|
1340
|
+
* @param now - current time (Unix ms), which decides whether a window is live.
|
|
1341
|
+
*/
|
|
1342
|
+
function familyQuotaFor(account, family, now = Date.now()) {
|
|
1090
1343
|
const cache = account.cachedQuota ?? {};
|
|
1091
1344
|
if (family) return cache[family];
|
|
1092
1345
|
let worst;
|
|
1346
|
+
let worstScore;
|
|
1093
1347
|
for (const entry of Object.values(cache)) {
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1348
|
+
const score = pressureScore(entry, now);
|
|
1349
|
+
if (score === void 0) continue;
|
|
1350
|
+
if (worstScore === void 0 || score < worstScore) {
|
|
1351
|
+
worst = entry;
|
|
1352
|
+
worstScore = score;
|
|
1353
|
+
}
|
|
1096
1354
|
}
|
|
1097
1355
|
return worst;
|
|
1098
1356
|
}
|
|
1099
1357
|
/** Whether the account's quota cache needs a refresh (missing, or past its health-based TTL). */
|
|
1100
1358
|
function isQuotaStale(account, now = Date.now()) {
|
|
1101
1359
|
if (!account.cachedQuota || !account.cachedQuotaUpdatedAt) return true;
|
|
1102
|
-
const
|
|
1360
|
+
const mostPressured = familyQuotaFor(account, void 0, now);
|
|
1361
|
+
const ttl = computeSoftQuotaCacheTtlMs(resetInPast(mostPressured?.resetTime, now) ? void 0 : mostPressured?.remainingFraction, resetInPast(mostPressured?.weeklyResetTime, now) ? void 0 : mostPressured?.weeklyFraction);
|
|
1103
1362
|
return now - account.cachedQuotaUpdatedAt > ttl;
|
|
1104
1363
|
}
|
|
1105
1364
|
/** How long a measured 5h/weekly window snapshot stays fresh. */
|
|
@@ -1125,20 +1384,26 @@ function isLimitsStale(account, now = Date.now()) {
|
|
|
1125
1384
|
if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt)) return true;
|
|
1126
1385
|
return now - updatedAt > LIMITS_CACHE_TTL_MS;
|
|
1127
1386
|
}
|
|
1128
|
-
/** Whether
|
|
1129
|
-
function
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const reset = Date.parse(quota.resetTime);
|
|
1134
|
-
if (!Number.isNaN(reset) && reset <= now) return false;
|
|
1135
|
-
}
|
|
1136
|
-
return quota.remainingFraction < SOFT_QUOTA_THRESHOLD;
|
|
1387
|
+
/** Whether a reset moment has already passed. An absent or malformed one has not. */
|
|
1388
|
+
function resetInPast(resetTime, now) {
|
|
1389
|
+
if (!resetTime) return false;
|
|
1390
|
+
const reset = Date.parse(resetTime);
|
|
1391
|
+
return !Number.isNaN(reset) && reset <= now;
|
|
1137
1392
|
}
|
|
1138
1393
|
/**
|
|
1139
1394
|
* Required drain rate: headroomFraction / remainingHours — how fast the
|
|
1140
1395
|
* family's remaining quota must be consumed to avoid expiring unused at its
|
|
1141
1396
|
* reset (mirrors AuthStorage.#computeWindowRequiredDrain with a daily window).
|
|
1397
|
+
*
|
|
1398
|
+
* DELIBERATELY FIVE-HOUR ONLY, and this is a decision rather than an omission.
|
|
1399
|
+
* The weekly window influences `blockedUntil` and `hot` instead of the ranking
|
|
1400
|
+
* ORDER, because a week is not a spend-by deadline: the `DAY_MS` clamp below
|
|
1401
|
+
* caps the horizon at 24h, so a weekly reset ~5 days out would report ~24h of
|
|
1402
|
+
* urgency for a budget that cannot be spent faster to any benefit — and every
|
|
1403
|
+
* family would then be ranked by a number that no longer distinguishes them.
|
|
1404
|
+
* Making this weekly-aware therefore means replacing that clamp with a horizon
|
|
1405
|
+
* the weekly window can actually express, not adding a second fraction to the
|
|
1406
|
+
* numerator.
|
|
1142
1407
|
*/
|
|
1143
1408
|
function requiredDrainFor(quota, now = Date.now()) {
|
|
1144
1409
|
const remaining = quota?.remainingFraction;
|
|
@@ -1160,6 +1425,32 @@ function parseFutureResetMs(resetTime, now) {
|
|
|
1160
1425
|
return reset;
|
|
1161
1426
|
}
|
|
1162
1427
|
/**
|
|
1428
|
+
* Whether the requested family on this account is effectively quota-exhausted
|
|
1429
|
+
* for account STEERING (affinity pins, the active-account preference): an
|
|
1430
|
+
* explicit quota-exhausted cooldown in force, a spent 5-hour window
|
|
1431
|
+
* (`remainingFraction <= 0` with a future reset), or a weekly window at or
|
|
1432
|
+
* below `WEEKLY_QUOTA_THRESHOLD` — a spent week refills in days, so its rule
|
|
1433
|
+
* mirrors `isFamilyDrained`'s weekly branch rather than the block wall.
|
|
1434
|
+
*
|
|
1435
|
+
* Deliberately a DIFFERENT predicate from the one in `pickAccount`'s `!picked`
|
|
1436
|
+
* branch, which applies a strict zero to BOTH windows to mirror
|
|
1437
|
+
* `rankPoolCandidates`' block wall when classifying `AgyPoolBlockedError`; and
|
|
1438
|
+
* deliberately NARROWER than `isFamilyDrained`'s 5-hour rule
|
|
1439
|
+
* (`SOFT_QUOTA_THRESHOLD`): account prioritization holds a chosen account
|
|
1440
|
+
* until it is actually spent, not until ranking would prefer another one.
|
|
1441
|
+
*/
|
|
1442
|
+
function isFamilyQuotaExhausted(account, family, now = Date.now()) {
|
|
1443
|
+
if (account.cooldownReason === "quota-exhausted" && (account.coolingDownUntil ?? 0) > now) return true;
|
|
1444
|
+
const quota = familyQuotaFor(account, family);
|
|
1445
|
+
if (!quota) return false;
|
|
1446
|
+
const spent = (fraction, resetTime, threshold = 0) => {
|
|
1447
|
+
if (typeof fraction !== "number" || fraction > threshold || !resetTime) return false;
|
|
1448
|
+
const resetMs = parseFutureResetMs(resetTime, now);
|
|
1449
|
+
return resetMs !== void 0 && resetMs > now;
|
|
1450
|
+
};
|
|
1451
|
+
return spent(quota.remainingFraction, quota.resetTime, 0) || spent(quota.weeklyFraction, quota.weeklyResetTime, .01);
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1163
1454
|
* Rank pool candidates for one request, mirroring AuthStorage's antigravity
|
|
1164
1455
|
* ordering: unblocked first (earliest unblock time among blocked), hot windows
|
|
1165
1456
|
* last, measured usage before unmeasured, required-drain descending, then
|
|
@@ -1171,16 +1462,22 @@ function rankPoolCandidates(entries, modelId, now = Date.now(), startIndex = 0)
|
|
|
1171
1462
|
const activePos = entries.findIndex((e) => e.index === startIndex);
|
|
1172
1463
|
const clampedStart = activePos >= 0 ? activePos : 0;
|
|
1173
1464
|
const candidates = (entries.length === 0 ? [] : [...entries.slice(clampedStart), ...entries.slice(0, clampedStart)]).map(({ account, index }, orderPos) => {
|
|
1174
|
-
const quota = familyQuotaFor(account, family);
|
|
1175
|
-
const
|
|
1465
|
+
const quota = familyQuotaFor(account, family, now);
|
|
1466
|
+
const live5h = quota !== void 0 && !resetInPast(quota.resetTime, now);
|
|
1467
|
+
const liveWeekly = quota !== void 0 && !resetInPast(quota.weeklyResetTime, now);
|
|
1468
|
+
const remaining = live5h ? quota.remainingFraction : void 0;
|
|
1176
1469
|
const used = typeof remaining === "number" ? Math.min(Math.max(1 - remaining, 0), 1) : void 0;
|
|
1470
|
+
const weeklyRemaining = liveWeekly ? quota.weeklyFraction : void 0;
|
|
1471
|
+
const weeklyUsed = typeof weeklyRemaining === "number" ? Math.min(Math.max(1 - weeklyRemaining, 0), 1) : void 0;
|
|
1177
1472
|
let blockedUntil = null;
|
|
1178
1473
|
if (account.coolingDownUntil && account.coolingDownUntil > now) blockedUntil = account.coolingDownUntil;
|
|
1179
1474
|
const familyLimit = account.rateLimitResetTimes?.[familyKeyOf(modelId)];
|
|
1180
1475
|
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);
|
|
1476
|
+
if (blockedUntil === null && quota) {
|
|
1477
|
+
const resetMs = typeof remaining === "number" && remaining <= 0 ? parseFutureResetMs(quota.resetTime, now) : void 0;
|
|
1183
1478
|
if (resetMs !== void 0) blockedUntil = resetMs;
|
|
1479
|
+
const weeklyResetMs = typeof weeklyRemaining === "number" && weeklyRemaining <= 0 ? parseFutureResetMs(quota.weeklyResetTime, now) : void 0;
|
|
1480
|
+
if (weeklyResetMs !== void 0) blockedUntil = blockedUntil === null ? weeklyResetMs : Math.max(blockedUntil, weeklyResetMs);
|
|
1184
1481
|
}
|
|
1185
1482
|
return {
|
|
1186
1483
|
account,
|
|
@@ -1189,8 +1486,8 @@ function rankPoolCandidates(entries, modelId, now = Date.now(), startIndex = 0)
|
|
|
1189
1486
|
blockedUntil,
|
|
1190
1487
|
usedFraction: used,
|
|
1191
1488
|
requiredDrain: requiredDrainFor(quota, now),
|
|
1192
|
-
hot: used !== void 0 && used >= .85,
|
|
1193
|
-
measured: used !== void 0
|
|
1489
|
+
hot: used !== void 0 && used >= .85 || weeklyUsed !== void 0 && weeklyUsed >= .85,
|
|
1490
|
+
measured: used !== void 0 || weeklyUsed !== void 0
|
|
1194
1491
|
};
|
|
1195
1492
|
});
|
|
1196
1493
|
candidates.sort((left, right) => {
|
|
@@ -1381,6 +1678,22 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1381
1678
|
refreshInFlight = /* @__PURE__ */ new Map();
|
|
1382
1679
|
/** In-flight quota fetches keyed by account: concurrent selections share one fetchAvailableModels call. */
|
|
1383
1680
|
quotaRefreshInFlight = /* @__PURE__ */ new Map();
|
|
1681
|
+
/**
|
|
1682
|
+
* In-flight `retrieveUserQuotaSummary` probes keyed by account.
|
|
1683
|
+
*
|
|
1684
|
+
* ONE promise per account per cycle, shared by BOTH consumers:
|
|
1685
|
+
* `refreshLimits` (display, writes `cachedLimits`) and `refreshQuotaCache`
|
|
1686
|
+
* (scheduling, writes `cachedQuota`). Since #48 both read the same endpoint for
|
|
1687
|
+
* the same account, so a stale account paid two round trips where one would
|
|
1688
|
+
* do; `quotaRefreshInFlight` de-duplicated only WITHIN the scheduling path.
|
|
1689
|
+
* Sharing makes the two caches agree on what upstream actually said.
|
|
1690
|
+
*
|
|
1691
|
+
* It carries the FETCH and nothing else: the invariant that `refreshLimits`
|
|
1692
|
+
* must never write `cachedQuota` (which `rankPoolCandidates` turns into a
|
|
1693
|
+
* `blockedUntil`) is about WHERE a result lands, and each path still decides
|
|
1694
|
+
* that for itself.
|
|
1695
|
+
*/
|
|
1696
|
+
quotaSummaryInFlight = /* @__PURE__ */ new Map();
|
|
1384
1697
|
failureCounts = /* @__PURE__ */ new Map();
|
|
1385
1698
|
/** Accounts whose request-time project discovery already failed (no retry per request). */
|
|
1386
1699
|
projectRetryFailed = /* @__PURE__ */ new Set();
|
|
@@ -1488,6 +1801,28 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1488
1801
|
clearAffinityForAccount(accountKey) {
|
|
1489
1802
|
for (const [conversation, pin] of this.affinity) if (pin.key === accountKey) this.affinity.delete(conversation);
|
|
1490
1803
|
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Clear all session affinity pins.
|
|
1806
|
+
*
|
|
1807
|
+
* Called when an account is explicitly activated by the user, so all
|
|
1808
|
+
* ongoing and upcoming conversations immediately steer to the newly
|
|
1809
|
+
* activated account rather than staying pinned to the previous one.
|
|
1810
|
+
*/
|
|
1811
|
+
clearAllAffinity() {
|
|
1812
|
+
this.affinity.clear();
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Explicitly activate an account by index: sets `storage.activeIndex = index`,
|
|
1816
|
+
* clears session affinity pins so conversations immediately switch to the
|
|
1817
|
+
* activated account, and resets transient failure counts.
|
|
1818
|
+
*/
|
|
1819
|
+
async activateAccount(index) {
|
|
1820
|
+
await this.store.mutate((storage) => {
|
|
1821
|
+
if (index < 0 || index >= storage.accounts.length) throw new Error("account not found");
|
|
1822
|
+
storage.activeIndex = index;
|
|
1823
|
+
});
|
|
1824
|
+
this.clearAllAffinity();
|
|
1825
|
+
}
|
|
1491
1826
|
constructor(options) {
|
|
1492
1827
|
this.store = options.store;
|
|
1493
1828
|
this.onRotate = options.onRotate;
|
|
@@ -1592,6 +1927,49 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1592
1927
|
return this.refreshToken(key, account, cached);
|
|
1593
1928
|
}
|
|
1594
1929
|
/**
|
|
1930
|
+
* Probe `retrieveUserQuotaSummary` for one account, sharing ONE in-flight
|
|
1931
|
+
* promise with every other caller in this cycle.
|
|
1932
|
+
*
|
|
1933
|
+
* Returns `[]` on any failure — the fetch itself already answers a dead
|
|
1934
|
+
* endpoint with an empty list — so both consumers read "no groups" the same
|
|
1935
|
+
* way. The promise is dropped as soon as it settles, so the NEXT cycle
|
|
1936
|
+
* re-probes.
|
|
1937
|
+
*
|
|
1938
|
+
* The account's proxy is honoured here or the call would egress direct and
|
|
1939
|
+
* leak the account's real IP, and one timeout bounds the whole endpoint
|
|
1940
|
+
* fallback chain.
|
|
1941
|
+
*/
|
|
1942
|
+
probeQuotaSummary(account) {
|
|
1943
|
+
const key = this.accountKey(account);
|
|
1944
|
+
const existing = this.quotaSummaryInFlight.get(key);
|
|
1945
|
+
if (existing) return existing;
|
|
1946
|
+
const probe = (async () => {
|
|
1947
|
+
try {
|
|
1948
|
+
const auth = await this.accessTokenFor(account);
|
|
1949
|
+
if (!auth) return [];
|
|
1950
|
+
const { fetchQuotaSummary } = await import("./quota-summary-adID82I2.mjs");
|
|
1951
|
+
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1952
|
+
const bounded = (input, init) => {
|
|
1953
|
+
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1954
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1955
|
+
return routed(input, {
|
|
1956
|
+
...init,
|
|
1957
|
+
signal
|
|
1958
|
+
});
|
|
1959
|
+
};
|
|
1960
|
+
return await fetchQuotaSummary(auth.access, account.projectId, bounded);
|
|
1961
|
+
} catch {
|
|
1962
|
+
return [];
|
|
1963
|
+
}
|
|
1964
|
+
})();
|
|
1965
|
+
this.quotaSummaryInFlight.set(key, probe);
|
|
1966
|
+
const release = () => {
|
|
1967
|
+
if (this.quotaSummaryInFlight.get(key) === probe) this.quotaSummaryInFlight.delete(key);
|
|
1968
|
+
};
|
|
1969
|
+
probe.then(release, release);
|
|
1970
|
+
return probe;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1595
1973
|
* Refresh the display-only 5h/weekly windows for every enabled account.
|
|
1596
1974
|
*
|
|
1597
1975
|
* SEPARATE from `refreshQuotaCache`, and deliberately so. That method writes
|
|
@@ -1639,22 +2017,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1639
2017
|
const probes = await Promise.all(targets.map(async (account) => {
|
|
1640
2018
|
const key = this.accountKey(account);
|
|
1641
2019
|
try {
|
|
1642
|
-
const
|
|
1643
|
-
if (!auth) return {
|
|
1644
|
-
key,
|
|
1645
|
-
ok: false
|
|
1646
|
-
};
|
|
1647
|
-
const { fetchQuotaSummary } = await import("./quota-summary-5CypVffd.mjs");
|
|
1648
|
-
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1649
|
-
const bounded = (input, init) => {
|
|
1650
|
-
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1651
|
-
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1652
|
-
return routed(input, {
|
|
1653
|
-
...init,
|
|
1654
|
-
signal
|
|
1655
|
-
});
|
|
1656
|
-
};
|
|
1657
|
-
const groups = await fetchQuotaSummary(auth.access, account.projectId, bounded);
|
|
2020
|
+
const groups = await this.probeQuotaSummary(account);
|
|
1658
2021
|
if (groups.length === 0) return {
|
|
1659
2022
|
key,
|
|
1660
2023
|
ok: false
|
|
@@ -1716,16 +2079,18 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1716
2079
|
try {
|
|
1717
2080
|
const auth = await this.accessTokenFor(account);
|
|
1718
2081
|
if (!auth) return null;
|
|
1719
|
-
const { fetchAvailableModels } = await import("./models-
|
|
2082
|
+
const { fetchAvailableModels } = await import("./models-Qn0-sh6X.mjs").then((n) => n.r);
|
|
1720
2083
|
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1721
|
-
const
|
|
2084
|
+
const bounded = (input, init) => {
|
|
1722
2085
|
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1723
2086
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1724
2087
|
return routed(input, {
|
|
1725
2088
|
...init,
|
|
1726
2089
|
signal
|
|
1727
2090
|
});
|
|
1728
|
-
}
|
|
2091
|
+
};
|
|
2092
|
+
const [discovered, groups] = await Promise.all([fetchAvailableModels(auth.access, account.projectId, bounded), this.probeQuotaSummary(account)]);
|
|
2093
|
+
const quotas = ingestFamilyQuotas(discovered, groups, account.cachedQuota);
|
|
1729
2094
|
return {
|
|
1730
2095
|
key,
|
|
1731
2096
|
quotas,
|
|
@@ -1765,7 +2130,8 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1765
2130
|
}
|
|
1766
2131
|
/**
|
|
1767
2132
|
* Pick the account for one request: the affinity pin wins while it is fresh,
|
|
1768
|
-
* healthy, and not
|
|
2133
|
+
* healthy, and not quota-exhausted for the requested model; the explicitly
|
|
2134
|
+
* activated account wins next while it is usable; otherwise the pool is
|
|
1769
2135
|
* ranked by family-scoped usage (OMP-aligned) and the best candidate wins.
|
|
1770
2136
|
*/
|
|
1771
2137
|
async pickAccount(storage, model, conversationKey) {
|
|
@@ -1778,7 +2144,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1778
2144
|
const lastIndex = storage.accounts.findIndex((a) => this.accountKey(a) === pinnedKey);
|
|
1779
2145
|
if (lastIndex !== -1) {
|
|
1780
2146
|
const last = storage.accounts[lastIndex];
|
|
1781
|
-
if (last.enabled !== false && !isCoolingDown(last, now) && !isFamilyRateLimited(last, familyKey, now) && !
|
|
2147
|
+
if (last.enabled !== false && !isCoolingDown(last, now) && !isFamilyRateLimited(last, familyKey, now) && !isFamilyQuotaExhausted(last, family, now)) return {
|
|
1782
2148
|
account: last,
|
|
1783
2149
|
index: lastIndex
|
|
1784
2150
|
};
|
|
@@ -1789,15 +2155,25 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1789
2155
|
index
|
|
1790
2156
|
})).filter(({ account }) => account.enabled !== false);
|
|
1791
2157
|
if (eligible.length === 0) return void 0;
|
|
2158
|
+
const active = storage.accounts[storage.activeIndex];
|
|
2159
|
+
const isActiveUsable = (acc) => acc !== void 0 && acc.enabled !== false && !isCoolingDown(acc, now) && !isFamilyRateLimited(acc, familyKey, now) && !isFamilyQuotaExhausted(acc, family, now);
|
|
2160
|
+
if (isActiveUsable(active) && this.inFlightCount(this.accountKey(active), now) < 3) return {
|
|
2161
|
+
account: active,
|
|
2162
|
+
index: storage.activeIndex
|
|
2163
|
+
};
|
|
1792
2164
|
const ranked = rankPoolCandidates(eligible, model, now, storage.activeIndex);
|
|
1793
2165
|
const picked = ranked.find((candidate) => candidate.blockedUntil === null && this.inFlightCount(this.accountKey(candidate.account), now) < 3) ?? ranked.find((candidate) => candidate.blockedUntil === null);
|
|
1794
2166
|
if (!picked) {
|
|
1795
2167
|
const quotaExhausted = (account) => {
|
|
1796
2168
|
if (account.cooldownReason === "quota-exhausted" && (account.coolingDownUntil ?? 0) > now) return true;
|
|
1797
|
-
const quota = familyQuotaFor(account, family);
|
|
1798
|
-
if (
|
|
1799
|
-
const
|
|
1800
|
-
|
|
2169
|
+
const quota = familyQuotaFor(account, family, now);
|
|
2170
|
+
if (!quota) return false;
|
|
2171
|
+
const spent = (fraction, resetTime) => {
|
|
2172
|
+
if (typeof fraction !== "number" || fraction > 0 || !resetTime) return false;
|
|
2173
|
+
const resetAt = Date.parse(resetTime);
|
|
2174
|
+
return !Number.isNaN(resetAt) && resetAt > now;
|
|
2175
|
+
};
|
|
2176
|
+
return spent(quota.remainingFraction, quota.resetTime) || spent(quota.weeklyFraction, quota.weeklyResetTime);
|
|
1801
2177
|
};
|
|
1802
2178
|
const retryable = ranked.filter((candidate) => !quotaExhausted(candidate.account));
|
|
1803
2179
|
const blocked = retryable.length > 0 ? retryable : ranked;
|
|
@@ -1885,7 +2261,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1885
2261
|
}
|
|
1886
2262
|
const key = this.accountKey(picked.account);
|
|
1887
2263
|
if (!picked.account.projectId && !this.projectRetryFailed.has(key)) try {
|
|
1888
|
-
const { loadCodeAssist } = await import("./exchange-
|
|
2264
|
+
const { loadCodeAssist } = await import("./exchange-LzEyxve1.mjs").then((n) => n.n);
|
|
1889
2265
|
const { projectId } = await loadCodeAssist(auth.access, { proxyUrl: picked.account.proxy });
|
|
1890
2266
|
if (projectId) {
|
|
1891
2267
|
await this.store.mutate((s) => {
|
|
@@ -1967,6 +2343,13 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1967
2343
|
if (decision.action === "revoke") {
|
|
1968
2344
|
this.tokenCache.delete(key);
|
|
1969
2345
|
this.failureCounts.delete(key);
|
|
2346
|
+
const currentIndex = storage.accounts.findIndex((a) => this.accountKey(a) === key);
|
|
2347
|
+
const nextIndex = pickNextAccountIndex(storage.accounts, currentIndex >= 0 ? currentIndex : storage.activeIndex, Date.now());
|
|
2348
|
+
if (nextIndex !== storage.activeIndex) {
|
|
2349
|
+
storage.activeIndex = nextIndex;
|
|
2350
|
+
nextIndexToRotate = nextIndex;
|
|
2351
|
+
}
|
|
2352
|
+
this.clearAffinityForAccount(key);
|
|
1970
2353
|
return;
|
|
1971
2354
|
}
|
|
1972
2355
|
if (kind === "rate-limit" && info?.rateLimitCategory !== "soft_rate_limit") {
|
|
@@ -1982,7 +2365,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1982
2365
|
}
|
|
1983
2366
|
}
|
|
1984
2367
|
}
|
|
1985
|
-
if (decision.action === "rotate") {
|
|
2368
|
+
if (decision.action === "rotate" || decision.action === "cool") {
|
|
1986
2369
|
const currentIndex = storage.accounts.findIndex((a) => this.accountKey(a) === key);
|
|
1987
2370
|
const familyKey = familyKeyOf(info?.model);
|
|
1988
2371
|
const nextIndex = pickNextAccountIndex(storage.accounts, currentIndex >= 0 ? currentIndex : storage.activeIndex, Date.now(), familyKey);
|
|
@@ -2040,8 +2423,8 @@ var AgySessionManager = class AgySessionManager {
|
|
|
2040
2423
|
};
|
|
2041
2424
|
try {
|
|
2042
2425
|
const account = session.account.email ?? session.account.id;
|
|
2043
|
-
const { toAgyRequestBody } = await import("./translate-
|
|
2044
|
-
const { fetchAgyFirstOk } = await import("./constants-
|
|
2426
|
+
const { toAgyRequestBody } = await import("./translate-YhGDUuVa.mjs").then((n) => n.n);
|
|
2427
|
+
const { fetchAgyFirstOk } = await import("./constants-BpyRirvk.mjs").then((n) => n.f);
|
|
2045
2428
|
const { parseAgySse } = await import("./parse-Bs4rPkHA.mjs").then((n) => n.n);
|
|
2046
2429
|
const requestId = generateAntigravityRequestId();
|
|
2047
2430
|
const body = toAgyRequestBody({
|
|
@@ -2483,4 +2866,4 @@ var JsonAccountStore = class {
|
|
|
2483
2866
|
//#endregion
|
|
2484
2867
|
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
2868
|
|
|
2486
|
-
//# sourceMappingURL=accounts-
|
|
2869
|
+
//# sourceMappingURL=accounts-BN4h0AU4.mjs.map
|