tokenmaxxing 1.19.1 → 1.21.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/src/lib/decide.ts CHANGED
@@ -2,11 +2,11 @@ import { maxBy } from "es-toolkit";
2
2
  import { z } from "zod";
3
3
  import { withLock } from "./lock.ts";
4
4
  import { paths } from "./paths.ts";
5
- import { MAX_CHECK_DELAY_TICKS, POST_SWAP_COOLDOWN_MS, maxCheckDelayMs, loadAccounts, loadConfig, loadDepletedWait, loadLastSwapAt, loadUsage, loadUsageSnapshot, loadModelUsage, measureFamilyWindow, saveAccounts, saveDepletedWait, saveModelUsage, writeUsage } from "./state.ts";
5
+ import { MAX_CHECK_DELAY_TICKS, POST_SWAP_COOLDOWN_MS, maxCheckDelayMs, loadAccounts, loadConfig, loadDepletedWait, loadLastSwapAt, loadUsage, loadUsageSnapshot, loadModelUsage, saveAccounts, saveDepletedWait, saveModelUsage, writeUsage } from "./state.ts";
6
6
  import { readOAuthAccount } from "./claudejson.ts";
7
- import { bankedResetBelievedAvailable, claimBankedReset } from "./bankedreset.ts";
8
- import { chooseAndSwap, isSkippableSwapError, performSwap } from "./swap.ts";
9
- import { currentWins, effectiveBars, hardBars, isExhausted, nextWeeklyReset, pickBest, pickEarliestReset, sessionLadder, usableAt } from "./picker.ts";
7
+ import { chooseAndSwap, isSkippableSwapError, performSwap, type VerifyVerdict } from "./swap.ts";
8
+ import { probeParkedUsage } from "./sample.ts";
9
+ import { currentWins, effectiveBars, hardBars, isExhausted, nextWeeklyReset, pickBest, pickEarliestReset, sessionLadder, usableAt, type PickCtx } from "./picker.ts";
10
10
  import { familyTokens, gatedFamilies, probeUsage, type EnforcedClass } from "./usage.ts";
11
11
  import { log } from "./log.ts";
12
12
  import { AccountSchema, ModelUsageStateSchema, UsageStateSchema, type Account, type Config, type EnforcedLimit, type ModelUsageState, type Thresholds, type UsageState, type UsageWindow } from "./types.ts";
@@ -16,12 +16,13 @@ const SwapDecisionSchema = z.object({
16
16
  account: AccountSchema.nullable(),
17
17
  reason: z.string(),
18
18
  waitUntil: z.number().optional(),
19
- reset: z.boolean().optional(),
20
19
  });
21
20
  export type SwapDecision = z.infer<typeof SwapDecisionSchema>;
22
21
 
23
22
  const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
24
23
  const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
24
+ const VERIFY_PROBE_BUDGET = 2;
25
+ const VERIFY_PROBE_KILL_MS = 12_000;
25
26
 
26
27
  function liveUsed(input: { window: UsageWindow; windowMs: number; sampledAt: number; now: number }): number {
27
28
  const { window: w, windowMs, sampledAt, now } = input;
@@ -37,17 +38,6 @@ function capForFamily(mu: ModelUsageState, family: string, now: number): UsageWi
37
38
  return maxBy(rows, (w) => liveUsed({ window: w, windowMs: WEEK_MS, sampledAt: mu.sampledAt ?? mu.ts, now }));
38
39
  }
39
40
 
40
- function familyCaps(perModel: Record<string, UsageWindow>, families: string[]): Record<string, number> {
41
- return Object.fromEntries(
42
- families.flatMap((family) => {
43
- const rows = Object.entries(perModel)
44
- .filter(([k]) => familyTokens(k).includes(family))
45
- .map(([, w]) => w.usedPercentage);
46
- return rows.length > 0 ? [[family, Math.max(...rows)] as const] : [];
47
- }),
48
- );
49
- }
50
-
51
41
  function overlayLive(accounts: Account[], u: UsageState | null, mu: ModelUsageState | null, account: string | null): Account[] {
52
42
  return accounts.map((a) => {
53
43
  if (account == null || a.accountUuid !== account) return a;
@@ -76,41 +66,6 @@ function needsPerModel(u: UsageState | null, cfg: Config): boolean {
76
66
  return u != null && gatedFamilies(u.model, cfg.policy.switchModels).length > 0;
77
67
  }
78
68
 
79
- const BankedVerdictSchema = z.enum(["hold", "claim", "pass"]);
80
- type BankedVerdict = z.infer<typeof BankedVerdictSchema>;
81
-
82
- function bankedResetVerdict(input: {
83
- seat: Account;
84
- u: UsageState | null;
85
- mu: ModelUsageState | null;
86
- enforced: EnforcedLimit | null;
87
- cfg: Config;
88
- bars: Thresholds;
89
- switchFamilies: string[];
90
- now: number;
91
- }): BankedVerdict {
92
- const { seat, u, mu, enforced, cfg, bars, switchFamilies, now } = input;
93
- if (enforced && enforced.kind !== "session") return "pass";
94
- if (!u || u.account !== seat.accountUuid) return "pass";
95
- if (!bankedResetBelievedAvailable(seat, now)) return "pass";
96
- if (u.sampledAt == null || now - u.sampledAt > cfg.policy.usagePollTtlMs) return "pass";
97
- const cost = u.sessionWindowWeeklyCost ?? seat.sessionWindowWeeklyCost;
98
- if (cost == null) return "pass";
99
- const weeklyUsed = liveUsed({ window: u.sevenDay, windowMs: WEEK_MS, sampledAt: u.ts, now });
100
- if (weeklyUsed + cost > bars.weekly) return "pass";
101
- const muSame = mu && mu.account === seat.accountUuid && now - (mu.sampledAt ?? mu.ts) <= cfg.policy.usagePollTtlMs ? mu : null;
102
- for (const family of switchFamilies) {
103
- const cap = muSame ? capForFamily(muSame, family, now) : undefined;
104
- if (!cap || !muSame) return "pass";
105
- const familyCost = muSame.familyCosts?.[family];
106
- if (familyCost == null) return "pass";
107
- const capUsed = liveUsed({ window: cap, windowMs: WEEK_MS, sampledAt: muSame.sampledAt ?? muSame.ts, now });
108
- if (capUsed + familyCost > bars.weekly) return "pass";
109
- }
110
- const atWall = enforced?.kind === "session" || liveUsed({ window: u.fiveHour, windowMs: FIVE_HOURS_MS, sampledAt: u.ts, now }) >= hardBars(cfg).session;
111
- return atWall ? "claim" : "hold";
112
- }
113
-
114
69
  function isEngaged(u: UsageState | null, mu: ModelUsageState | null, account: string | null, bars: Thresholds, cfg: Config, now: number): boolean {
115
70
  if (!u || !account || u.account !== account) return false;
116
71
  return liveUsed({ window: u.fiveHour, windowMs: FIVE_HOURS_MS, sampledAt: u.ts, now }) >= cfg.policy.greedySessionFloor || isOver(u, mu, account, bars, cfg, now);
@@ -146,7 +101,6 @@ async function loadFreshSnapshots(cfg: Config, account: string | null, now: numb
146
101
  if (account && !probeAttempted && (!usageFresh(u, uAt, account, ttl, now) || needsPerModel(u, cfg))) {
147
102
  const full = await probeUsage();
148
103
  const ts = Date.now();
149
- const prevMu = mu;
150
104
  if (readOAuthAccount()?.accountUuid === account) {
151
105
  if (full) {
152
106
  const teed = loadUsageSnapshot();
@@ -158,7 +112,7 @@ async function loadFreshSnapshots(cfg: Config, account: string | null, now: numb
158
112
  writeUsage(u);
159
113
  uAt = ts;
160
114
  }
161
- mu = { perModel: full.perModel, account, ts, sampledAt: ts, ...measureFamilyWindow(prevMu, account, full.session, familyCaps(full.perModel, cfg.policy.switchModels)) };
115
+ mu = { perModel: full.perModel, account, ts, sampledAt: ts };
162
116
  saveModelUsage(mu);
163
117
  const expected = gatedFamilies(u?.model ?? null, cfg.policy.switchModels);
164
118
  const rows = Object.keys(full.perModel);
@@ -166,7 +120,7 @@ async function loadFreshSnapshots(cfg: Config, account: string | null, now: numb
166
120
  log("usage.no_permodel_row", { families: expected.join(","), rows: rows.join(",") });
167
121
  }
168
122
  } else {
169
- mu = { perModel: mu?.account === account ? (mu?.perModel ?? {}) : {}, account, ts, sampledAt: mu?.account === account ? (mu?.sampledAt ?? mu?.ts) : undefined, ...measureFamilyWindow(prevMu, account, null, {}) };
123
+ mu = { perModel: mu?.account === account ? (mu?.perModel ?? {}) : {}, account, ts, sampledAt: mu?.account === account ? (mu?.sampledAt ?? mu?.ts) : undefined };
170
124
  saveModelUsage(mu);
171
125
  }
172
126
  }
@@ -221,7 +175,6 @@ export async function evaluateAndMaybeSwap(now = Date.now(), anticipatory = fals
221
175
  if (tee && tee.state.account === account2 && (active.lastUsageAt == null || tee.at >= active.lastUsageAt)) {
222
176
  active.lastUsage = { fiveHour: tee.state.fiveHour, sevenDay: tee.state.sevenDay };
223
177
  active.lastUsageAt = tee.state.sampledAt ?? tee.at;
224
- if (tee.state.sessionWindowWeeklyCost != null) active.sessionWindowWeeklyCost = tee.state.sessionWindowWeeklyCost;
225
178
  sampled = true;
226
179
  }
227
180
  if (mu2 && mu2.account === account2 && Object.keys(mu2.perModel).length > 0) {
@@ -253,18 +206,60 @@ export async function evaluateAndMaybeSwap(now = Date.now(), anticipatory = fals
253
206
  rejected.add(candidate.accountUuid);
254
207
  log("decide.candidate_rejected", { account: candidate.accountUuid.slice(0, 8), error: e instanceof Error ? e.message : String(e) });
255
208
  };
209
+ let probeBudget = VERIFY_PROBE_BUDGET;
210
+ const verified = async (candidate: Account, ctx: PickCtx): Promise<VerifyVerdict> => {
211
+ if (candidate.lastUsageAt != null && now - candidate.lastUsageAt <= cfg.policy.usagePollTtlMs) return "go";
212
+ const short = candidate.accountUuid.slice(0, 8);
213
+ const idx2 = loadAccounts();
214
+ const stored = idx2.accounts.find((a) => a.accountUuid === candidate.accountUuid);
215
+ if (!stored) return "go";
216
+ if (probeBudget <= 0) {
217
+ log("decide.verify_budget_spent", { account: short });
218
+ return "go";
219
+ }
220
+ probeBudget -= 1;
221
+ const sample = await probeParkedUsage(stored, { retries: 0, refreshParked: false, killMs: VERIFY_PROBE_KILL_MS, signal: AbortSignal.timeout(VERIFY_PROBE_KILL_MS) });
222
+ if (sample.ok) {
223
+ const at = Date.now();
224
+ stored.lastUsage = { fiveHour: sample.usage.session, sevenDay: sample.usage.weekAll };
225
+ stored.lastUsageAt = at;
226
+ if (Object.keys(sample.usage.perModel).length > 0) {
227
+ stored.lastPerModel = sample.usage.perModel;
228
+ stored.lastPerModelAt = at;
229
+ }
230
+ }
231
+ if (sample.ok || stored.needsReauth === true) saveAccounts(idx2);
232
+ if (stored.needsReauth === true) {
233
+ rejected.add(candidate.accountUuid);
234
+ log("decide.candidate_rejected", { account: short, error: "needs reauth" });
235
+ return "skip";
236
+ }
237
+ if (!sample.ok) {
238
+ log("decide.candidate_unverified", { account: short });
239
+ return "go";
240
+ }
241
+ log(isExhausted(stored, ctx) ? "decide.candidate_walled" : "decide.candidate_verified", {
242
+ account: short,
243
+ session: sample.usage.session.usedPercentage,
244
+ weekly: sample.usage.weekAll.usedPercentage,
245
+ });
246
+ return "rerank";
247
+ };
256
248
 
257
249
  const greedy = async (holdMargin: number): Promise<SwapDecision> => {
258
250
  while (true) {
259
251
  const cur = loadAccounts();
260
252
  const active = seatOf(cur);
261
- const pool = usable(cur.accounts);
262
- const ctxAll = { now, thresholds: barsOf(cur.accounts), currentAccountUuid: null, switchFamilies, holdMargin };
253
+ const ctxAll = { now, thresholds: barsOf(cur.accounts), currentAccountUuid: null, currentOrganizationUuid: active?.organizationUuid ?? null, orgAffinityFloor: cfg.policy.greedySessionFloor, switchFamilies, holdMargin };
254
+ const seatOrg = active != null && !active.needsReauth && !isExhausted(active, ctxAll) ? active.organizationUuid : null;
255
+ const pool = usable(cur.accounts).filter((a) => seatOrg == null || a.organizationUuid === seatOrg);
263
256
  if (currentWins(active, pool, ctxAll)) {
264
257
  return { swapped: false, account: null, reason: "current-best" };
265
258
  }
266
- const best = pickBest(pool, { ...ctxAll, currentAccountUuid: active?.accountUuid ?? null });
259
+ const ctx = { ...ctxAll, currentAccountUuid: active?.accountUuid ?? null };
260
+ const best = pickBest(pool, ctx);
267
261
  if (!best) return { swapped: false, account: null, reason: "no-usable-target" };
262
+ if ((await verified(best, ctx)) !== "go") continue;
268
263
  try {
269
264
  await performSwap(best);
270
265
  } catch (e) {
@@ -277,34 +272,23 @@ export async function evaluateAndMaybeSwap(now = Date.now(), anticipatory = fals
277
272
  };
278
273
  if (!enforced2 && !isOver(u2, mu2, account2, bars, cfg, now)) return greedy(cfg.policy.greedySwapMargin);
279
274
 
280
- const resetPreferred = cfg.policy.preferToUseBankedReset.includes("claude");
281
- let resetTried = false;
282
275
  while (true) {
283
276
  const cur = loadAccounts();
284
277
  const seat = seatOf(cur);
285
278
  const pool = usable(cur.accounts);
286
279
  const thresholds = barsOf(cur.accounts);
287
280
  if (!enforced2 && seat && !seat.needsReauth && !isExhausted(seat, { now, thresholds, currentAccountUuid: null, switchFamilies })) return greedy(0);
288
- if (resetPreferred && !resetTried && seat && !seat.needsReauth) {
289
- const verdict = bankedResetVerdict({ seat, u: u2, mu: mu2, enforced: enforced2, cfg, bars: thresholds, switchFamilies, now });
290
- if (verdict === "hold") {
291
- log("decide.banked_reset_hold", { account: seat.accountUuid.slice(0, 8), enforced: enforced2 != null });
292
- return { swapped: false, account: seat, reason: "banked-reset-hold" };
293
- }
294
- if (verdict === "claim") {
295
- resetTried = true;
296
- const claim = await claimBankedReset({ account: seat });
297
- if (claim === "reset") return { swapped: false, account: seat, reason: "banked-reset", reset: true };
298
- }
299
- }
300
- const best = pickBest(pool, { now, thresholds, currentAccountUuid: seat?.accountUuid ?? null, switchFamilies });
281
+ const ctx = { now, thresholds, currentAccountUuid: seat?.accountUuid ?? null, currentOrganizationUuid: seat?.organizationUuid ?? null, orgAffinityFloor: cfg.policy.greedySessionFloor, switchFamilies };
282
+ const best = pickBest(pool, ctx);
301
283
  if (!best) break;
284
+ if ((await verified(best, ctx)) !== "go") continue;
302
285
  try {
303
286
  await performSwap(best);
304
287
  } catch (e) {
305
288
  skipOrThrow(e, best);
306
289
  continue;
307
290
  }
291
+ log("decide.hard_swap", { account: best.accountUuid.slice(0, 8), enforced: enforced2 != null });
308
292
  return { swapped: true, account: best, reason: "swapped" };
309
293
  }
310
294
 
@@ -314,7 +298,7 @@ export async function evaluateAndMaybeSwap(now = Date.now(), anticipatory = fals
314
298
  log("decide.last_drop_hold", { account: seat.accountUuid.slice(0, 8) });
315
299
  return { swapped: false, account: null, reason: "last-drop-hold" };
316
300
  }
317
- const squeezed = await chooseAndSwap({ ...hardCtx, currentAccountUuid: seat?.accountUuid ?? null }, rejected);
301
+ const squeezed = await chooseAndSwap({ ...hardCtx, currentAccountUuid: seat?.accountUuid ?? null, currentOrganizationUuid: seat?.organizationUuid ?? null, orgAffinityFloor: cfg.policy.greedySessionFloor }, rejected, verified);
318
302
  if (squeezed) {
319
303
  log("decide.last_drop_swap", { account: squeezed.accountUuid.slice(0, 8) });
320
304
  return { swapped: true, account: squeezed, reason: "last-drop-swap" };
@@ -404,12 +388,11 @@ export async function recordEnforcedLimit(input: { limit: EnforcedClass; account
404
388
  account: accountUuid,
405
389
  ts: now,
406
390
  sampledAt: resetsAt == null ? now : sampledAt ?? now,
407
- ...measureFamilyWindow(muSame, accountUuid, null, {}),
408
391
  });
409
392
  log("usage.enforced_limit", { kind: limit.kind, family: limit.family, resetsAt });
410
393
  return { outcome: "stamped", resetsAt };
411
394
  }
412
- saveModelUsage({ perModel: carriedRows, account: accountUuid, ts: now, sampledAt, ...measureFamilyWindow(muSame, accountUuid, null, {}) });
395
+ saveModelUsage({ perModel: carriedRows, account: accountUuid, ts: now, sampledAt });
413
396
  if (account && limit.resetsAt != null) {
414
397
  account.enforcedUntil = limit.resetsAt;
415
398
  saveAccounts(idx);
package/src/lib/oauth.ts CHANGED
@@ -94,11 +94,12 @@ export function isAccessTokenExpiring(creds: OAuthCreds, skewMs = 120_000, now =
94
94
  return !creds.expiresAt || creds.expiresAt - now <= skewMs;
95
95
  }
96
96
 
97
- export async function fetchTokenIdentity(accessToken: string): Promise<TokenIdentity> {
97
+ export async function fetchTokenIdentity(accessToken: string, signal?: AbortSignal): Promise<TokenIdentity> {
98
98
  let res: Response;
99
99
  try {
100
100
  res = await http.get(PROFILE_URL, {
101
101
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
102
+ signal,
102
103
  });
103
104
  } catch (e) {
104
105
  throw new IdentityUnavailableError(null, e instanceof Error ? e.message : String(e));
package/src/lib/paths.ts CHANGED
@@ -19,7 +19,6 @@ export const paths = {
19
19
  usageJson: join(TM_HOME, "usage.json"),
20
20
  modelUsageJson: join(TM_HOME, "model-usage.json"),
21
21
  lastSwapJson: join(TM_HOME, "lastswap.json"),
22
- lastResetJson: join(TM_HOME, "lastreset.json"),
23
22
  depletedJson: join(TM_HOME, "depleted.json"),
24
23
  nextCheckJson: join(TM_HOME, "nextcheck.json"),
25
24
  respawnDir: join(TM_HOME, "respawn"),
package/src/lib/picker.ts CHANGED
@@ -34,6 +34,8 @@ const PickCtxSchema = z.object({
34
34
  now: z.number(),
35
35
  thresholds: ThresholdsSchema,
36
36
  currentAccountUuid: z.string().nullable(),
37
+ currentOrganizationUuid: z.string().nullable().optional(),
38
+ orgAffinityFloor: z.number().optional(),
37
39
  switchFamilies: z.array(z.string()),
38
40
  holdMargin: z.number().min(0).optional(),
39
41
  });
@@ -94,9 +96,27 @@ export function pacePressure(a: Account, now: number): number {
94
96
  return Math.max(0, 100 - used) / Math.max(1, reset - now);
95
97
  }
96
98
 
97
- const swapPreference = (now: number) => [
98
- (a: Account) => -pacePressure(a, now),
99
- (a: Account) => weeklyExpiry(a, now),
99
+ function sessionUsed(a: Account, now: number): number {
100
+ const w = a.lastUsage?.fiveHour;
101
+ if (w == null) return 101;
102
+ if (w.resetsAt != null) return w.resetsAt <= now ? 0 : w.usedPercentage;
103
+ if (a.lastUsageAt != null && now >= a.lastUsageAt + FIVE_HOURS_MS) return 0;
104
+ return w.usedPercentage;
105
+ }
106
+
107
+ function orgTier(a: Account, ctx: PickCtx): number {
108
+ if (ctx.currentOrganizationUuid == null || a.organizationUuid !== ctx.currentOrganizationUuid) return 1;
109
+ if (ctx.orgAffinityFloor != null && sessionUsed(a, ctx.now) >= ctx.orgAffinityFloor) return 1;
110
+ if (!a.lastUsage || ctx.switchFamilies.some((family) => gatedPerModelWindows(a, [family]).length === 0)) return 1;
111
+ const headroom = Math.max(1, ctx.thresholds.session - (ctx.orgAffinityFloor ?? ctx.thresholds.session));
112
+ if (isExhausted(a, { ...ctx, thresholds: { session: ctx.thresholds.session - headroom, weekly: ctx.thresholds.weekly - headroom } })) return 1;
113
+ return 0;
114
+ }
115
+
116
+ const swapPreference = (ctx: PickCtx) => [
117
+ (a: Account) => orgTier(a, ctx),
118
+ (a: Account) => -pacePressure(a, ctx.now),
119
+ (a: Account) => weeklyExpiry(a, ctx.now),
100
120
  (a: Account) => a.lastUsage?.sevenDay.usedPercentage ?? 101,
101
121
  ];
102
122
 
@@ -104,7 +124,7 @@ export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
104
124
  const usable = accounts.filter(
105
125
  (a) => a.accountUuid !== ctx.currentAccountUuid && !a.needsReauth && !isExhausted(a, ctx),
106
126
  );
107
- return sortBy(usable, swapPreference(ctx.now))[0] ?? null;
127
+ return sortBy(usable, swapPreference(ctx))[0] ?? null;
108
128
  }
109
129
 
110
130
  export function currentWins(active: Account | null, accounts: Account[], ctx: PickCtx): boolean {
@@ -114,7 +134,7 @@ export function currentWins(active: Account | null, accounts: Account[], ctx: Pi
114
134
  const margin = ctx.holdMargin ?? 0;
115
135
  const bestPace = pacePressure(best, ctx.now);
116
136
  if (margin > 0 && bestPace > 0 && bestPace <= pacePressure(active, ctx.now) * (1 + margin)) return true;
117
- return swapPreference(ctx.now).every((k) => k(active) === k(best));
137
+ return swapPreference(ctx).every((k) => k(active) === k(best));
118
138
  }
119
139
 
120
140
  export function usableAt(a: Account, ctx: PickCtx): number {
package/src/lib/sample.ts CHANGED
@@ -24,10 +24,10 @@ const IdentityCheckSchema = z.discriminatedUnion("status", [
24
24
  ]);
25
25
  type IdentityCheck = z.infer<typeof IdentityCheckSchema>;
26
26
 
27
- async function checkIdentity(creds: OAuthCreds, account: Account): Promise<IdentityCheck> {
27
+ async function checkIdentity(creds: OAuthCreds, account: Account, signal?: AbortSignal): Promise<IdentityCheck> {
28
28
  let identity: TokenIdentity;
29
29
  try {
30
- identity = await fetchTokenIdentity(creds.accessToken);
30
+ identity = await fetchTokenIdentity(creds.accessToken, signal);
31
31
  } catch (e) {
32
32
  return {
33
33
  status: "unavailable",
@@ -44,7 +44,10 @@ function refreshPlanFields(account: Account, creds: OAuthCreds): void {
44
44
  if (creds.rateLimitTier != null) account.rateLimitTier = creds.rateLimitTier;
45
45
  }
46
46
 
47
- export async function probeParkedUsage(account: Account, opts: { ping?: boolean } = {}): Promise<SampleOutcome> {
47
+ export async function probeParkedUsage(
48
+ account: Account,
49
+ opts: { ping?: boolean; retries?: number; refreshParked?: boolean; killMs?: number; signal?: AbortSignal } = {},
50
+ ): Promise<SampleOutcome> {
48
51
  const backup = parkedTarget(account.keychainItem);
49
52
  const parkedRaw = await readItem(backup);
50
53
  if (!parkedRaw) return { ok: false, reason: "no parked credential - run `tokenmaxxing auth`" };
@@ -59,6 +62,9 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
59
62
  account.needsReauth = true;
60
63
  return { ok: false, reason: "parked credential was cleared after a failed refresh - re-auth with `tokenmaxxing auth`" };
61
64
  }
65
+ if (opts.refreshParked === false && isAccessTokenExpiring(creds, 300_000)) {
66
+ return { ok: false, reason: "parked access token is near expiry - verification requires an unexpired token" };
67
+ }
62
68
 
63
69
  const liveRaw = await readItem(liveTarget());
64
70
  let liveAccount: string | null = null;
@@ -73,7 +79,7 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
73
79
  if (!isDeadCredential(liveCreds)) {
74
80
  liveToken = liveCreds.accessToken;
75
81
  try {
76
- liveAccount = (await fetchTokenIdentity(liveCreds.accessToken)).accountUuid;
82
+ liveAccount = (await fetchTokenIdentity(liveCreds.accessToken, opts.signal)).accountUuid;
77
83
  } catch (e) {
78
84
  return { ok: false, reason: `cannot verify the live credential's owner (${(e instanceof Error ? e.message : String(e)).slice(0, 80)}) - refusing to sample a possibly-live account` };
79
85
  }
@@ -92,6 +98,9 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
92
98
  };
93
99
 
94
100
  if (isAccessTokenExpiring(creds, 300_000)) {
101
+ if (opts.refreshParked === false) {
102
+ return { ok: false, reason: "parked access token is near expiry - verification requires an unexpired token" };
103
+ }
95
104
  const owner = await checkIdentity(creds, account);
96
105
  if (owner.status === "mismatch") {
97
106
  const kept = await relocate(owner.owner);
@@ -136,7 +145,7 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
136
145
  await writeItem(backup, JSON.stringify({ claudeAiOauth: creds }));
137
146
  }
138
147
 
139
- const identity = await checkIdentity(creds, account);
148
+ const identity = await checkIdentity(creds, account, opts.signal);
140
149
  if (identity.status === "mismatch") {
141
150
  const kept = await relocate(identity.owner);
142
151
  account.needsReauth = true;
@@ -151,13 +160,13 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
151
160
  rmSync(dir, { recursive: true, force: true });
152
161
  mkdirSync(dir, { recursive: true });
153
162
  const isoTarget = isolatedTarget(dir);
154
- const installed = JSON.stringify({ claudeAiOauth: creds });
163
+ const installed = JSON.stringify({ claudeAiOauth: opts.refreshParked === false ? { ...creds, refreshToken: "" } : creds });
155
164
 
156
165
  try {
157
166
  await writeItem(isoTarget, installed);
158
167
  writeFileSync(join(dir, ".claude.json"), JSON.stringify({ oauthAccount: account.oauthAccount, hasCompletedOnboarding: true }));
159
168
  const ping = opts.ping ? await pingSession(dir) : null;
160
- const usage = await probeUsage(dir);
169
+ const usage = await probeUsage(dir, Date.now(), opts);
161
170
  const outcome: SampleOutcome = usage
162
171
  ? { ok: true, usage }
163
172
  : { ok: false, reason: "`/usage` returned no limit data (see log)" };
@@ -167,10 +176,15 @@ export async function probeParkedUsage(account: Account, opts: { ping?: boolean
167
176
  }
168
177
  return outcome;
169
178
  } finally {
170
- const afterIso = await readItem(isoTarget);
171
- if (afterIso && afterIso !== installed) await writeItem(backup, claudeAiOauthOnly(afterIso));
172
- await deleteItem(isoTarget);
173
- rmSync(dir, { recursive: true, force: true });
179
+ try {
180
+ if (opts.refreshParked !== false) {
181
+ const afterIso = await readItem(isoTarget);
182
+ if (afterIso && afterIso !== installed) await writeItem(backup, claudeAiOauthOnly(afterIso));
183
+ }
184
+ } finally {
185
+ await deleteItem(isoTarget);
186
+ rmSync(dir, { recursive: true, force: true });
187
+ }
174
188
  }
175
189
  }
176
190
 
package/src/lib/state.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import { closeSync, existsSync, fstatSync, openSync, readFileSync, rmSync, utimesSync } from "node:fs";
2
- import { isEqual, uniq } from "es-toolkit";
2
+ import { isEqual } from "es-toolkit";
3
3
  import { z } from "zod";
4
4
  import { paths, realClaudeBinFromEnv, realCodexBinFromEnv } from "./paths.ts";
5
5
  import { writeFileAtomic } from "./atomic.ts";
6
6
  import {
7
7
  AccountsIndexSchema,
8
- BankedResetProvidersSchema,
9
8
  ConfigSchema,
10
9
  LastSwapSchema,
11
10
  ModelUsageStateSchema,
@@ -14,11 +13,8 @@ import {
14
13
  UsageStateSchema,
15
14
  type AccountsIndex,
16
15
  type Config,
17
- type FamilyAnchor,
18
16
  type ModelUsageState,
19
17
  type UsageState,
20
- type UsageWindow,
21
- type WindowAnchor,
22
18
  } from "./types.ts";
23
19
 
24
20
  const DEFAULT_CONFIG: Config = {
@@ -34,7 +30,6 @@ const DEFAULT_CONFIG: Config = {
34
30
  usagePollTtlMs: 90_000,
35
31
  maxWaitMs: 3_600_000,
36
32
  checkIntervalMs: 60_000,
37
- preferToUseBankedReset: [],
38
33
  },
39
34
  };
40
35
 
@@ -55,7 +50,6 @@ export const ConfigFileSchema = z
55
50
  usagePollTtlMs: z.number().int().positive(),
56
51
  maxWaitMs: z.number().int().positive(),
57
52
  checkIntervalMs: z.number().int().min(10_000),
58
- preferToUseBankedReset: BankedResetProvidersSchema,
59
53
  })
60
54
  .partial(),
61
55
  })
@@ -89,9 +83,6 @@ export function mergeConfigFile(p: z.infer<typeof ConfigFileSchema>): MergeOutco
89
83
  if (p.policy?.switchModels) {
90
84
  cfg.policy.switchModels = p.policy.switchModels.map((s) => s.toLowerCase());
91
85
  }
92
- if (p.policy?.preferToUseBankedReset) {
93
- cfg.policy.preferToUseBankedReset = uniq(p.policy.preferToUseBankedReset);
94
- }
95
86
  const envBin = realClaudeBinFromEnv();
96
87
  if (envBin) cfg.claudeBin = envBin;
97
88
  const envCodexBin = realCodexBinFromEnv();
@@ -196,29 +187,6 @@ export function saveLastSwapAt(ts: number): void {
196
187
  writeFileAtomic(paths.lastSwapJson, JSON.stringify(LastSwapSchema.parse({ ts })));
197
188
  }
198
189
 
199
- export function loadLastResetAt(): number | null {
200
- if (!existsSync(paths.lastResetJson)) return null;
201
- let json: unknown;
202
- try {
203
- json = JSON.parse(readFileSync(paths.lastResetJson, "utf8"));
204
- } catch {
205
- throw new Error(`${paths.lastResetJson} is corrupt (unparsable JSON) - refusing to treat a damaged reset clock as never-reset; repair or remove the file`);
206
- }
207
- return LastSwapSchema.parse(json).ts;
208
- }
209
-
210
- export function saveLastResetAt(ts: number): void {
211
- writeFileAtomic(paths.lastResetJson, JSON.stringify(LastSwapSchema.parse({ ts })));
212
- }
213
-
214
- export function loadLastSeatChangeAt(): number | null {
215
- const swapAt = loadLastSwapAt();
216
- const resetAt = loadLastResetAt();
217
- if (swapAt == null) return resetAt;
218
- if (resetAt == null) return swapAt;
219
- return Math.max(swapAt, resetAt);
220
- }
221
-
222
190
  const DepletedWaitSchema = z.object({ waitUntil: z.number(), accountUuid: z.string(), ts: z.number() });
223
191
  export type DepletedWait = z.infer<typeof DepletedWaitSchema>;
224
192
 
@@ -270,61 +238,13 @@ export function clearNextCheck(): void {
270
238
 
271
239
  const USAGE_TS_REFRESH_MS = 10 * 60_000;
272
240
  const SAMPLED_AT_REFRESH_MS = 30_000;
273
- const SESSION_COST_MIN_DELTA = 25;
274
-
275
- function anchorHolds(anchor: WindowAnchor, next: UsageState): boolean {
276
- return (
277
- anchor.fiveHourResetsAt === next.fiveHour.resetsAt &&
278
- anchor.sevenDayResetsAt === next.sevenDay.resetsAt &&
279
- next.fiveHour.usedPercentage >= anchor.session &&
280
- next.sevenDay.usedPercentage >= anchor.weekly
281
- );
282
- }
283
-
284
- export function measureSessionWindow(prev: UsageState | null, next: UsageState, stamp: boolean): Pick<UsageState, "anchor" | "sessionWindowWeeklyCost"> {
285
- const same = prev != null && prev.account === next.account;
286
- const carried = same ? prev.sessionWindowWeeklyCost : undefined;
287
- if (stamp) return { ...(same && prev.anchor ? { anchor: prev.anchor } : {}), ...(carried != null ? { sessionWindowWeeklyCost: carried } : {}) };
288
- const anchor: WindowAnchor =
289
- same && prev.anchor && anchorHolds(prev.anchor, next)
290
- ? prev.anchor
291
- : { fiveHourResetsAt: next.fiveHour.resetsAt, sevenDayResetsAt: next.sevenDay.resetsAt, session: next.fiveHour.usedPercentage, weekly: next.sevenDay.usedPercentage };
292
- const sessionDelta = next.fiveHour.usedPercentage - anchor.session;
293
- const weeklyDelta = next.sevenDay.usedPercentage - anchor.weekly;
294
- const measured = sessionDelta >= SESSION_COST_MIN_DELTA && weeklyDelta > 0 ? (100 * weeklyDelta) / sessionDelta : undefined;
295
- const cost = measured ?? carried;
296
- return { anchor, ...(cost != null ? { sessionWindowWeeklyCost: cost } : {}) };
297
- }
298
-
299
- function familyAnchorHolds(anchor: FamilyAnchor, session: UsageWindow, caps: Record<string, number>): boolean {
300
- return (
301
- anchor.fiveHourResetsAt === session.resetsAt &&
302
- session.usedPercentage >= anchor.session &&
303
- Object.entries(caps).every(([family, used]) => anchor.caps[family] == null || used >= anchor.caps[family])
304
- );
305
- }
306
-
307
- export function measureFamilyWindow(prev: ModelUsageState | null, account: string | null, session: UsageWindow | null, caps: Record<string, number>): Pick<ModelUsageState, "anchor" | "familyCosts"> {
308
- const same = prev != null && prev.account === account;
309
- const carried = same ? prev.familyCosts : undefined;
310
- if (session == null) return { ...(same && prev.anchor ? { anchor: prev.anchor } : {}), ...(carried != null ? { familyCosts: carried } : {}) };
311
- const held = same && prev.anchor && familyAnchorHolds(prev.anchor, session, caps) ? prev.anchor : null;
312
- const anchor: FamilyAnchor = held ? { ...held, caps: { ...caps, ...held.caps } } : { fiveHourResetsAt: session.resetsAt, session: session.usedPercentage, caps };
313
- const sessionDelta = session.usedPercentage - anchor.session;
314
- const measured = Object.entries(caps).flatMap(([family, used]) => {
315
- const capDelta = used - (anchor.caps[family] ?? used);
316
- return sessionDelta >= SESSION_COST_MIN_DELTA && capDelta > 0 ? [[family, (100 * capDelta) / sessionDelta] as const] : [];
317
- });
318
- const familyCosts = { ...carried, ...Object.fromEntries(measured) };
319
- return { anchor, ...(Object.keys(familyCosts).length > 0 ? { familyCosts } : {}) };
320
- }
321
241
 
322
242
  export function writeUsage(input: UsageState, opts: { stamp?: boolean } = {}): boolean {
323
243
  const prev = loadUsage();
324
244
  const stamp = opts.stamp === true;
325
245
  const carriedSampleAt = prev != null && prev.account === input.account ? (prev.sampledAt ?? prev.ts) : undefined;
326
246
  const sampledAt = stamp ? (carriedSampleAt ?? input.sampledAt) : input.ts;
327
- const next: UsageState = { ...input, ...measureSessionWindow(prev, input, stamp), ...(sampledAt != null ? { sampledAt } : {}) };
247
+ const next: UsageState = { ...input, ...(sampledAt != null ? { sampledAt } : {}) };
328
248
  if (
329
249
  prev &&
330
250
  isEqual({ ...prev, ts: 0, sampledAt: 0 }, { ...next, ts: 0, sampledAt: 0 }) &&
package/src/lib/swap.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { clearDepletedWait, clearNextCheck, clearUsageSnapshots, loadAccounts, saveAccounts, saveLastSwapAt } from "./state.ts";
2
3
  import { readItem, writeItem, liveTarget, parkedTarget, claudeAiOauthOnly, mergeIntoLive } from "./credstore.ts";
3
4
  import { refreshCredential, isAccessTokenExpiring, isDeadCredential, fetchTokenIdentity, describeIdentity, IdentityUnavailableError, InvalidGrantError, RefreshRejectedError } from "./oauth.ts";
@@ -11,6 +12,9 @@ function parseBlob(raw: string) {
11
12
  return CredentialBlobSchema.parse(JSON.parse(raw));
12
13
  }
13
14
 
15
+ const VerifyVerdictSchema = z.enum(["go", "rerank", "skip"]);
16
+ export type VerifyVerdict = z.infer<typeof VerifyVerdictSchema>;
17
+
14
18
  export async function performSwap(target: Account): Promise<void> {
15
19
  const idx = loadAccounts();
16
20
 
@@ -196,13 +200,25 @@ export function isSkippableSwapError(e: unknown): boolean {
196
200
  return e instanceof InvalidGrantError || e instanceof RefreshRejectedError || e instanceof IdentityUnavailableError;
197
201
  }
198
202
 
199
- export async function chooseAndSwap(ctx: PickCtx, exclude: ReadonlySet<string> = new Set()): Promise<Account | null> {
203
+ export async function chooseAndSwap(
204
+ ctx: PickCtx,
205
+ exclude: ReadonlySet<string> = new Set(),
206
+ verify?: (candidate: Account, ctx: PickCtx) => Promise<VerifyVerdict>,
207
+ ): Promise<Account | null> {
200
208
  const tried = new Set<string>(exclude);
201
209
  while (true) {
202
210
  const idx = loadAccounts();
203
211
  const candidates = idx.accounts.filter((a) => !tried.has(a.accountUuid));
204
212
  const best = pickBest(candidates, ctx);
205
213
  if (!best) return null;
214
+ if (verify) {
215
+ const verdict = await verify(best, ctx);
216
+ if (verdict === "rerank") continue;
217
+ if (verdict === "skip") {
218
+ tried.add(best.accountUuid);
219
+ continue;
220
+ }
221
+ }
206
222
  tried.add(best.accountUuid);
207
223
  try {
208
224
  await performSwap(best);