tokenmaxxing 1.114.0 → 1.116.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/DESIGN.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/render.ts +1 -1
- package/src/cli/status.ts +4 -3
- package/src/entries/supervisor.ts +1 -0
- package/src/lib/codex.ts +6 -3
- package/src/lib/codexusage.ts +2 -0
- package/src/lib/decide.ts +6 -4
- package/src/lib/picker.ts +13 -4
- package/src/lib/types.ts +7 -3
- package/src/lib/usage.ts +3 -1
package/DESIGN.md
CHANGED
|
@@ -66,7 +66,7 @@ The supervisor sees `respawn/<sid>`, SIGTERMs its child at the already-committed
|
|
|
66
66
|
|
|
67
67
|
### 3.4 Placement (supervisor, under the pool lock)
|
|
68
68
|
1. Fold every account's tee into the index.
|
|
69
|
-
2. Rank the usable accounts (§5) with the live session counts from `live/`, or the soonest-recovering account when none is usable; with no pooled account or every account needing re-auth, launch without a store (still supervised, on whatever login the environment names). Print mode, every subcommand, the bare resume picker, an invalid `--session-id`,
|
|
69
|
+
2. Rank the usable accounts (§5) with the live session counts from `live/`, or the soonest-recovering account when none is usable; with no pooled account or every account needing re-auth, launch without a store (still supervised, on whatever login the environment names). Print mode, every subcommand, the bare resume picker, an invalid `--session-id`, `--fork-session` with a resume, and `--no-session-persistence` (no transcript, so a move could not resume the run) are passed through unmanaged.
|
|
70
70
|
3. Spawn claude with the store env set, then write `live/<sid>` with the child's pid and start time. The lock covers the pick and the presence write, so concurrent launches see each other.
|
|
71
71
|
|
|
72
72
|
### 3.5 Multiple concurrent sessions
|
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ See [How switching decides](docs/content/docs/switching.mdx) for the policy and
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
`thresholds.session` and `thresholds.weekly` are the two bars, one number each (an array `thresholds.session`, the former ladder, fails config loading with a message naming the field); `projectionMargin` is a fixed safety margin subtracted from the session bar only (default 3), so a large turn is less likely to blow past the 5-hour bar between checks; the weekly bar takes no margin because one turn cannot overshoot a week; `thresholds.accounts` gives one account its own pair, keyed by label (`{"shared-seat": {"session": 75, "weekly": 75}}`), and `policy.accountReleaseMs` (30 minutes for the session window, 5 hours for a weekly window by default) lifts each of its windows to the higher of its own and the global bar that close to the window's reset; `switchModels` names the models whose per-model cap triggers a move; `usagePollTtlMs` is the shortest sample interval, the one an account near a bar gets, for a seat's own sample, for `status`, and for the accounts each check tick samples; `maxWaitMs` bounds the depleted-pool countdown - a reset further out than this does not pause the session (no respawn marker is written and the session simply keeps hitting its limit until an account recovers); `checkIntervalMs` is the periodic check tick (default 60s), which `init` writes into the timer - re-run `tokenmaxxing init` after changing it so the timer unit picks up the new tick. `claudeBin`, `codexBin`, `grokBin`, `opencodeBin`, and `piBin` pin the real binaries; `init` writes them, and `TOKENMAXXING_CLAUDE_BIN`, `TOKENMAXXING_CODEX_BIN`, `TOKENMAXXING_GROK_BIN`, `TOKENMAXXING_OPENCODE_BIN`, and `TOKENMAXXING_PI_BIN` override them for one process, except that `init` pins the binary it resolved into `config.json`.
|
|
115
|
+
`thresholds.session` and `thresholds.weekly` are the two bars, one number each (an array `thresholds.session`, the former ladder, fails config loading with a message naming the field); `projectionMargin` is a fixed safety margin subtracted from the session bar only (default 3), so a large turn is less likely to blow past the 5-hour bar between checks; the weekly bar takes no margin because one turn cannot overshoot a week; `thresholds.accounts` gives one account its own pair, keyed by label (`{"shared-seat": {"session": 75, "weekly": 75}}`), and `"credits": true` in an entry keeps an account with purchased usage credits usable past its windows, ranked after every account with plan headroom left; `policy.accountReleaseMs` (30 minutes for the session window, 5 hours for a weekly window by default) lifts each of its windows to the higher of its own and the global bar that close to the window's reset; `switchModels` names the models whose per-model cap triggers a move; `usagePollTtlMs` is the shortest sample interval, the one an account near a bar gets, for a seat's own sample, for `status`, and for the accounts each check tick samples; `maxWaitMs` bounds the depleted-pool countdown - a reset further out than this does not pause the session (no respawn marker is written and the session simply keeps hitting its limit until an account recovers); `checkIntervalMs` is the periodic check tick (default 60s), which `init` writes into the timer - re-run `tokenmaxxing init` after changing it so the timer unit picks up the new tick. `claudeBin`, `codexBin`, `grokBin`, `opencodeBin`, and `piBin` pin the real binaries; `init` writes them, and `TOKENMAXXING_CLAUDE_BIN`, `TOKENMAXXING_CODEX_BIN`, `TOKENMAXXING_GROK_BIN`, `TOKENMAXXING_OPENCODE_BIN`, and `TOKENMAXXING_PI_BIN` override them for one process, except that `init` pins the binary it resolved into `config.json`.
|
|
116
116
|
|
|
117
117
|
State lives in `~/.config/tokenmaxxing/`; outside it the install touches Claude Code's `settings.json`, codex's `hooks.json`, the timer and usage hub units, and your shell rc. Each account's credential store is `stores/<uuid8>/`, which Claude Code reads and refreshes as its own: on macOS the credential is a login-keychain item keyed by the store path (never plaintext on disk), on Linux a 0600 `.credentials.json` inside the store (the same plaintext model claude itself uses for `~/.claude/.credentials.json`).
|
|
118
118
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.116.0",
|
|
4
4
|
"description": "Automatic Claude Code account switching: pool multiple accounts, start each session on its own account, and resume a session on a fresher account when quota fills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/cli/render.ts
CHANGED
|
@@ -46,7 +46,7 @@ export function bar(pct: number, width = 16): string {
|
|
|
46
46
|
const clamped = clamp(pct, 0, 100);
|
|
47
47
|
const filled = Math.round((clamped / 100) * width);
|
|
48
48
|
const body = "█".repeat(filled) + "░".repeat(width - filled);
|
|
49
|
-
const label = `${
|
|
49
|
+
const label = `${Math.max(0, pct).toFixed(0).padStart(3)}%`;
|
|
50
50
|
const paint = clamped >= 95 ? c.red : clamped >= 75 ? c.yellow : c.green;
|
|
51
51
|
return `${paint(body)} ${label}`;
|
|
52
52
|
}
|
package/src/cli/status.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { codexPool, grokPool, opencodeGoPool } from "../lib/paths.ts";
|
|
|
9
9
|
import { barFor, earliestReset, gatedWindows, landWindows, isExhausted, isSessionWindow, limitWindows, liveUsed, nextWeeklyReset, sessionWindow, thresholdBars, weeklyWindow } from "../lib/picker.ts";
|
|
10
10
|
import type { Provider, SampleReport } from "../lib/provider.ts";
|
|
11
11
|
import { bar, c, count, emitJson, fmtAgo } from "./render.ts";
|
|
12
|
-
import type { Account, Bars, Config, Thresholds, Window } from "../lib/types.ts";
|
|
12
|
+
import type { Account, AccountThresholds, Bars, Config, Thresholds, Window } from "../lib/types.ts";
|
|
13
13
|
|
|
14
14
|
export type WindowReport = { usedPercentage: number; resetsAt: number | null; windowSeconds: number | null };
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ type StatusAccount = {
|
|
|
24
24
|
sessions: number;
|
|
25
25
|
needsReauth: boolean;
|
|
26
26
|
exhausted: boolean;
|
|
27
|
-
thresholds:
|
|
27
|
+
thresholds: AccountThresholds | null;
|
|
28
28
|
usage: UsageReport | null;
|
|
29
29
|
usageAt: number | null;
|
|
30
30
|
limitsAt: number | null;
|
|
@@ -116,6 +116,7 @@ async function collect(p: Provider, cfg: Config, now: number, cached: boolean):
|
|
|
116
116
|
}
|
|
117
117
|
if (a.lastUsageAt != null && (stored.lastUsageAt == null || a.lastUsageAt > stored.lastUsageAt)) {
|
|
118
118
|
landWindows(stored, p.mergeWindows(a.windows, stored.windows), a.lastUsageAt, bars);
|
|
119
|
+
stored.hasCredits = a.hasCredits;
|
|
119
120
|
if (a.email != null) stored.email = a.email;
|
|
120
121
|
if (a.tier != null) stored.tier = a.tier;
|
|
121
122
|
dirty = true;
|
|
@@ -221,7 +222,7 @@ function card(p: Provider, a: StatusAccount, now: number, staleAfterMs: number):
|
|
|
221
222
|
for (const w of a.usage.limits) lines.push(usageRow(p.windowLabel(w.name), w));
|
|
222
223
|
}
|
|
223
224
|
const notes: Note[] = [];
|
|
224
|
-
if (a.thresholds) notes.push({ paint: c.dim, text: `thresholds 5h ${a.thresholds.session}% weekly ${a.thresholds.weekly}
|
|
225
|
+
if (a.thresholds) notes.push({ paint: c.dim, text: `thresholds 5h ${a.thresholds.session}% weekly ${a.thresholds.weekly}%${a.thresholds.credits ? ", usable past them on credits" : ""}` });
|
|
225
226
|
if (a.sample.ok && a.sample.source === "statusline") {
|
|
226
227
|
const stale = a.usageAt == null || now - a.usageAt > staleAfterMs;
|
|
227
228
|
const age = a.usageAt != null ? fmtAgo(a.usageAt, now) : "age unknown";
|
|
@@ -81,6 +81,7 @@ export function analyzeArgs(argv: string[]): Analysis {
|
|
|
81
81
|
if (a === "--") break;
|
|
82
82
|
else if (a === "-p" || a === "--print") printMode = true;
|
|
83
83
|
else if (a === "--version" || a === "-v" || a === "--help" || a === "-h") printMode = true;
|
|
84
|
+
else if (a === "--no-session-persistence") printMode = true;
|
|
84
85
|
else if (a === "--session-id") {
|
|
85
86
|
const next = argv[++i] ?? null;
|
|
86
87
|
if (next && isUuid(next)) sessionId = next;
|
package/src/lib/codex.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { codexSupervisorLink, ensurePathInRc, installCodexSupervisor, managedShe
|
|
|
13
13
|
import { withLock } from "./lock.ts";
|
|
14
14
|
import { errorMessage, log } from "./log.ts";
|
|
15
15
|
import { codexPaths, codexPool, codexSeatFromEnv, optionalEnv } from "./paths.ts";
|
|
16
|
-
import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
16
|
+
import { isExhausted, onCredits, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
17
17
|
import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
|
|
18
18
|
import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
19
19
|
import { restoreTermios, saveTermios } from "./tty.ts";
|
|
@@ -33,6 +33,7 @@ function presence(): Map<string, number> {
|
|
|
33
33
|
function applyUsage(account: Account, usage: CodexUsage, at: number): void {
|
|
34
34
|
account.windows = usage.windows;
|
|
35
35
|
account.lastUsageAt = at;
|
|
36
|
+
account.hasCredits = usage.hasCredits ?? undefined;
|
|
36
37
|
if (usage.email != null) account.email = usage.email;
|
|
37
38
|
if (usage.planType != null) account.tier = usage.planType;
|
|
38
39
|
}
|
|
@@ -272,8 +273,10 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
272
273
|
const usable = idx.accounts.filter(
|
|
273
274
|
(a) => !closed.has(a.id) && a.needsReauth !== true && !isExhausted(a, ctx) && codexStoreUsable(a.id)
|
|
274
275
|
);
|
|
275
|
-
const
|
|
276
|
-
const
|
|
276
|
+
const plan = usable.filter((a) => !onCredits(a, ctx));
|
|
277
|
+
const open = plan.length > 0 ? plan : usable;
|
|
278
|
+
const fewest = Math.min(...open.map((a) => lent[a.id] ?? 0));
|
|
279
|
+
const picked = pickBest(open.filter((a) => (lent[a.id] ?? 0) === fewest), ctx);
|
|
277
280
|
if (!picked) return null;
|
|
278
281
|
const store = ensureCodexStoreHome(picked.id);
|
|
279
282
|
writePresence({ dir: codexPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
package/src/lib/codexusage.ts
CHANGED
|
@@ -32,6 +32,7 @@ const WireUsageSchema = z.looseObject({
|
|
|
32
32
|
email: z.string().nullish(),
|
|
33
33
|
plan_type: z.string().nullish(),
|
|
34
34
|
rate_limit: WireRateLimitSchema.nullish(),
|
|
35
|
+
credits: z.looseObject({ has_credits: z.boolean().nullish() }).nullish(),
|
|
35
36
|
additional_rate_limits: z
|
|
36
37
|
.array(z.looseObject({ limit_name: z.string(), rate_limit: WireRateLimitSchema.nullish() }))
|
|
37
38
|
.nullish(),
|
|
@@ -81,6 +82,7 @@ export async function fetchCodexUsage(input: { auth: CodexAuthJson; at: number }
|
|
|
81
82
|
accountId: wire.account_id,
|
|
82
83
|
email: wire.email ?? null,
|
|
83
84
|
planType: wire.plan_type ?? null,
|
|
85
|
+
hasCredits: wire.credits?.has_credits ?? null,
|
|
84
86
|
windows: [
|
|
85
87
|
...toWindows(wire.rate_limit, null, at),
|
|
86
88
|
...(wire.additional_rate_limits ?? []).flatMap((row) => toWindows(row.rate_limit, row.limit_name, at)),
|
package/src/lib/decide.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { countBy } from "es-toolkit";
|
|
|
2
2
|
import { CLAUDE_COMPACT_KILL_MS } from "./compact.ts";
|
|
3
3
|
import { withLock } from "./lock.ts";
|
|
4
4
|
import { loadAccounts, loadConfig, liveWaitClaims, releaseWaitClaim, replaceWaitClaim, saveAccounts } from "./state.ts";
|
|
5
|
-
import { isExhausted, landWindows, limitWindows, nextWeeklyReset, pickBest, pickWaitTarget, screensUntilReset, sessionWindow, thresholdBars, weeklyWindow, type PickCtx } from "./picker.ts";
|
|
5
|
+
import { isExhausted, landWindows, limitWindows, liveUsed, nextWeeklyReset, pickBest, pickWaitTarget, screensUntilReset, sessionWindow, thresholdBars, weeklyWindow, type PickCtx } from "./picker.ts";
|
|
6
6
|
import { familyTokens } from "./usage.ts";
|
|
7
7
|
import { errorMessage, log } from "./log.ts";
|
|
8
8
|
import type { Observation, Provider } from "./provider.ts";
|
|
@@ -48,11 +48,13 @@ function enforcedWall(limit: EnforcedLimit, account: Account, now: number): numb
|
|
|
48
48
|
.map((w) => w.resetsAt)
|
|
49
49
|
.find((r): r is number => r != null) ?? null;
|
|
50
50
|
const session = sessionWindow(account)?.resetsAt ?? null;
|
|
51
|
+
const weekly = weeklyWindow(account);
|
|
52
|
+
const span = limit.kind !== "overage" ? limit.kind : weekly != null && liveUsed(weekly, now) >= 100 ? "weekly" : "session";
|
|
51
53
|
const cachedReset =
|
|
52
|
-
|
|
54
|
+
span === "session"
|
|
53
55
|
? session != null && session > now ? session : null
|
|
54
|
-
: nextWeeklyReset(familyReset ??
|
|
55
|
-
return limit.resetsAt ?? cachedReset ?? now + (
|
|
56
|
+
: nextWeeklyReset(familyReset ?? weekly?.resetsAt ?? null, now);
|
|
57
|
+
return limit.resetsAt ?? cachedReset ?? now + (span === "session" ? FIVE_HOURS_MS : WEEK_MS);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export async function evaluateAndMaybeSwap(p: Provider, now = Date.now(), canRespawn = false, enforced: EnforcedLimit | null = null, opts: EvalOpts = {}): Promise<SwapDecision> {
|
package/src/lib/picker.ts
CHANGED
|
@@ -6,7 +6,7 @@ export function thresholdBars(cfg: Config): Bars {
|
|
|
6
6
|
const bars = (t: Thresholds): Thresholds => ({ session: t.session - cfg.policy.projectionMargin, weekly: t.weekly });
|
|
7
7
|
return {
|
|
8
8
|
...bars(cfg.thresholds),
|
|
9
|
-
accounts: new Map(Object.entries(cfg.thresholds.accounts).map(([label, t]) => [label, bars(t)])),
|
|
9
|
+
accounts: new Map(Object.entries(cfg.thresholds.accounts).map(([label, t]) => [label, { ...bars(t), credits: t.credits }])),
|
|
10
10
|
accountReleaseMs: cfg.policy.accountReleaseMs,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -68,9 +68,13 @@ function windowEnd(w: Window): number {
|
|
|
68
68
|
return w.resetsAt ?? w.sampledAt + (w.windowSeconds ?? 0) * 1000;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function spendsCredits(a: Account, bars: Bars): boolean {
|
|
72
|
+
return bars.accounts.get(a.label)?.credits === true && a.hasCredits !== false;
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
function blockedUntil(a: Account, w: Window, bars: Bars, now: number): number {
|
|
72
76
|
const used = liveUsed(w, now);
|
|
73
|
-
if (used < barFor(a, w, bars, now)) return 0;
|
|
77
|
+
if (used < barFor(a, w, bars, now) || spendsCredits(a, bars)) return 0;
|
|
74
78
|
const reset = windowEnd(w);
|
|
75
79
|
const release = reset - releaseMs(w, bars);
|
|
76
80
|
return release > now && used < barFor(a, w, bars, release) ? release : reset;
|
|
@@ -95,10 +99,10 @@ export function isExhausted(a: Account, ctx: PickCtx): boolean {
|
|
|
95
99
|
|
|
96
100
|
export function landWindows(a: Account, windows: Window[], at: number, thresholds: Bars): void {
|
|
97
101
|
const ctx: PickCtx = { now: at, thresholds, currentId: null, families: [], seats: null };
|
|
98
|
-
const blocked = a.enforcedUntil != null && isExhausted({ ...a, enforcedUntil: undefined }, ctx);
|
|
102
|
+
const blocked = a.enforcedUntil != null && isExhausted({ ...a, enforcedUntil: undefined, hasCredits: false }, ctx);
|
|
99
103
|
a.windows = windows;
|
|
100
104
|
a.lastUsageAt = at;
|
|
101
|
-
if (blocked && !isExhausted({ ...a, enforcedUntil: undefined }, ctx)) a.enforcedUntil = undefined;
|
|
105
|
+
if (blocked && !isExhausted({ ...a, enforcedUntil: undefined, hasCredits: false }, ctx)) a.enforcedUntil = undefined;
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
export function nextWeeklyReset(resetsAt: number | null, now: number): number | null {
|
|
@@ -139,7 +143,12 @@ export function seatHeadroom(a: Account, ctx: PickCtx): number {
|
|
|
139
143
|
return (barFor(a, session, ctx.thresholds, ctx.now) - liveUsed(session, ctx.now)) / ((ctx.seats?.get(a.id) ?? 0) + 1);
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
export function onCredits(a: Account, ctx: PickCtx): boolean {
|
|
147
|
+
return spendsCredits(a, ctx.thresholds) && isExhausted({ ...a, hasCredits: false }, ctx);
|
|
148
|
+
}
|
|
149
|
+
|
|
142
150
|
const swapPreference = (ctx: PickCtx) => [
|
|
151
|
+
(a: Account) => (onCredits(a, ctx) ? 1 : 0),
|
|
143
152
|
...(ctx.seats == null ? [] : [(a: Account) => -seatHeadroom(a, ctx)]),
|
|
144
153
|
(a: Account) => -pacePressure(a, ctx),
|
|
145
154
|
(a: Account) => weeklyExpiry(a, ctx.now),
|
package/src/lib/types.ts
CHANGED
|
@@ -72,6 +72,7 @@ export const AccountSchema = z.object({
|
|
|
72
72
|
usageRetryAt: z.number().optional(),
|
|
73
73
|
enforcedUntil: z.number().optional(),
|
|
74
74
|
needsReauth: z.boolean().optional(),
|
|
75
|
+
hasCredits: z.boolean().optional(),
|
|
75
76
|
oauthAccount: OAuthAccountSchema.optional(),
|
|
76
77
|
});
|
|
77
78
|
export type Account = z.infer<typeof AccountSchema>;
|
|
@@ -84,7 +85,7 @@ export type AccountsIndex = z.infer<typeof AccountsIndexSchema>;
|
|
|
84
85
|
|
|
85
86
|
export type EnforcedLimit = {
|
|
86
87
|
account: string;
|
|
87
|
-
kind: "session" | "weekly" | "model" | "credits" | "org";
|
|
88
|
+
kind: "session" | "weekly" | "model" | "credits" | "org" | "overage";
|
|
88
89
|
family: string | null;
|
|
89
90
|
resetsAt: number | null;
|
|
90
91
|
blind: boolean;
|
|
@@ -92,7 +93,9 @@ export type EnforcedLimit = {
|
|
|
92
93
|
|
|
93
94
|
export type Thresholds = { session: number; weekly: number };
|
|
94
95
|
|
|
95
|
-
export type
|
|
96
|
+
export type AccountThresholds = Thresholds & { credits: boolean };
|
|
97
|
+
|
|
98
|
+
export type Bars = Thresholds & { accounts: Map<string, AccountThresholds>; accountReleaseMs: Thresholds };
|
|
96
99
|
|
|
97
100
|
const PercentSchema = z.number().min(0).max(100);
|
|
98
101
|
|
|
@@ -102,7 +105,7 @@ export const ConfigSchema = z
|
|
|
102
105
|
.object({
|
|
103
106
|
session: PercentSchema.default(90),
|
|
104
107
|
weekly: PercentSchema.default(98),
|
|
105
|
-
accounts: z.record(z.string(), z.object({ session: PercentSchema, weekly: PercentSchema })).default({}),
|
|
108
|
+
accounts: z.record(z.string(), z.object({ session: PercentSchema, weekly: PercentSchema, credits: z.boolean().default(false) })).default({}),
|
|
106
109
|
})
|
|
107
110
|
.prefault({}),
|
|
108
111
|
claudeBin: z.string().default(""),
|
|
@@ -248,6 +251,7 @@ export type CodexUsage = {
|
|
|
248
251
|
accountId: string;
|
|
249
252
|
email: string | null;
|
|
250
253
|
planType: string | null;
|
|
254
|
+
hasCredits: boolean | null;
|
|
251
255
|
windows: Window[];
|
|
252
256
|
};
|
|
253
257
|
|
package/src/lib/usage.ts
CHANGED
|
@@ -115,7 +115,8 @@ export type EnforcedClass =
|
|
|
115
115
|
| { kind: "weekly"; resetsAt: number | null }
|
|
116
116
|
| { kind: "model"; family: string; resetsAt: number | null }
|
|
117
117
|
| { kind: "credits"; family: string; resetsAt: null }
|
|
118
|
-
| { kind: "org"; resetsAt: null }
|
|
118
|
+
| { kind: "org"; resetsAt: null }
|
|
119
|
+
| { kind: "overage"; resetsAt: null };
|
|
119
120
|
|
|
120
121
|
const ErrorBodySchema = z.looseObject({
|
|
121
122
|
error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
|
|
@@ -140,6 +141,7 @@ export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]
|
|
|
140
141
|
const type = q.rateLimitType ?? "";
|
|
141
142
|
if (type === "five_hour") return { kind: "session", resetsAt };
|
|
142
143
|
if (type === "seven_day") return { kind: "weekly", resetsAt };
|
|
144
|
+
if (type === "overage") return { kind: "overage", resetsAt: null };
|
|
143
145
|
const family = switchModels.find((f) => type.includes(f));
|
|
144
146
|
return family ? { kind: "model", family, resetsAt } : null;
|
|
145
147
|
}
|