@timo972/cc-router 0.12.0 → 0.12.1-rc.1

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.
@@ -6,9 +6,15 @@
6
6
  * flow is not here because it runs inquirer.
7
7
  */
8
8
  const REQUEST_TIMEOUT_MS = 3_000;
9
+ // Worst case is every account timing out: OpenAI token refreshes are
10
+ // sequential with a 15s deadline each, usage fetches run two at a time with
11
+ // 10s each. The server single-flights the pass, so a client that does give
12
+ // up and presses again joins the running one rather than stacking another.
13
+ const REFRESH_ALL_TIMEOUT_MS = 120_000;
9
14
  const MAX_PUBLIC_ROWS = 12;
10
15
  export function createAccountsApi(baseUrl, authToken) {
11
- const base = baseUrl.replace(/\/+$/, "") + "/cc-router/accounts";
16
+ const root = baseUrl.replace(/\/+$/, "");
17
+ const base = root + "/cc-router/accounts";
12
18
  const authHeaders = authToken ? { authorization: `Bearer ${authToken}` } : {};
13
19
  async function send(method, path, body) {
14
20
  const res = await fetch(base + path, {
@@ -35,13 +41,53 @@ export function createAccountsApi(baseUrl, authToken) {
35
41
  const payload = await res.json();
36
42
  return Array.isArray(payload.accounts) ? payload.accounts.flatMap(publicAccountSafeView) : [];
37
43
  }
44
+ async function refreshAll() {
45
+ // Usage fetches for every account run behind this call, so it gets its
46
+ // own, longer budget than the account mutations above.
47
+ const res = await fetch(root + "/cc-router/refresh", {
48
+ method: "POST",
49
+ headers: authHeaders,
50
+ signal: AbortSignal.timeout(REFRESH_ALL_TIMEOUT_MS),
51
+ });
52
+ if (!res.ok)
53
+ throw new Error(`HTTP ${res.status}`);
54
+ const payload = await res.json();
55
+ return publicRefreshResult(payload.refresh);
56
+ }
38
57
  return {
39
58
  list,
59
+ refreshAll,
60
+ async resetUsage(id, redeemRequestId) {
61
+ const response = await fetch(`${base}/${encodeURIComponent(id)}/reset-usage`, {
62
+ method: "POST",
63
+ headers: { ...authHeaders, "content-type": "application/json" },
64
+ body: JSON.stringify({ redeemRequestId }),
65
+ signal: AbortSignal.timeout(REFRESH_ALL_TIMEOUT_MS),
66
+ });
67
+ if (!response.ok)
68
+ throw new Error(`HTTP ${response.status}`);
69
+ const body = await response.json();
70
+ const reset = isRecord(body) ? body.reset : undefined;
71
+ if (!isRecord(reset) || typeof reset.code !== "string" || !["reset", "nothing_to_reset", "no_credit", "already_redeemed"].includes(reset.code)
72
+ || typeof reset.usageRefreshed !== "boolean")
73
+ throw new Error("Invalid reset response");
74
+ return { code: reset.code, usageRefreshed: reset.usageRefreshed };
75
+ },
40
76
  patch(id, patch) { return send("PATCH", `/${encodeURIComponent(id)}`, patch); },
41
77
  setProviderEnabled(provider, enabled) { return send("PATCH", `/providers/${encodeURIComponent(provider)}`, { enabled }); },
42
78
  remove(id) { return send("DELETE", `/${encodeURIComponent(id)}`); },
43
79
  };
44
80
  }
81
+ function publicRefreshResult(value) {
82
+ const record = isRecord(value) ? value : {};
83
+ return {
84
+ accounts: publicInteger(record.accounts),
85
+ usageRefreshed: publicInteger(record.usageRefreshed),
86
+ usageFailed: publicInteger(record.usageFailed),
87
+ tokenRefreshFailed: publicInteger(record.tokenRefreshFailed),
88
+ durationMs: publicInteger(record.durationMs),
89
+ };
90
+ }
45
91
  function isRecord(value) {
46
92
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
47
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timo972/cc-router",
3
- "version": "0.12.0",
3
+ "version": "0.12.1-rc.1",
4
4
  "description": "Cache-aware session router for Claude Max OAuth tokens — use multiple Claude Max accounts with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {