tokenmaxxing 1.171.0 → 1.172.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/package.json +1 -1
- package/src/lib/codex.ts +38 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.172.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/lib/codex.ts
CHANGED
|
@@ -41,13 +41,22 @@ function applyUsage(account: Account, usage: CodexUsage, at: number): void {
|
|
|
41
41
|
if (usage.planType != null) account.tier = usage.planType;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
type CodexReadFailure = { ok: false; reason: string; deadGrant: boolean; expiring?: boolean; rejectedToken?: string };
|
|
44
|
+
type CodexReadFailure = { ok: false; reason: string; deadGrant: boolean; deadAccessToken?: string; expiring?: boolean; rejected?: boolean; rejectedToken?: string };
|
|
45
45
|
type CodexReadOutcome = { ok: true; usage: CodexUsage; at: number } | CodexReadFailure;
|
|
46
46
|
|
|
47
47
|
const REJECTED_REFRESH_BACKOFF_MS = 60 * 60 * 1000;
|
|
48
48
|
|
|
49
49
|
function afterRejection(rejected: CodexReadFailure, refreshFailure: CodexReadFailure): CodexReadFailure {
|
|
50
|
-
return { ...refreshFailure, reason: `${rejected.reason}; refresh skipped: ${refreshFailure.reason}` };
|
|
50
|
+
return { ...refreshFailure, rejected: true, reason: `${rejected.reason}; refresh skipped: ${refreshFailure.reason}` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function storeHolds(accountId: string, accessToken: string | undefined): boolean {
|
|
54
|
+
if (accessToken === undefined) return true;
|
|
55
|
+
try {
|
|
56
|
+
return readCodexStoreAuth(accountId)?.tokens.access_token === accessToken;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
async function freshCodexAuth(account: Account, now: number, refresh: boolean, holder?: string, rejectedToken?: string): Promise<{ ok: true; auth: CodexAuthJson } | CodexReadFailure> {
|
|
@@ -70,7 +79,7 @@ async function freshCodexAuth(account: Account, now: number, refresh: boolean, h
|
|
|
70
79
|
try {
|
|
71
80
|
auth = await refreshCodexAuth({ auth, now });
|
|
72
81
|
} catch (e) {
|
|
73
|
-
if (e instanceof CodexInvalidGrantError) return { ok: false, reason: e.message, deadGrant: true };
|
|
82
|
+
if (e instanceof CodexInvalidGrantError) return { ok: false, reason: e.message, deadGrant: true, deadAccessToken: auth.tokens.access_token };
|
|
74
83
|
if (e instanceof CodexRefreshFailedError) return { ok: false, reason: e.message, deadGrant: false };
|
|
75
84
|
throw e;
|
|
76
85
|
}
|
|
@@ -85,22 +94,25 @@ async function readCodexUsageOnce(account: Account, now: number, refresh: boolea
|
|
|
85
94
|
const at = Date.now();
|
|
86
95
|
return { ok: true, usage: await fetchCodexUsage({ auth: fresh.auth, at }), at };
|
|
87
96
|
} catch (e) {
|
|
88
|
-
if (e instanceof CodexUsageReadError) return { ok: false, reason: e.message, deadGrant: false, rejectedToken: e.status === 401 ? fresh.auth.tokens.access_token : undefined };
|
|
97
|
+
if (e instanceof CodexUsageReadError) return { ok: false, reason: e.message, deadGrant: false, rejected: e.status === 401, rejectedToken: e.status === 401 ? fresh.auth.tokens.access_token : undefined };
|
|
89
98
|
throw e;
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
async function readCodexUsage(account: Account, now: number, refresh: boolean, holder?: string): Promise<CodexReadOutcome> {
|
|
94
103
|
const outcome = await readCodexUsageOnce(account, now, refresh, holder);
|
|
95
|
-
if (outcome.ok || outcome.rejectedToken === undefined
|
|
104
|
+
if (outcome.ok || outcome.rejectedToken === undefined) return outcome;
|
|
96
105
|
const { rejectedToken } = outcome;
|
|
97
106
|
const fresh = await withLock(codexPool.lockFile, () => freshCodexAuth(account, now, true, holder, rejectedToken));
|
|
98
|
-
|
|
107
|
+
if (!fresh.ok) return afterRejection(outcome, fresh);
|
|
108
|
+
const retried = await readCodexUsageOnce(account, now, false);
|
|
109
|
+
return retried.ok ? retried : { ...retried, rejectedToken: undefined };
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
export async function observeCodex(account: Account, cfg: Config, now: number, opts: { probe: boolean; refresh: boolean; holder?: string }): Promise<Observation | null> {
|
|
112
|
+
export async function observeCodex(account: Account, cfg: Config, now: number, opts: { probe: boolean; refresh: boolean; holder?: string; rejected?: Set<string> }): Promise<Observation | null> {
|
|
102
113
|
if (opts.probe && (account.lastUsageAt == null || now - account.lastUsageAt > cfg.policy.usagePollTtlMs)) {
|
|
103
114
|
const outcome = await readCodexUsage(account, now, opts.refresh, opts.holder);
|
|
115
|
+
if (!outcome.ok && outcome.rejected === true) opts.rejected?.add(account.id);
|
|
104
116
|
await withLock(codexPool.lockFile, () => {
|
|
105
117
|
const idx = loadAccounts(codexPool);
|
|
106
118
|
const a = idx.accounts.find((x) => x.id === account.id);
|
|
@@ -109,7 +121,7 @@ export async function observeCodex(account: Account, cfg: Config, now: number, o
|
|
|
109
121
|
if (a.lastUsageAt == null || outcome.at > a.lastUsageAt) {
|
|
110
122
|
applyUsage(a, outcome.usage, outcome.at);
|
|
111
123
|
}
|
|
112
|
-
} else if (outcome.deadGrant) {
|
|
124
|
+
} else if (outcome.deadGrant && storeHolds(a.id, outcome.deadAccessToken)) {
|
|
113
125
|
a.needsReauth = true;
|
|
114
126
|
}
|
|
115
127
|
saveAccounts(codexPool, idx);
|
|
@@ -126,23 +138,31 @@ async function samplePool(accounts: Account[], now: number): Promise<Map<string,
|
|
|
126
138
|
applyUsage(account, outcome.usage, outcome.at);
|
|
127
139
|
reports.set(account.id, { ok: true, source: "probe" });
|
|
128
140
|
} else {
|
|
129
|
-
if (outcome.deadGrant) account.needsReauth = true;
|
|
130
141
|
reports.set(account.id, { ok: false, reason: outcome.reason });
|
|
131
142
|
}
|
|
132
143
|
};
|
|
133
144
|
const expiring: { account: Account; rejected?: CodexReadFailure }[] = [];
|
|
134
145
|
await Promise.all(
|
|
135
146
|
accounts.map(async (account) => {
|
|
136
|
-
const outcome = await
|
|
147
|
+
const outcome = await readCodexUsageOnce(account, now, false);
|
|
137
148
|
if (!outcome.ok && (outcome.expiring === true || outcome.rejectedToken !== undefined)) expiring.push({ account, rejected: outcome.rejectedToken === undefined ? undefined : outcome });
|
|
138
149
|
else record(account, outcome);
|
|
139
150
|
}),
|
|
140
151
|
);
|
|
141
152
|
if (expiring.length > 0) {
|
|
142
|
-
const rotated = await withLock(codexPool.lockFile,
|
|
153
|
+
const rotated = await withLock(codexPool.lockFile, async () => {
|
|
154
|
+
const results = await Promise.all(expiring.map(async ({ account, rejected }) => ({ account, rejected, fresh: await freshCodexAuth(account, now, true, undefined, rejected?.rejectedToken) })));
|
|
155
|
+
const dead = new Set(results.filter(({ fresh }) => !fresh.ok && fresh.deadGrant).map(({ account }) => account.id));
|
|
156
|
+
if (dead.size > 0) {
|
|
157
|
+
const idx = loadAccounts(codexPool);
|
|
158
|
+
for (const a of idx.accounts) if (dead.has(a.id)) a.needsReauth = true;
|
|
159
|
+
saveAccounts(codexPool, idx);
|
|
160
|
+
}
|
|
161
|
+
return results;
|
|
162
|
+
});
|
|
143
163
|
await Promise.all(
|
|
144
164
|
rotated.map(async ({ account, rejected, fresh }) =>
|
|
145
|
-
record(account, fresh.ok ? await
|
|
165
|
+
record(account, fresh.ok ? await readCodexUsageOnce(account, now, false) : rejected ? afterRejection(rejected, fresh) : fresh),
|
|
146
166
|
),
|
|
147
167
|
);
|
|
148
168
|
}
|
|
@@ -271,10 +291,11 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
271
291
|
const now = Date.now();
|
|
272
292
|
const seatId = `seat-${pid}`;
|
|
273
293
|
const cfg = loadConfig();
|
|
294
|
+
const rejected = new Set<string>();
|
|
274
295
|
await Promise.all(
|
|
275
296
|
loadAccounts(codexPool)
|
|
276
297
|
.accounts.filter((a) => a.needsReauth !== true && codexStoreUsable(a.id))
|
|
277
|
-
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
298
|
+
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false, rejected })),
|
|
278
299
|
);
|
|
279
300
|
const granted = await withLock(codexPool.lockFile, (): SeatBorrow => {
|
|
280
301
|
const ctx = codexPickCtx(now, null);
|
|
@@ -283,6 +304,9 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
283
304
|
const held = living.find((p) => p.id === seatId);
|
|
284
305
|
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
285
306
|
if (heldAccount) {
|
|
307
|
+
if (rejected.has(heldAccount.id)) {
|
|
308
|
+
return { denied: "the usage endpoint rejects the account this pid holds and its store could not be refreshed now - borrow again later" };
|
|
309
|
+
}
|
|
286
310
|
if (heldAccount.needsReauth === true) {
|
|
287
311
|
return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth --codex` and borrow again" };
|
|
288
312
|
}
|
|
@@ -295,7 +319,7 @@ export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
|
295
319
|
const closed = new Set(living.filter((p) => p.id === parent || p.id.startsWith(PI_PRESENCE_PREFIX)).map((p) => p.accountId));
|
|
296
320
|
const lent = countBy(living, (p) => p.accountId);
|
|
297
321
|
const usable = idx.accounts.filter(
|
|
298
|
-
(a) => !closed.has(a.id) && a.needsReauth !== true && !isExhausted(a, ctx) && codexStoreUsable(a.id)
|
|
322
|
+
(a) => !closed.has(a.id) && !rejected.has(a.id) && a.needsReauth !== true && !isExhausted(a, ctx) && codexStoreUsable(a.id)
|
|
299
323
|
);
|
|
300
324
|
const plan = usable.filter((a) => !onCredits(a, ctx));
|
|
301
325
|
const open = plan.length > 0 ? plan : usable;
|