codex-multi-auth 2.12.0 → 2.14.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.
Files changed (45) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +10 -1
  3. package/dist/lib/accounts.d.ts +75 -0
  4. package/dist/lib/accounts.d.ts.map +1 -1
  5. package/dist/lib/accounts.js +213 -42
  6. package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
  7. package/dist/lib/codex-manager/account-manager-commands.js +1 -0
  8. package/dist/lib/codex-manager/commands/limits.d.ts +21 -0
  9. package/dist/lib/codex-manager/commands/limits.d.ts.map +1 -0
  10. package/dist/lib/codex-manager/commands/limits.js +158 -0
  11. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  12. package/dist/lib/codex-manager/help.js +1 -0
  13. package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
  14. package/dist/lib/codex-manager/persist-selected-account.js +14 -0
  15. package/dist/lib/codex-manager.d.ts.map +1 -1
  16. package/dist/lib/codex-manager.js +12 -0
  17. package/dist/lib/forecast.d.ts.map +1 -1
  18. package/dist/lib/forecast.js +1 -14
  19. package/dist/lib/preemptive-quota-scheduler.d.ts +12 -0
  20. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
  21. package/dist/lib/preemptive-quota-scheduler.js +21 -0
  22. package/dist/lib/redaction.d.ts +17 -0
  23. package/dist/lib/redaction.d.ts.map +1 -0
  24. package/dist/lib/redaction.js +28 -0
  25. package/dist/lib/request/error-classification.d.ts +13 -6
  26. package/dist/lib/request/error-classification.d.ts.map +1 -1
  27. package/dist/lib/request/error-classification.js +45 -0
  28. package/dist/lib/request/fetch-helpers.d.ts +1 -1
  29. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  30. package/dist/lib/request/fetch-helpers.js +1 -1
  31. package/dist/lib/runtime/rotation-proxy-state.d.ts +6 -0
  32. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  33. package/dist/lib/runtime/rotation-server-types.d.ts +7 -0
  34. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  35. package/dist/lib/runtime/rotation-storage-meta.d.ts.map +1 -1
  36. package/dist/lib/runtime/rotation-storage-meta.js +4 -1
  37. package/dist/lib/runtime-rotation-proxy.d.ts +23 -0
  38. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  39. package/dist/lib/runtime-rotation-proxy.js +237 -13
  40. package/dist/lib/storage.d.ts +20 -2
  41. package/dist/lib/storage.d.ts.map +1 -1
  42. package/dist/lib/storage.js +69 -25
  43. package/package.json +1 -1
  44. package/scripts/codex-routing.js +1 -0
  45. package/scripts/codex.js +157 -2
@@ -0,0 +1,158 @@
1
+ import { formatAccountLabel } from "../../accounts.js";
2
+ import { MODEL_FAMILIES } from "../../prompts/codex.js";
3
+ import { findQuotaCacheEntryForAccount } from "../../quota-readiness.js";
4
+ import { redactEmails } from "../../redaction.js";
5
+ const LIMITS_SCHEMA_VERSION = 1;
6
+ const LIMITS_REFRESH_MAX_AGE_MS = 5 * 60_000;
7
+ const LIMITS_USAGE = "Usage: codex-multi-auth limits --json [--refresh]";
8
+ /** Parse the intentionally small, JSON-only limits command surface. */
9
+ function parseLimitsOptions(args) {
10
+ const options = { json: false, refresh: false, help: false };
11
+ for (const arg of args) {
12
+ if (arg === "--json" || arg === "-j") {
13
+ options.json = true;
14
+ continue;
15
+ }
16
+ if (arg === "--refresh") {
17
+ options.refresh = true;
18
+ continue;
19
+ }
20
+ if (arg === "--help" || arg === "-h") {
21
+ options.help = true;
22
+ continue;
23
+ }
24
+ return { ok: false, message: `Unknown limits option: ${arg}` };
25
+ }
26
+ if (!options.json && !options.help) {
27
+ return { ok: false, message: LIMITS_USAGE };
28
+ }
29
+ return { ok: true, options };
30
+ }
31
+ /** Convert a cached quota window to the explicit-null public JSON contract. */
32
+ function publicWindow(window) {
33
+ return {
34
+ usedPercent: window.usedPercent ?? null,
35
+ windowMinutes: window.windowMinutes ?? null,
36
+ resetAtMs: window.resetAtMs ?? null,
37
+ };
38
+ }
39
+ /** Remove internal probe metadata and stabilize optional quota fields. */
40
+ function publicQuotaEntry(entry) {
41
+ return {
42
+ updatedAt: entry.updatedAt,
43
+ status: entry.status,
44
+ planType: entry.planType ?? null,
45
+ primary: publicWindow(entry.primary),
46
+ secondary: publicWindow(entry.secondary),
47
+ };
48
+ }
49
+ /**
50
+ * Emit configured accounts joined to safe cached quota records.
51
+ *
52
+ * Cached mode performs no provider requests. Refresh mode delegates to the
53
+ * existing sequential, age-gated refresh path before serializing the snapshot.
54
+ */
55
+ export async function runLimitsCommand(args, deps) {
56
+ const parsed = parseLimitsOptions(args);
57
+ const logInfo = deps.logInfo ?? console.log;
58
+ const logError = deps.logError ?? console.error;
59
+ if (!parsed.ok) {
60
+ logError(parsed.message);
61
+ return 1;
62
+ }
63
+ if (parsed.options.help) {
64
+ logInfo(LIMITS_USAGE);
65
+ return 0;
66
+ }
67
+ deps.setStoragePath(null);
68
+ const storage = await deps.loadAccounts();
69
+ if (!storage || storage.accounts.length === 0) {
70
+ const generatedAt = deps.getNow?.() ?? Date.now();
71
+ logInfo(JSON.stringify({
72
+ schemaVersion: LIMITS_SCHEMA_VERSION,
73
+ generatedAt,
74
+ mode: parsed.options.refresh ? "refresh" : "cached",
75
+ // Keep the key set identical for an empty pool so a consumer
76
+ // never has to branch on its presence. There is no account to
77
+ // route to, so `routedIndex` is null rather than a positional 0
78
+ // that addresses nothing.
79
+ selection: {
80
+ pinnedIndex: null,
81
+ routedIndex: null,
82
+ activeIndexByFamily: {},
83
+ },
84
+ accounts: [],
85
+ }, null, 2));
86
+ return 0;
87
+ }
88
+ let cache = await deps.loadQuotaCache();
89
+ if (parsed.options.refresh) {
90
+ cache = await deps.refreshQuotaCache(storage, cache, LIMITS_REFRESH_MAX_AGE_MS);
91
+ }
92
+ const generatedAt = deps.getNow?.() ?? Date.now();
93
+ const selection = resolveSelection(storage, deps.resolveActiveIndex);
94
+ const accounts = storage.accounts.map((account, index) => {
95
+ const quota = findQuotaCacheEntryForAccount(cache, account, storage.accounts);
96
+ return {
97
+ index,
98
+ // Labels are built from the account email. Mask it, exactly as
99
+ // `forecast --json` already does, so a snapshot written to a log
100
+ // shipper or a ticket does not carry the address.
101
+ label: redactEmails(formatAccountLabel(account, index)),
102
+ enabled: account.enabled !== false,
103
+ current: index === selection.routedIndex,
104
+ quota: quota ? publicQuotaEntry(quota) : null,
105
+ };
106
+ });
107
+ logInfo(JSON.stringify({
108
+ schemaVersion: LIMITS_SCHEMA_VERSION,
109
+ generatedAt,
110
+ mode: parsed.options.refresh ? "refresh" : "cached",
111
+ selection,
112
+ accounts,
113
+ }, null, 2));
114
+ return 0;
115
+ }
116
+ /**
117
+ * The CONFIGURED routing target, and the state that decides it.
118
+ *
119
+ * `current` cannot be `activeIndexByFamily.codex` alone. The runtime proxy
120
+ * routes on `pinnedAccountIndex` whenever a `switch` pin is set, and flows that
121
+ * move the active index without touching the pin (rotation saves, `unpin`, an
122
+ * ephemeral `--account`) would otherwise leave `current: true` on a row that is
123
+ * not the configured target. Both inputs are emitted so a consumer can tell
124
+ * which one applied, and the per-family map is emitted because a pool can hold
125
+ * a different active index per family.
126
+ *
127
+ * This is deliberately NOT a prediction of which account the next request
128
+ * lands on. The proxy skips an account that is disabled, inside a rate-limit
129
+ * window, cooling down, or behind an open circuit breaker, and it applies
130
+ * session affinity and an ephemeral `--account` override that never touch
131
+ * storage. Reproducing that here would mean a third copy of the selector
132
+ * (`why-selected` and `forecast` already own live selection), and a snapshot
133
+ * read from a cache cannot be authoritative about it in any case. Consumers
134
+ * that need liveness have `enabled` on every row and `why-selected --json`.
135
+ *
136
+ * `routedIndex` is null only for an empty pool, where no row is `current`.
137
+ */
138
+ function resolveSelection(storage, resolveActiveIndex) {
139
+ if (storage.accounts.length === 0) {
140
+ return { pinnedIndex: null, routedIndex: null, activeIndexByFamily: {} };
141
+ }
142
+ const activeIndexByFamily = {};
143
+ for (const family of MODEL_FAMILIES) {
144
+ activeIndexByFamily[family] = resolveActiveIndex(storage, family);
145
+ }
146
+ const rawPin = storage.pinnedAccountIndex;
147
+ const pinnedIndex = typeof rawPin === "number" &&
148
+ Number.isInteger(rawPin) &&
149
+ rawPin >= 0 &&
150
+ rawPin < storage.accounts.length
151
+ ? rawPin
152
+ : null;
153
+ return {
154
+ pinnedIndex,
155
+ routedIndex: pinnedIndex ?? activeIndexByFamily.codex ?? 0,
156
+ activeIndexByFamily,
157
+ };
158
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAsDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAuGtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAuDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAuGtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
@@ -8,6 +8,7 @@ export function printUsage() {
8
8
  " codex-multi-auth login [--device-auth|--manual|--no-browser] [--org <org_id>] [--preserve-selection] [--account <index|email|account_id>]",
9
9
  " codex-multi-auth status [--json] (list is the same command)",
10
10
  " codex-multi-auth check (always live-probes)",
11
+ " codex-multi-auth limits --json [--refresh] (structured quota windows; refresh is age-gated)",
11
12
  "",
12
13
  "Daily use:",
13
14
  " codex-multi-auth list [--json]",
@@ -1 +1 @@
1
- {"version":3,"file":"persist-selected-account.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/persist-selected-account.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EACN,KAAK,gBAAgB,EAIrB,MAAM,eAAe,CAAC;AAQvB;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CAAC,EACnD,OAAO,EACP,WAAW,EACX,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,2BAAmC,EACnC,MAAc,EACd,QAAgB,EAChB,sBAA8B,GAC9B,EAAE;IACF,OAAO,EAAE,gBAAgB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,qBAAqB,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAqGrD"}
1
+ {"version":3,"file":"persist-selected-account.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/persist-selected-account.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EACN,KAAK,gBAAgB,EAIrB,MAAM,eAAe,CAAC;AAQvB;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CAAC,EACnD,OAAO,EACP,WAAW,EACX,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,2BAAmC,EACnC,MAAc,EACd,QAAgB,EAChB,sBAA8B,GAC9B,EAAE;IACF,OAAO,EAAE,gBAAgB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,qBAAqB,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAmHrD"}
@@ -77,6 +77,20 @@ export async function persistAndSyncSelectedAccount({ storage, targetIndex, pars
77
77
  account.lastSwitchReason = switchReason;
78
78
  if (setPin) {
79
79
  storage.pinnedAccountIndex = targetIndex;
80
+ // A manual switch requests a fresh upstream attempt, including when
81
+ // reselecting the same account after an out-of-band quota reset. Real 429
82
+ // responses will populate these markers again.
83
+ //
84
+ // `setPin` is the right trigger because it is set only by `switch` (which
85
+ // the login dashboard's account picker also routes through), i.e. exactly
86
+ // the paths where a person picked this account. `best` passes `clearPin`
87
+ // and a backup restore passes neither, and neither of those is a request
88
+ // to revalidate a rate-limited account.
89
+ //
90
+ // Delete rather than assign `{}`: `AccountManager.buildStorageSnapshot`
91
+ // omits the field when the map is empty, so assigning would persist a
92
+ // second on-disk shape for the same state.
93
+ delete account.rateLimitResetTimes;
80
94
  }
81
95
  else if (clearPin) {
82
96
  delete storage.pinnedAccountIndex;
@@ -1 +1 @@
1
- {"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"AA2DA,OAAO,EAIN,yBAAyB,EACzB,MAAM,eAAe,CAAC;AA4BvB,OAAO,EAEN,mBAAmB,EAMnB,sBAAsB,EAGtB,MAAM,qCAAqC,CAAC;AA+B7C,OAAO,EAEN,KAAK,gBAAgB,EAWrB,MAAM,cAAc,CAAC;AAKtB,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,CAAC;AA4IvD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAuGrE;AACD,mFAAmF;AACnF,wBAAgB,wBAAwB,IAAI,CAC3C,OAAO,EAAE,gBAAgB,KACrB,OAAO,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAwEzD;AAmPD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C7E"}
1
+ {"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"AA4DA,OAAO,EAIN,yBAAyB,EACzB,MAAM,eAAe,CAAC;AA6BvB,OAAO,EAEN,mBAAmB,EAMnB,sBAAsB,EAGtB,MAAM,qCAAqC,CAAC;AA+B7C,OAAO,EAEN,KAAK,gBAAgB,EAWrB,MAAM,cAAc,CAAC;AAKtB,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,CAAC;AA4IvD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAuGrE;AACD,mFAAmF;AACnF,wBAAgB,wBAAwB,IAAI,CAC3C,OAAO,EAAE,gBAAgB,KACrB,OAAO,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAwEzD;AA8PD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C7E"}
@@ -14,6 +14,7 @@ import { runBudgetCommand } from "./codex-manager/commands/budget.js";
14
14
  import { runBridgeCommand } from "./codex-manager/commands/bridge.js";
15
15
  import { runCheckCommand } from "./codex-manager/commands/check.js";
16
16
  import { runIntegrationsCommand } from "./codex-manager/commands/integrations.js";
17
+ import { runLimitsCommand } from "./codex-manager/commands/limits.js";
17
18
  import { runModelsCommand } from "./codex-manager/commands/models.js";
18
19
  import { runMonitorCommand } from "./codex-manager/commands/monitor.js";
19
20
  import { runConfigExplainCommand } from "./codex-manager/commands/config-explain.js";
@@ -37,6 +38,7 @@ import { runHistoryCommand } from "./codex-manager/commands/history.js";
37
38
  import { runUnpinCommand } from "./codex-manager/commands/unpin.js";
38
39
  import { runWorkspaceCommand } from "./codex-manager/commands/workspace.js";
39
40
  import { runUsageCommand } from "./codex-manager/commands/usage.js";
41
+ import { refreshQuotaCacheForMenu } from "./codex-manager/login-menu-data.js";
40
42
  import { printUsage } from "./codex-manager/help.js";
41
43
  import { availabilityTone, formatBackupSavedAt, formatCompactQuotaSnapshot, formatRateLimitEntry, formatResultSummary, normalizeFailureDetail, riskTone, styleAccountDetailText, stylePromptText, styleQuotaSummary, } from "./codex-manager/formatters/index.js";
42
44
  import { applyUiThemeFromDashboardSettings } from "./codex-manager/settings-hub.js";
@@ -376,6 +378,16 @@ const CLI_COMMAND_HANDLERS = new Map([
376
378
  ["login", (rest) => runAuthLogin(rest, { runForecast, createRepairCommandDeps })],
377
379
  ["list", runListOrStatusCommand],
378
380
  ["status", runListOrStatusCommand],
381
+ [
382
+ "limits",
383
+ (rest) => runLimitsCommand(rest, {
384
+ setStoragePath,
385
+ loadAccounts,
386
+ loadQuotaCache,
387
+ refreshQuotaCache: refreshQuotaCacheForMenu,
388
+ resolveActiveIndex,
389
+ }),
390
+ ],
379
391
  [
380
392
  "switch",
381
393
  (rest) => runSwitchCommand(rest, {
@@ -1 +1 @@
1
- {"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../lib/forecast.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAKlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,KAAK,oBAAoB,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAChE,KAAK,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,cAAc,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,6BAA6B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAKlB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CACf;AAED,UAAU,0BAA0B;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,0BAA0B,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAuFD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAUnE;AAWD,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,oBAAoB,GACzB,qBAAqB,CA8OvB;AAED,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,oBAAoB,EAAE,GAC5B,qBAAqB,EAAE,CAEzB;AAkCD,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,qBAAqB,EAAE,GAC9B,sBAAsB,CAuDxB;AAED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,qBAAqB,EAAE,GAC9B,eAAe,CAWjB;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,qBAAqB,EAAE,EAChC,cAAc,EAAE,sBAAsB,GACpC,mBAAmB,CAgBrB"}
1
+ {"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../lib/forecast.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAKlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,KAAK,oBAAoB,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAChE,KAAK,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,cAAc,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,6BAA6B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAKlB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CACf;AAED,UAAU,0BAA0B;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,0BAA0B,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAqED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAUnE;AAWD,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,oBAAoB,GACzB,qBAAqB,CA8OvB;AAED,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,oBAAoB,EAAE,GAC5B,qBAAqB,EAAE,CAEzB;AAkCD,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,qBAAqB,EAAE,GAC9B,sBAAsB,CAuDxB;AAED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,qBAAqB,EAAE,GAC9B,eAAe,CAWjB;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,qBAAqB,EAAE,EAChC,cAAc,EAAE,sBAAsB,GACpC,mBAAmB,CAgBrB"}
@@ -1,25 +1,12 @@
1
1
  import { AUTH_INVALIDATION_MARKER, formatAccountLabel, formatWaitTime, } from "./accounts.js";
2
2
  import { findQuotaCacheEntryForAccount, isQuotaCacheEntryExhausted, quotaUsedPercentIsExhausted, } from "./quota-readiness.js";
3
+ import { redactEmails } from "./redaction.js";
3
4
  import { getRateLimitResetTimeForFamily, getRateLimitResetTimeForModel, } from "./runtime/account-status.js";
4
5
  function clampRisk(score) {
5
6
  if (!Number.isFinite(score))
6
7
  return 100;
7
8
  return Math.max(0, Math.min(100, Math.round(score)));
8
9
  }
9
- function maskEmail(value) {
10
- const atIndex = value.indexOf("@");
11
- if (atIndex <= 0)
12
- return "***@***";
13
- const local = value.slice(0, atIndex);
14
- const domain = value.slice(atIndex + 1);
15
- const domainParts = domain.split(".");
16
- const tld = domainParts.pop() ?? "";
17
- const prefix = local.slice(0, Math.min(2, local.length));
18
- return `${prefix}***@***.${tld || "***"}`;
19
- }
20
- function redactEmails(value) {
21
- return value.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, (match) => maskEmail(match));
22
- }
23
10
  function redactSensitiveReason(value) {
24
11
  return redactEmails(value
25
12
  .replace(/Bearer\s+\S+/gi, "Bearer ***")
@@ -48,6 +48,18 @@ export declare class PreemptiveQuotaScheduler {
48
48
  update(key: string, snapshot: QuotaSchedulerSnapshot): void;
49
49
  markRateLimited(key: string, retryAfterMs: number, now?: number): void;
50
50
  getDeferral(key: string, now?: number): QuotaDeferralDecision;
51
+ /**
52
+ * Forget cached quota observations for one account across its model keys.
53
+ *
54
+ * @param prefix Account key INCLUDING its trailing separator, as produced by
55
+ * `buildQuotaScheduleAccountPrefix`. Without that separator the prefix also
56
+ * matches neighbouring identities (`account:email:foo` matches
57
+ * `account:email:foobar:codex`). An empty prefix is a no-op rather than a
58
+ * silent clear-all; use `clearAll()` when that is the intent.
59
+ */
60
+ clearByPrefix(prefix: string): void;
61
+ /** Forget every cached quota observation. */
62
+ clearAll(): void;
51
63
  prune(now?: number): number;
52
64
  }
53
65
  //# sourceMappingURL=preemptive-quota-scheduler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preemptive-quota-scheduler.d.ts","sourceRoot":"","sources":["../../lib/preemptive-quota-scheduler.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,oBAAoB,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,GAAG,uBAAuB,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,sBAAsB,GAAG,IAAI,CAqB5H;AAED,qBAAa,wBAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6C;IACvE,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,gCAAgC,CAAS;IACjD,OAAO,CAAC,kCAAkC,CAAS;IACnD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,GAAE,qBAA0B;IAQ/C,SAAS,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI;IA2CpD,OAAO,CAAC,UAAU;IAMlB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAM3D,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,IAAI;IA8B1E,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,qBAAqB;IAkEjE,KAAK,CAAC,GAAG,SAAa,GAAG,MAAM;CAW/B"}
1
+ {"version":3,"file":"preemptive-quota-scheduler.d.ts","sourceRoot":"","sources":["../../lib/preemptive-quota-scheduler.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,oBAAoB,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,GAAG,uBAAuB,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,sBAAsB,GAAG,IAAI,CAqB5H;AAED,qBAAa,wBAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6C;IACvE,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,gCAAgC,CAAS;IACjD,OAAO,CAAC,kCAAkC,CAAS;IACnD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,GAAE,qBAA0B;IAQ/C,SAAS,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI;IA2CpD,OAAO,CAAC,UAAU;IAMlB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAM3D,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,IAAI;IA8B1E,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,qBAAqB;IAkEjE;;;;;;;;OAQG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAOnC,6CAA6C;IAC7C,QAAQ,IAAI,IAAI;IAIhB,KAAK,CAAC,GAAG,SAAa,GAAG,MAAM;CAW/B"}
@@ -264,6 +264,27 @@ export class PreemptiveQuotaScheduler {
264
264
  }
265
265
  return { defer: false, waitMs: 0 };
266
266
  }
267
+ /**
268
+ * Forget cached quota observations for one account across its model keys.
269
+ *
270
+ * @param prefix Account key INCLUDING its trailing separator, as produced by
271
+ * `buildQuotaScheduleAccountPrefix`. Without that separator the prefix also
272
+ * matches neighbouring identities (`account:email:foo` matches
273
+ * `account:email:foobar:codex`). An empty prefix is a no-op rather than a
274
+ * silent clear-all; use `clearAll()` when that is the intent.
275
+ */
276
+ clearByPrefix(prefix) {
277
+ if (!prefix)
278
+ return;
279
+ for (const key of this.snapshots.keys()) {
280
+ if (key.startsWith(prefix))
281
+ this.snapshots.delete(key);
282
+ }
283
+ }
284
+ /** Forget every cached quota observation. */
285
+ clearAll() {
286
+ this.snapshots.clear();
287
+ }
267
288
  prune(now = Date.now()) {
268
289
  let removed = 0;
269
290
  for (const [key, snapshot] of this.snapshots.entries()) {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Email redaction for machine-readable command output.
3
+ *
4
+ * Account labels are built from the operator-visible email, so any command that
5
+ * emits them as JSON hands that address to whatever consumes the output: log
6
+ * shippers, dashboards, ticket attachments. `forecast --json` has masked them
7
+ * since it was written; this module is that same masking, shared so other JSON
8
+ * surfaces do not each re-derive it.
9
+ */
10
+ /**
11
+ * Mask one address, keeping just enough to tell two accounts apart: the first
12
+ * two characters of the local part and the TLD.
13
+ */
14
+ export declare function maskEmail(value: string): string;
15
+ /** Mask every address embedded in a free-text string. */
16
+ export declare function redactEmails(value: string): string;
17
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../lib/redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Email redaction for machine-readable command output.
3
+ *
4
+ * Account labels are built from the operator-visible email, so any command that
5
+ * emits them as JSON hands that address to whatever consumes the output: log
6
+ * shippers, dashboards, ticket attachments. `forecast --json` has masked them
7
+ * since it was written; this module is that same masking, shared so other JSON
8
+ * surfaces do not each re-derive it.
9
+ */
10
+ /**
11
+ * Mask one address, keeping just enough to tell two accounts apart: the first
12
+ * two characters of the local part and the TLD.
13
+ */
14
+ export function maskEmail(value) {
15
+ const atIndex = value.indexOf("@");
16
+ if (atIndex <= 0)
17
+ return "***@***";
18
+ const local = value.slice(0, atIndex);
19
+ const domain = value.slice(atIndex + 1);
20
+ const domainParts = domain.split(".");
21
+ const tld = domainParts.pop() ?? "";
22
+ const prefix = local.slice(0, Math.min(2, local.length));
23
+ return `${prefix}***@***.${tld || "***"}`;
24
+ }
25
+ /** Mask every address embedded in a free-text string. */
26
+ export function redactEmails(value) {
27
+ return value.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, (match) => maskEmail(match));
28
+ }
@@ -35,15 +35,22 @@ export declare function resolveUnsupportedCodexFallbackModel(options: ResolveUns
35
35
  */
36
36
  export declare function shouldFallbackToGpt52OnUnsupportedGpt53(requestedModel: string | undefined, errorBody: unknown): boolean;
37
37
  /**
38
- * Detects whether an error code or response body indicates an entitlement/subscription issue for Codex models.
38
+ * Detects an upstream response saying the selected model is temporarily out of
39
+ * capacity, as opposed to the account being rate limited or the server being
40
+ * broken.
39
41
  *
40
- * Entitlement errors signal that the requested feature is not included in the user's plan and should not be treated as rate limits.
41
- * This function is pure and safe to call concurrently; it performs no filesystem access (including on Windows) and does not read or redact tokens — callers must avoid passing sensitive credentials in `code` or `bodyText`.
42
+ * The distinction matters because the two want opposite handling. A rate limit
43
+ * is per account, so rotating to another account clears it. Capacity is a
44
+ * property of the MODEL, so every account in the pool fails identically and
45
+ * rotating just burns the pool's transient budget in a few seconds, ending the
46
+ * request as a 503. The caller waits and re-sends on the same account instead.
47
+ * See issue #689.
42
48
  *
43
- * @param code - The error code string returned by the service
44
- * @param bodyText - The response body text to inspect for entitlement-related phrases
45
- * @returns `true` if the combined `code` or `bodyText` indicates an entitlement/subscription issue, `false` otherwise
49
+ * @param status - Upstream HTTP status.
50
+ * @param bodyText - Upstream error body, already read.
51
+ * @returns `true` when the request should be waited out rather than rotated.
46
52
  */
53
+ export declare function isModelAtCapacityError(status: number, bodyText: string): boolean;
47
54
  export declare function isEntitlementError(code: string, bodyText: string): boolean;
48
55
  /**
49
56
  * Detects whether an error indicates the workspace/account has been disabled or expired.
@@ -1 +1 @@
1
- {"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoD7E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sCAAsC;IACtD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,+BAA+B,EAAE,OAAO,CAAC;IACzC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAiDD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBzF;AAED,sGAAsG;AACtG,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ3F;AAED,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,OAAO,GAChB,yBAAyB,CAqD3B;AAED,wBAAgB,oCAAoC,CACnD,OAAO,EAAE,sCAAsC,GAC7C,MAAM,GAAG,SAAS,CAkCpB;AAED;;GAEG;AACH,wBAAgB,uCAAuC,CACtD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,OAAO,GAChB,OAAO,CAgBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GACrB,OAAO,CAoDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAmB1E"}
1
+ {"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoD7E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sCAAsC;IACtD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,+BAA+B,EAAE,OAAO,CAAC;IACzC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAiDD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBzF;AAED,sGAAsG;AACtG,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ3F;AAED,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,OAAO,GAChB,yBAAyB,CAqD3B;AAED,wBAAgB,oCAAoC,CACnD,OAAO,EAAE,sCAAsC,GAC7C,MAAM,GAAG,SAAS,CAkCpB;AAED;;GAEG;AACH,wBAAgB,uCAAuC,CACtD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,OAAO,GAChB,OAAO,CAgBT;AAqCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAIT;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GACrB,OAAO,CAoDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAmB1E"}
@@ -242,6 +242,51 @@ export function shouldFallbackToGpt52OnUnsupportedGpt53(requestedModel, errorBod
242
242
  * @param bodyText - The response body text to inspect for entitlement-related phrases
243
243
  * @returns `true` if the combined `code` or `bodyText` indicates an entitlement/subscription issue, `false` otherwise
244
244
  */
245
+ /**
246
+ * Statuses that are never a capacity signal, whatever the body says.
247
+ *
248
+ * Auth, payment, entitlement and not-found responses are terminal for this
249
+ * request and are each handled by their own branch upstream of the capacity
250
+ * check. Retrying them would spin until the deadline for no reason.
251
+ */
252
+ const NON_CAPACITY_STATUSES = new Set([401, 402, 403, 404]);
253
+ /**
254
+ * Phrases and codes that mean the model itself is out of capacity right now.
255
+ *
256
+ * This is matched on the body TEXT rather than on a status code, because the
257
+ * status this arrives with is not documented and has been reported differently
258
+ * by different callers. Matching text the way `isEntitlementError` and
259
+ * `isWorkspaceDisabledError` already do means a wrong guess about the status
260
+ * leaves behavior exactly as it is today, rather than breaking a path.
261
+ *
262
+ * `at capacity` is deliberately broad. In an OpenAI error body that phrase does
263
+ * not occur outside capacity pressure, and a false positive costs a bounded
264
+ * wait and one re-send, not a wrong answer.
265
+ */
266
+ const MODEL_AT_CAPACITY_PATTERN = /\bat capacity\b|\bmodel_capacity_exceeded\b|\bcapacity_exceeded\b|\bcurrently overloaded\b|\boverloaded_error\b|\bslow_down\b/i;
267
+ /**
268
+ * Detects an upstream response saying the selected model is temporarily out of
269
+ * capacity, as opposed to the account being rate limited or the server being
270
+ * broken.
271
+ *
272
+ * The distinction matters because the two want opposite handling. A rate limit
273
+ * is per account, so rotating to another account clears it. Capacity is a
274
+ * property of the MODEL, so every account in the pool fails identically and
275
+ * rotating just burns the pool's transient budget in a few seconds, ending the
276
+ * request as a 503. The caller waits and re-sends on the same account instead.
277
+ * See issue #689.
278
+ *
279
+ * @param status - Upstream HTTP status.
280
+ * @param bodyText - Upstream error body, already read.
281
+ * @returns `true` when the request should be waited out rather than rotated.
282
+ */
283
+ export function isModelAtCapacityError(status, bodyText) {
284
+ if (NON_CAPACITY_STATUSES.has(status))
285
+ return false;
286
+ if (!bodyText)
287
+ return false;
288
+ return MODEL_AT_CAPACITY_PATTERN.test(bodyText);
289
+ }
245
290
  export function isEntitlementError(code, bodyText) {
246
291
  const haystack = `${code} ${bodyText}`.toLowerCase();
247
292
  // "usage_not_included" means the subscription doesn't include this feature
@@ -6,7 +6,7 @@ import { type FastSessionInputTrimPlan } from "./request-transformer.js";
6
6
  import type { UserConfig, RequestBody } from "../types.js";
7
7
  import type { UsageTokenCounts } from "../usage/types.js";
8
8
  export { shouldRefreshToken, refreshAndUpdateToken } from "./token-refresh.js";
9
- export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
9
+ export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isModelAtCapacityError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
10
10
  export type { EntitlementError, UnsupportedCodexModelInfo, ResolveUnsupportedCodexFallbackOptions, } from "./error-classification.js";
11
11
  export { extractRequestUrl, rewriteUrlForCodex, resolveProxyUrlForRequest, closeSharedProxyDispatchers, applyProxyCompatibleInit, } from "./url-rewriting.js";
12
12
  export type { ProxyCompatibleRequestInit } from "./url-rewriting.js";
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import { HTTP_STATUS, ERROR_MESSAGES, LOG_STAGES, MAX_RATE_LIMIT_DELAY_MS, } fro
11
11
  import { CHATGPT_CODEX_UNSUPPORTED_MODEL_CODE, createEntitlementErrorResponse, extractUnsupportedCodexModelFromText, isEntitlementError, isUnsupportedCodexModelForChatGpt, } from "./error-classification.js";
12
12
  import { logDeprecationHeaders } from "./headers.js";
13
13
  export { shouldRefreshToken, refreshAndUpdateToken } from "./token-refresh.js";
14
- export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
14
+ export { DEFAULT_UNSUPPORTED_CODEX_FALLBACK_CHAIN, extractUnsupportedCodexModelFromText, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, shouldFallbackToGpt52OnUnsupportedGpt53, isEntitlementError, isModelAtCapacityError, isWorkspaceDisabledError, createEntitlementErrorResponse, } from "./error-classification.js";
15
15
  export { extractRequestUrl, rewriteUrlForCodex, resolveProxyUrlForRequest, closeSharedProxyDispatchers, applyProxyCompatibleInit, } from "./url-rewriting.js";
16
16
  export { createCodexHeaders } from "./headers.js";
17
17
  // MAX_RATE_LIMIT_DELAY_MS is centralized in constants.ts (shared with the
@@ -21,6 +21,12 @@ export interface RotationProxyStateInit {
21
21
  tokenRefreshSkewMs: number;
22
22
  networkErrorCooldownMs: number;
23
23
  serverErrorCooldownMs: number;
24
+ /**
25
+ * Wall-clock ceiling on how long ONE request may wait out a model-capacity
26
+ * response before giving up. `0` restores the pre-#689 behaviour of rotating
27
+ * the pool and returning the exhaustion 503.
28
+ */
29
+ modelCapacityRetryMs: number;
24
30
  tokenInvalidationCooldownMs: number;
25
31
  minRotationIntervalMs: number;
26
32
  pidOffsetEnabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
1
+ {"version":3,"file":"rotation-proxy-state.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-proxy-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACtC,oBAAoB,EAAE,cAAc,CAAC;IACrC,gBAAgB,EAAE,SAAS,GAAG,QAAQ,CAAC;IACvC,kBAAkB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5C,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2BAA2B,EAAE,MAAM,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8BAA8B,EAAE,MAAM,CAAC;IACvC,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,8BAA8B,EAAE,MAAM,CAAC;IACvC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;IACjE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAID,gBAAgB;AAChB,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,kBAAkB,CAuBpB;AAED,gBAAgB;AAChB,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkDhC"}
@@ -48,6 +48,13 @@ export interface RuntimeRotationProxyOptions {
48
48
  * the value survives the launcher -> detached app-helper process boundary.
49
49
  */
50
50
  forcedAccountIndex?: number | null;
51
+ /**
52
+ * Wall-clock ceiling on how long ONE request may wait out a
53
+ * "selected model is at capacity" response before giving up (issue #689).
54
+ * `0` disables the wait entirely. Falls back to
55
+ * `CODEX_MULTI_AUTH_MODEL_CAPACITY_RETRY_MS`, then to a 10 minute default.
56
+ */
57
+ modelCapacityRetryMs?: number;
51
58
  }
52
59
  export interface RequestContext {
53
60
  body: Buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;IAC5C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;IAC5C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"rotation-storage-meta.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-storage-meta.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAqCnE,MAAM,WAAW,WAAW;IAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AA8BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACtC,WAAW,GAAE,MAAyB,GACpC,WAAW,CAmFb;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC7C,WAAW,GAAE,MAAyB,GACpC,MAAM,GAAG,IAAI,CAEf;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC9C,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,EACjD,sBAAsB,EAAE,MAAM,EAC9B,WAAW,GAAE,MAAyB,GACpC,MAAM,CAOR"}
1
+ {"version":3,"file":"rotation-storage-meta.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-storage-meta.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAqCnE,MAAM,WAAW,WAAW;IAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AA8BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACtC,WAAW,GAAE,MAAyB,GACpC,WAAW,CAsFb;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC7C,WAAW,GAAE,MAAyB,GACpC,MAAM,GAAG,IAAI,CAEf;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC9C,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,EACjD,sBAAsB,EAAE,MAAM,EAC9B,WAAW,GAAE,MAAyB,GACpC,MAAM,CAOR"}
@@ -90,7 +90,10 @@ export function readStorageMetaFromDisk(storagePath = getStoragePath()) {
90
90
  : null;
91
91
  const affinityGeneration = typeof parsed.affinityGeneration === "number" &&
92
92
  Number.isFinite(parsed.affinityGeneration) &&
93
- Number.isInteger(parsed.affinityGeneration) &&
93
+ // Safe, not merely integral: see readAffinityGenerationFromDisk. Past
94
+ // 2^53 the CLI's bump stops advancing, so a proxy that accepted the
95
+ // value would never observe another switch.
96
+ Number.isSafeInteger(parsed.affinityGeneration) &&
94
97
  parsed.affinityGeneration >= 0
95
98
  ? parsed.affinityGeneration
96
99
  : 0;