tokenmaxxing 1.100.0 → 1.101.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/entries/stopfailurehook.ts +6 -5
- package/src/lib/decide.ts +1 -0
- package/src/lib/settings.ts +2 -1
- package/src/lib/types.ts +1 -1
- package/src/lib/usage.ts +8 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.101.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",
|
|
@@ -4,7 +4,7 @@ import { evaluateAndMaybeSwap } from "../lib/decide.ts";
|
|
|
4
4
|
import { readStdin } from "../lib/proc.ts";
|
|
5
5
|
import { adoptLiveSession, refusedAccounts, supervisedSession, writeRespawnMarker } from "../lib/sessions.ts";
|
|
6
6
|
import { loadConfig } from "../lib/state.ts";
|
|
7
|
-
import { classifyEnforcedLimit, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
|
|
7
|
+
import { classifyEnforcedLimit, ENFORCED_ERRORS, findEnforcedRow, parseErrorBody, readTranscriptTail, type TranscriptRow } from "../lib/usage.ts";
|
|
8
8
|
import { paths } from "../lib/paths.ts";
|
|
9
9
|
import { JsonTextSchema, type EnforcedLimit } from "../lib/types.ts";
|
|
10
10
|
import { errorMessage, log } from "../lib/log.ts";
|
|
@@ -20,10 +20,10 @@ const StopFailureStdin = z.looseObject({
|
|
|
20
20
|
const ROW_WAIT_MS = 3_000;
|
|
21
21
|
const ROW_POLL_MS = 200;
|
|
22
22
|
|
|
23
|
-
async function awaitEnforcedRow(input: { transcriptPath: string; lastAssistantMessage: string | undefined; now: number }): Promise<TranscriptRow | null> {
|
|
23
|
+
async function awaitEnforcedRow(input: { transcriptPath: string; error: string; lastAssistantMessage: string | undefined; now: number }): Promise<TranscriptRow | null> {
|
|
24
24
|
const deadline = Date.now() + ROW_WAIT_MS;
|
|
25
25
|
while (true) {
|
|
26
|
-
const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), lastAssistantMessage: input.lastAssistantMessage, now: input.now });
|
|
26
|
+
const row = findEnforcedRow({ rows: await readTranscriptTail(input.transcriptPath), error: input.error, lastAssistantMessage: input.lastAssistantMessage, now: input.now });
|
|
27
27
|
if (row || Date.now() >= deadline) return row;
|
|
28
28
|
await Bun.sleep(ROW_POLL_MS);
|
|
29
29
|
}
|
|
@@ -36,7 +36,8 @@ export async function runStopFailureHook(): Promise<number> {
|
|
|
36
36
|
const now = Date.now();
|
|
37
37
|
const parsed = StopFailureStdin.safeParse(JsonTextSchema.safeParse(await readStdin()).data);
|
|
38
38
|
const stdin = parsed.success ? parsed.data : {};
|
|
39
|
-
|
|
39
|
+
const error = stdin.error ?? "rate_limit";
|
|
40
|
+
if (!ENFORCED_ERRORS.includes(error)) return 0;
|
|
40
41
|
|
|
41
42
|
const stdinSid = stdin.session_id;
|
|
42
43
|
const mainLoop = stdin.agent_id === undefined;
|
|
@@ -51,7 +52,7 @@ export async function runStopFailureHook(): Promise<number> {
|
|
|
51
52
|
const canRespawn = session != null && mainLoop;
|
|
52
53
|
const cfg = loadConfig();
|
|
53
54
|
const row = stdin.transcript_path
|
|
54
|
-
? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, lastAssistantMessage: stdin.last_assistant_message, now })
|
|
55
|
+
? await awaitEnforcedRow({ transcriptPath: stdin.transcript_path, error, lastAssistantMessage: stdin.last_assistant_message, now })
|
|
55
56
|
: null;
|
|
56
57
|
const limit = row ? classifyEnforcedLimit(row, cfg.policy.switchModels) : null;
|
|
57
58
|
|
package/src/lib/decide.ts
CHANGED
|
@@ -38,6 +38,7 @@ function modelKindCovered(limit: EnforcedLimit, account: Account, now: number, w
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function enforcedWall(limit: EnforcedLimit, account: Account, now: number): number {
|
|
41
|
+
if (limit.kind === "org") return now + FIVE_HOURS_MS;
|
|
41
42
|
const { family } = limit;
|
|
42
43
|
const familyReset =
|
|
43
44
|
family == null
|
package/src/lib/settings.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { paths } from "./paths.ts";
|
|
|
5
5
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
6
|
import { readJsonFile } from "./state.ts";
|
|
7
7
|
import { JsonTextSchema } from "./types.ts";
|
|
8
|
+
import { ENFORCED_ERRORS } from "./usage.ts";
|
|
8
9
|
|
|
9
10
|
export function installedBin(): string {
|
|
10
11
|
return join(paths.binDir, "tokenmaxxing");
|
|
@@ -29,7 +30,7 @@ const SUBCMD = {
|
|
|
29
30
|
sessionStart: "__session-start",
|
|
30
31
|
} as const;
|
|
31
32
|
|
|
32
|
-
const STOP_FAILURE_MATCHER = "
|
|
33
|
+
const STOP_FAILURE_MATCHER = ENFORCED_ERRORS.join("|");
|
|
33
34
|
|
|
34
35
|
function readSettings(): Settings {
|
|
35
36
|
if (!existsSync(paths.claudeSettings)) return {};
|
package/src/lib/types.ts
CHANGED
|
@@ -84,7 +84,7 @@ export type AccountsIndex = z.infer<typeof AccountsIndexSchema>;
|
|
|
84
84
|
|
|
85
85
|
export type EnforcedLimit = {
|
|
86
86
|
account: string;
|
|
87
|
-
kind: "session" | "weekly" | "model" | "credits";
|
|
87
|
+
kind: "session" | "weekly" | "model" | "credits" | "org";
|
|
88
88
|
family: string | null;
|
|
89
89
|
resetsAt: number | null;
|
|
90
90
|
blind: boolean;
|
package/src/lib/usage.ts
CHANGED
|
@@ -86,11 +86,13 @@ export function transcriptRowText(row: TranscriptRow): string {
|
|
|
86
86
|
|
|
87
87
|
const ROW_RECENCY_MS = 60_000;
|
|
88
88
|
|
|
89
|
-
export
|
|
90
|
-
|
|
89
|
+
export const ENFORCED_ERRORS = ["rate_limit", "oauth_org_not_allowed"];
|
|
90
|
+
|
|
91
|
+
export function findEnforcedRow(input: { rows: TranscriptRow[]; error: string; lastAssistantMessage: string | undefined; now: number }): TranscriptRow | null {
|
|
92
|
+
const { rows, error, lastAssistantMessage, now } = input;
|
|
91
93
|
for (let i = rows.length - 1; i >= 0; i--) {
|
|
92
94
|
const row = rows[i]!;
|
|
93
|
-
if (row.isApiErrorMessage !== true || row.error !==
|
|
95
|
+
if (row.isApiErrorMessage !== true || row.error !== error) continue;
|
|
94
96
|
const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
|
|
95
97
|
const byContent = lastAssistantMessage != null && lastAssistantMessage !== "" && transcriptRowText(row) === lastAssistantMessage;
|
|
96
98
|
const byRecency = Number.isFinite(ts) && Math.abs(now - ts) <= ROW_RECENCY_MS;
|
|
@@ -103,7 +105,8 @@ export type EnforcedClass =
|
|
|
103
105
|
| { kind: "session"; resetsAt: number | null }
|
|
104
106
|
| { kind: "weekly"; resetsAt: number | null }
|
|
105
107
|
| { kind: "model"; family: string; resetsAt: number | null }
|
|
106
|
-
| { kind: "credits"; family: string; resetsAt: null }
|
|
108
|
+
| { kind: "credits"; family: string; resetsAt: null }
|
|
109
|
+
| { kind: "org"; resetsAt: null };
|
|
107
110
|
|
|
108
111
|
const ErrorBodySchema = z.looseObject({
|
|
109
112
|
error: z.looseObject({ type: z.string().optional(), details: z.looseObject({ error_code: z.string().optional() }).optional() }).optional(),
|
|
@@ -120,6 +123,7 @@ export function parseErrorBody(errorDetails: string | undefined): z.infer<typeof
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
export function classifyEnforcedLimit(row: TranscriptRow, switchModels: string[]): EnforcedClass | null {
|
|
126
|
+
if (row.error === "oauth_org_not_allowed") return { kind: "org", resetsAt: null };
|
|
123
127
|
if (row.apiError === "model_requires_usage_credits") return { kind: "credits", family: CREDITS_FAMILY, resetsAt: null };
|
|
124
128
|
const q = row.quotaLimits;
|
|
125
129
|
if (!q) return null;
|