oc-auth-switcher 0.7.1 → 0.7.2

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 (3) hide show
  1. package/dist/cli.js +35 -18
  2. package/dist/index.js +26 -14
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -21,6 +21,14 @@ var METRIC_MODEL_FAMILY = {
21
21
  weekly7dSonnet: "sonnet",
22
22
  weekly7dFable: "fable"
23
23
  };
24
+ var UNIFIED_RATE_LIMIT_PREFIX = "anthropic-ratelimit-unified-";
25
+ var QUOTA_WINDOW_SCOPE_PATTERN = /^\d+[hmd](?:_[a-z0-9.\-]+)?$/i;
26
+ function quotaWindowScope(prefix) {
27
+ if (!prefix)
28
+ return;
29
+ const scope = prefix.toLowerCase().startsWith(UNIFIED_RATE_LIMIT_PREFIX) ? prefix.slice(UNIFIED_RATE_LIMIT_PREFIX.length) : prefix;
30
+ return QUOTA_WINDOW_SCOPE_PATTERN.test(scope) ? scope : undefined;
31
+ }
24
32
 
25
33
  // node_modules/@ex-machina/opencode-anthropic-auth/dist/constants.js
26
34
  var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
@@ -217,17 +225,20 @@ function normalizeState(raw) {
217
225
  weekly7dSonnet: threshold.weekly7dSonnet ?? DEFAULT_THRESHOLD,
218
226
  weekly7dFable: threshold.weekly7dFable ?? DEFAULT_THRESHOLD
219
227
  } : threshold ?? defaults.config.threshold;
220
- const usage = Object.fromEntries(Object.entries(raw.usage ?? {}).map(([name, accountUsage]) => [
221
- name,
222
- {
223
- session5h: { ...EMPTY_METRIC, ...accountUsage?.session5h },
224
- weekly7d: { ...EMPTY_METRIC, ...accountUsage?.weekly7d },
225
- weekly7dSonnet: { ...EMPTY_METRIC, ...accountUsage?.weekly7dSonnet },
226
- weekly7dFable: { ...EMPTY_METRIC, ...accountUsage?.weekly7dFable },
227
- rejected: { ...EMPTY_METRIC, ...accountUsage?.rejected },
228
- timestamp: accountUsage?.timestamp
229
- }
230
- ]));
228
+ const usage = Object.fromEntries(Object.entries(raw.usage ?? {}).map(([name, accountUsage]) => {
229
+ const rejected = { ...EMPTY_METRIC, ...accountUsage?.rejected };
230
+ return [
231
+ name,
232
+ {
233
+ session5h: { ...EMPTY_METRIC, ...accountUsage?.session5h },
234
+ weekly7d: { ...EMPTY_METRIC, ...accountUsage?.weekly7d },
235
+ weekly7dSonnet: { ...EMPTY_METRIC, ...accountUsage?.weekly7dSonnet },
236
+ weekly7dFable: { ...EMPTY_METRIC, ...accountUsage?.weekly7dFable },
237
+ rejected: rejected.status?.toLowerCase() === "rejected" && !quotaWindowScope(rejected.prefix) ? { ...EMPTY_METRIC } : rejected,
238
+ timestamp: accountUsage?.timestamp
239
+ }
240
+ ];
241
+ }));
231
242
  return {
232
243
  currentAccount: raw.currentAccount ?? defaults.currentAccount,
233
244
  requestCount: raw.requestCount ?? defaults.requestCount,
@@ -238,8 +249,8 @@ function normalizeState(raw) {
238
249
  authFailures: raw.authFailures ?? defaults.authFailures
239
250
  };
240
251
  }
241
- function loadState() {
242
- return normalizeState(safeReadJSON(STATE_FILE, {}));
252
+ function loadState(stateFile = STATE_FILE) {
253
+ return normalizeState(safeReadJSON(stateFile, {}));
243
254
  }
244
255
  function saveState(state) {
245
256
  return safeWriteJSON(STATE_FILE, state);
@@ -461,6 +472,10 @@ function formatRelativeDuration(targetMs, nowMs = Date.now()) {
461
472
  parts.push(`${Math.max(1, Math.round(remaining))}s`);
462
473
  return difference >= 0 ? `in ${parts.join(" ")}` : `${parts.join(" ")} ago`;
463
474
  }
475
+ function formatResetTime(reset, nowMs = Date.now()) {
476
+ const resetMs = reset * 1000;
477
+ return `${new Date(resetMs).toLocaleString()} (${formatRelativeDuration(resetMs, nowMs)})`;
478
+ }
464
479
  function displayRejectionPrefix(prefix) {
465
480
  return prefix?.replace(/^anthropic-ratelimit-unified-/i, "");
466
481
  }
@@ -865,8 +880,9 @@ ${BOLD}${CYAN}=== Auth Switcher Usage Dashboard ===${RESET}
865
880
  }
866
881
  for (const account of data.accounts) {
867
882
  const usage = state.usage[account.name];
883
+ const now = Date.now();
868
884
  const isActive = account.name === state.currentAccount;
869
- const isCooling = !!state.authFailures[account.name] && state.authFailures[account.name] > Date.now();
885
+ const isCooling = !!state.authFailures[account.name] && state.authFailures[account.name] > now;
870
886
  const tag = isActive ? `${GREEN} [ACTIVE]${RESET}` : isCooling ? `${RED} [COOLDOWN]${RESET}` : "";
871
887
  console.log(` ${BOLD}${account.name}${RESET}${tag}`);
872
888
  if (usage) {
@@ -874,8 +890,10 @@ ${BOLD}${CYAN}=== Auth Switcher Usage Dashboard ===${RESET}
874
890
  console.log(` 7d weekly: ${progressBar(usage.weekly7d.utilization, thresholds.weekly7d)}`);
875
891
  console.log(` 7d sonnet: ${progressBar(usage.weekly7dSonnet.utilization, thresholds.weekly7dSonnet)}`);
876
892
  console.log(` 7d fable: ${progressBar(usage.weekly7dFable.utilization, thresholds.weekly7dFable)}`);
877
- if (usage.rejected.status === "rejected") {
878
- console.log(` ${RED}Rate limit status: REJECTED${RESET}`);
893
+ const rejection = deriveAccountHealth(account, usage, thresholds, state.authFailures[account.name], now).reasons.find((reason) => reason.kind === "rejection");
894
+ if (rejection) {
895
+ const reset = rejection.reset ? ` \u2014 resets ${formatResetTime(rejection.reset, now)}` : "";
896
+ console.log(` ${RED}Rate limit status: ${rejection.message}${reset}${RESET}`);
879
897
  }
880
898
  if (usage.timestamp) {
881
899
  console.log(` ${DIM}Last updated: ${usage.timestamp}${RESET}`);
@@ -1067,8 +1085,7 @@ ${BOLD}${CYAN}=== Auth Switcher Status ===${RESET}
1067
1085
  console.log(` ${RED}Reason: ${reason.message}${RESET}`);
1068
1086
  }
1069
1087
  if (health.earliestReset) {
1070
- const resetMs = health.earliestReset * 1000;
1071
- console.log(` Reset: ${new Date(resetMs).toLocaleString()} (${formatRelativeDuration(resetMs, now)})`);
1088
+ console.log(` Reset: ${formatResetTime(health.earliestReset, now)}`);
1072
1089
  }
1073
1090
  const tokenColor = health.reasons.some((reason) => reason.kind === "token") ? RED : DIM;
1074
1091
  console.log(` ${tokenColor}Token: ${health.tokenExpiry}${RESET}`);
package/dist/index.js CHANGED
@@ -423,6 +423,14 @@ var METRIC_MODEL_FAMILY = {
423
423
  weekly7dSonnet: "sonnet",
424
424
  weekly7dFable: "fable"
425
425
  };
426
+ var UNIFIED_RATE_LIMIT_PREFIX = "anthropic-ratelimit-unified-";
427
+ var QUOTA_WINDOW_SCOPE_PATTERN = /^\d+[hmd](?:_[a-z0-9.\-]+)?$/i;
428
+ function quotaWindowScope(prefix) {
429
+ if (!prefix)
430
+ return;
431
+ const scope = prefix.toLowerCase().startsWith(UNIFIED_RATE_LIMIT_PREFIX) ? prefix.slice(UNIFIED_RATE_LIMIT_PREFIX.length) : prefix;
432
+ return QUOTA_WINDOW_SCOPE_PATTERN.test(scope) ? scope : undefined;
433
+ }
426
434
 
427
435
  // src/accounts.ts
428
436
  function normalizeAccount(raw) {
@@ -585,17 +593,20 @@ function normalizeState(raw) {
585
593
  weekly7dSonnet: threshold.weekly7dSonnet ?? DEFAULT_THRESHOLD,
586
594
  weekly7dFable: threshold.weekly7dFable ?? DEFAULT_THRESHOLD
587
595
  } : threshold ?? defaults.config.threshold;
588
- const usage = Object.fromEntries(Object.entries(raw.usage ?? {}).map(([name, accountUsage]) => [
589
- name,
590
- {
591
- session5h: { ...EMPTY_METRIC, ...accountUsage?.session5h },
592
- weekly7d: { ...EMPTY_METRIC, ...accountUsage?.weekly7d },
593
- weekly7dSonnet: { ...EMPTY_METRIC, ...accountUsage?.weekly7dSonnet },
594
- weekly7dFable: { ...EMPTY_METRIC, ...accountUsage?.weekly7dFable },
595
- rejected: { ...EMPTY_METRIC, ...accountUsage?.rejected },
596
- timestamp: accountUsage?.timestamp
597
- }
598
- ]));
596
+ const usage = Object.fromEntries(Object.entries(raw.usage ?? {}).map(([name, accountUsage]) => {
597
+ const rejected = { ...EMPTY_METRIC, ...accountUsage?.rejected };
598
+ return [
599
+ name,
600
+ {
601
+ session5h: { ...EMPTY_METRIC, ...accountUsage?.session5h },
602
+ weekly7d: { ...EMPTY_METRIC, ...accountUsage?.weekly7d },
603
+ weekly7dSonnet: { ...EMPTY_METRIC, ...accountUsage?.weekly7dSonnet },
604
+ weekly7dFable: { ...EMPTY_METRIC, ...accountUsage?.weekly7dFable },
605
+ rejected: rejected.status?.toLowerCase() === "rejected" && !quotaWindowScope(rejected.prefix) ? { ...EMPTY_METRIC } : rejected,
606
+ timestamp: accountUsage?.timestamp
607
+ }
608
+ ];
609
+ }));
599
610
  return {
600
611
  currentAccount: raw.currentAccount ?? defaults.currentAccount,
601
612
  requestCount: raw.requestCount ?? defaults.requestCount,
@@ -606,8 +617,8 @@ function normalizeState(raw) {
606
617
  authFailures: raw.authFailures ?? defaults.authFailures
607
618
  };
608
619
  }
609
- function loadState() {
610
- return normalizeState(safeReadJSON(STATE_FILE, {}));
620
+ function loadState(stateFile = STATE_FILE) {
621
+ return normalizeState(safeReadJSON(stateFile, {}));
611
622
  }
612
623
  function saveState(state) {
613
624
  return safeWriteJSON(STATE_FILE, state);
@@ -727,7 +738,8 @@ function updateUsageFromHeaders(state, accountName, headers) {
727
738
  }
728
739
  for (const [headerName, headerValue] of headers.entries()) {
729
740
  const normalizedName = headerName.toLowerCase();
730
- if (!/^anthropic-ratelimit-unified-(?:.*-)?status$/.test(normalizedName) || headerValue.toLowerCase() !== "rejected") {
741
+ const statusMatch = normalizedName.match(/^anthropic-ratelimit-unified-(.+)-status$/);
742
+ if (!statusMatch || !quotaWindowScope(statusMatch[1]) || headerValue.toLowerCase() !== "rejected") {
731
743
  continue;
732
744
  }
733
745
  const prefix = normalizedName.slice(0, -"-status".length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-auth-switcher",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "OpenCode auth plugin for multi-account Anthropic Claude Max rotation with automatic failover.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",