@timo972/cc-router 0.7.0 → 0.9.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/CHANGELOG.md +139 -0
- package/README.md +12 -87
- package/dist/cli/cmd-accounts.js +114 -11
- package/dist/cli/index.js +0 -0
- package/dist/protocol/openai-responses-collect.js +71 -0
- package/dist/providers/anthropic/usage-refresher.js +195 -0
- package/dist/providers/anthropic/usage.js +217 -0
- package/dist/proxy/account-add.js +30 -0
- package/dist/proxy/account-deletion.js +16 -0
- package/dist/proxy/anthropic-routing.js +31 -2
- package/dist/proxy/lease-lifecycle.js +182 -22
- package/dist/proxy/logger.js +3 -0
- package/dist/proxy/messages-cross-route.js +4 -1
- package/dist/proxy/request-model.js +17 -0
- package/dist/proxy/responses-server.js +43 -1
- package/dist/proxy/server.js +198 -24
- package/dist/proxy/session-router.js +12 -8
- package/dist/proxy/stats.js +11 -0
- package/dist/proxy/token-pool.js +379 -108
- package/dist/ui/Dashboard.js +90 -4
- package/dist/ui/accountsApi.js +136 -20
- package/package.json +12 -11
package/dist/ui/Dashboard.js
CHANGED
|
@@ -11,6 +11,87 @@ const EMPTY_RL = {
|
|
|
11
11
|
sevenDayUtil: 0, sevenDayReset: 0, claim: "", plan: "",
|
|
12
12
|
requestsLimit: 0, lastUpdated: 0,
|
|
13
13
|
};
|
|
14
|
+
/** Match TokenPool's source precedence for the dashboard's global windows. */
|
|
15
|
+
export function getGlobalCapacityView(rateLimits) {
|
|
16
|
+
const usage = rateLimits.usage;
|
|
17
|
+
const snapshotIsCurrent = usage !== undefined &&
|
|
18
|
+
usage.fetchStatus !== "unavailable" &&
|
|
19
|
+
usage.fetchedAt >= rateLimits.lastUpdated;
|
|
20
|
+
const usageFetchStatus = usage?.fetchStatus === "fresh" && !snapshotIsCurrent
|
|
21
|
+
? "stale"
|
|
22
|
+
: usage?.fetchStatus;
|
|
23
|
+
return {
|
|
24
|
+
fiveHour: snapshotIsCurrent && usage.fiveHour
|
|
25
|
+
? usage.fiveHour
|
|
26
|
+
: { utilization: rateLimits.fiveHourUtil, resetAt: rateLimits.fiveHourReset },
|
|
27
|
+
sevenDay: snapshotIsCurrent && usage.sevenDay
|
|
28
|
+
? usage.sevenDay
|
|
29
|
+
: { utilization: rateLimits.sevenDayUtil, resetAt: rateLimits.sevenDayReset },
|
|
30
|
+
usageFetchStatus,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Turn the safe account payload into compact dynamic model/cooldown rows. */
|
|
34
|
+
export function getAccountCapacityRows(account) {
|
|
35
|
+
const usage = account.rateLimits?.usage;
|
|
36
|
+
const modelCooldowns = account.modelCooldowns ?? [];
|
|
37
|
+
const rows = [];
|
|
38
|
+
const matchedCooldowns = new Set();
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (usage?.modelLimits.length) {
|
|
41
|
+
const usageFetchStatus = usage.fetchStatus === "fresh" &&
|
|
42
|
+
usage.fetchedAt < (account.rateLimits?.lastUpdated ?? 0)
|
|
43
|
+
? "stale"
|
|
44
|
+
: usage.fetchStatus;
|
|
45
|
+
const usageState = usageFetchStatus === "fresh" ? undefined : `usage ${usageFetchStatus}`;
|
|
46
|
+
const paidExtraAvailable = usage.extraUsage?.usable === true;
|
|
47
|
+
for (const limit of usage.modelLimits) {
|
|
48
|
+
const requestedCooldown = modelCooldowns.find(cooldown => cooldown.modelFamily === limit.modelFamily && cooldown.untilMs > now);
|
|
49
|
+
const exhausted = limit.utilization >= 1;
|
|
50
|
+
const capacityState = usageState
|
|
51
|
+
? usageState
|
|
52
|
+
: !limit.active
|
|
53
|
+
? "inactive"
|
|
54
|
+
: exhausted && paidExtraAvailable
|
|
55
|
+
? "paid extra active"
|
|
56
|
+
: exhausted
|
|
57
|
+
? "exhausted"
|
|
58
|
+
: "included available";
|
|
59
|
+
const state = requestedCooldown
|
|
60
|
+
? `${capacityState} · requested-model cooldown`
|
|
61
|
+
: capacityState;
|
|
62
|
+
if (requestedCooldown)
|
|
63
|
+
matchedCooldowns.add(requestedCooldown.modelFamily);
|
|
64
|
+
const color = requestedCooldown ? "yellow"
|
|
65
|
+
: usageState ? usageFetchStatus === "stale" ? "yellow" : "gray"
|
|
66
|
+
: !limit.active ? "gray"
|
|
67
|
+
: exhausted && paidExtraAvailable ? "yellow"
|
|
68
|
+
: exhausted || limit.severity === "critical" ? "red"
|
|
69
|
+
: limit.severity === "warning" || limit.utilization >= 0.7 ? "yellow"
|
|
70
|
+
: "green";
|
|
71
|
+
rows.push({
|
|
72
|
+
label: limit.displayName,
|
|
73
|
+
state,
|
|
74
|
+
color,
|
|
75
|
+
utilization: limit.utilization,
|
|
76
|
+
resetAt: limit.resetAt,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const cooldown of modelCooldowns) {
|
|
81
|
+
if (matchedCooldowns.has(cooldown.modelFamily) || cooldown.untilMs <= now)
|
|
82
|
+
continue;
|
|
83
|
+
rows.push({
|
|
84
|
+
label: `cooldown ${cooldown.modelFamily}`,
|
|
85
|
+
state: "requested-model cooldown",
|
|
86
|
+
color: "yellow",
|
|
87
|
+
resetAt: Math.floor(cooldown.untilMs / 1_000),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (account.globalCooldownUntilMs && account.globalCooldownUntilMs > now) {
|
|
91
|
+
rows.push({ label: "cooldown", state: "global", color: "red", resetAt: Math.floor(account.globalCooldownUntilMs / 1_000) });
|
|
92
|
+
}
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
14
95
|
export function Dashboard({ port, baseUrl, authToken, onIntent }) {
|
|
15
96
|
const { exit } = useApp();
|
|
16
97
|
const [data, setData] = useState(null);
|
|
@@ -453,6 +534,9 @@ function ProviderBadge({ label, status, ready, }) {
|
|
|
453
534
|
// ─── Account row (two-line: status + utilization bars) ───────────────────────
|
|
454
535
|
function AccountRow({ account: a, selected }) {
|
|
455
536
|
const rl = a.rateLimits ?? EMPTY_RL;
|
|
537
|
+
const usage = rl.usage;
|
|
538
|
+
const globalCapacity = getGlobalCapacityView(rl);
|
|
539
|
+
const capacityRows = getAccountCapacityRows(a);
|
|
456
540
|
const isLimited = rl.status === "rate_limited";
|
|
457
541
|
const isDisabled = a.enabled === false;
|
|
458
542
|
const dot = isDisabled ? "⊘" : isLimited ? "⊘" : a.busy ? "◌" : a.healthy ? "●" : "●";
|
|
@@ -475,7 +559,7 @@ function AccountRow({ account: a, selected }) {
|
|
|
475
559
|
: "";
|
|
476
560
|
const pointer = selected ? "▶" : " ";
|
|
477
561
|
const nameColor = isDisabled ? "gray" : undefined;
|
|
478
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: pointer }), _jsxs(Text, { color: dotColor, children: [" ", dot, " "] }), _jsx(Text, { color: nameColor, dimColor: isDisabled, children: a.id.slice(0, 20).padEnd(20) }), _jsx(Text, { color: statusColor, children: statusLabel }), providerTag && _jsx(Text, { color: a.provider === "openai_subscription" ? "cyan" : "magenta", children: providerTag.padEnd(10) }), !providerTag && _jsx(Text, { children: "".padEnd(10) }), _jsx(Text, { color: "gray", children: " req " }), _jsx(Text, { color: "white", children: String(a.requestCount).padStart(5) }), _jsx(Text, { color: "gray", children: " err " }), _jsx(Text, { color: a.errorCount > 0 ? "red" : "gray", children: String(a.errorCount).padStart(3) }), _jsx(Text, { color: "gray", children: " tok " }), _jsx(Text, { color: expiryColor, children: expiryLabel.padEnd(8) }), _jsx(Text, { color: "gray", children: " last " }), _jsx(Text, { color: "gray", children: formatAgo(a.lastUsedMs) }), a.provider !== "openai_subscription" && (_jsxs(Text, { color: "gray", children: [" ", a.activeSessions ?? 0, " active / ", a.inFlightRequests ?? 0, " streams"] })), capsHint && _jsx(Text, { color: "yellow", children: capsHint })] }), rl.lastUpdated > 0 && (_jsxs(Box, { paddingLeft: 4, children: [_jsx(UtilBar, { label: "5h", util:
|
|
562
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: pointer }), _jsxs(Text, { color: dotColor, children: [" ", dot, " "] }), _jsx(Text, { color: nameColor, dimColor: isDisabled, children: a.id.slice(0, 20).padEnd(20) }), _jsx(Text, { color: statusColor, children: statusLabel }), providerTag && _jsx(Text, { color: a.provider === "openai_subscription" ? "cyan" : "magenta", children: providerTag.padEnd(10) }), !providerTag && _jsx(Text, { children: "".padEnd(10) }), _jsx(Text, { color: "gray", children: " req " }), _jsx(Text, { color: "white", children: String(a.requestCount).padStart(5) }), _jsx(Text, { color: "gray", children: " err " }), _jsx(Text, { color: a.errorCount > 0 ? "red" : "gray", children: String(a.errorCount).padStart(3) }), _jsx(Text, { color: "gray", children: " tok " }), _jsx(Text, { color: expiryColor, children: expiryLabel.padEnd(8) }), _jsx(Text, { color: "gray", children: " last " }), _jsx(Text, { color: "gray", children: formatAgo(a.lastUsedMs) }), a.provider !== "openai_subscription" && (_jsxs(Text, { color: "gray", children: [" ", a.activeSessions ?? 0, " active / ", a.inFlightRequests ?? 0, " streams"] })), capsHint && _jsx(Text, { color: "yellow", children: capsHint })] }), (rl.lastUpdated > 0 || usage) && (_jsxs(Box, { paddingLeft: 4, children: [_jsx(UtilBar, { label: "5h", util: globalCapacity.fiveHour.utilization, resetTs: globalCapacity.fiveHour.resetAt, isActive: rl.claim === "five_hour", cap: s5 }), _jsx(Text, { children: " " }), _jsx(UtilBar, { label: "7d all-model", util: globalCapacity.sevenDay.utilization, resetTs: globalCapacity.sevenDay.resetAt, isActive: rl.claim === "seven_day", cap: w7 }), usage && _jsx(Text, { color: globalCapacity.usageFetchStatus === "fresh" ? "gray" : "yellow", children: ` usage ${globalCapacity.usageFetchStatus} ${usage.fetchedAt > 0 ? formatAgo(usage.fetchedAt) : ""}` })] })), capacityRows.map((row, index) => (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: row.color, children: [" ", row.label] }), _jsxs(Text, { color: "gray", children: [" \u00B7 ", row.state] }), row.utilization !== undefined && _jsx(Text, { color: row.color, children: ` ${Math.round(row.utilization * 100)}%` }), row.resetAt !== undefined && row.resetAt > 0 && (_jsxs(Text, { color: "gray", children: [" ", `↻${formatResetIn(row.resetAt)}`] }))] }, `${row.label}-${index}`)))] }));
|
|
479
563
|
}
|
|
480
564
|
// ─── Utilization bar ─────────────────────────────────────────────────────────
|
|
481
565
|
function UtilBar({ label, util, resetTs, isActive, cap }) {
|
|
@@ -507,8 +591,9 @@ function LogRow({ log, selected }) {
|
|
|
507
591
|
const time = new Date(log.ts).toLocaleTimeString("en-GB", { hour12: false });
|
|
508
592
|
const isError = log.type === "error";
|
|
509
593
|
const isRefresh = log.type === "refresh";
|
|
510
|
-
const
|
|
511
|
-
const
|
|
594
|
+
const isWarn = log.type === "warn";
|
|
595
|
+
const typeColor = isError ? "red" : isWarn ? "yellow" : isRefresh ? "yellow" : "gray";
|
|
596
|
+
const typeIcon = isError ? "✗" : isWarn ? "⚠" : isRefresh ? "↻" : "→";
|
|
512
597
|
const statusColor = log.statusCode === undefined ? undefined
|
|
513
598
|
: log.statusCode >= 500 ? "red"
|
|
514
599
|
: log.statusCode >= 400 ? "yellow"
|
|
@@ -543,6 +628,7 @@ function DetailPanel({ log }) {
|
|
|
543
628
|
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
544
629
|
});
|
|
545
630
|
const isError = log.type === "error";
|
|
631
|
+
const isWarn = log.type === "warn";
|
|
546
632
|
const statusLabel = log.statusCode === undefined ? "—"
|
|
547
633
|
: log.statusCode === 0 ? "connection error"
|
|
548
634
|
: `${log.statusCode} ${httpStatusText(log.statusCode)}`;
|
|
@@ -551,7 +637,7 @@ function DetailPanel({ log }) {
|
|
|
551
637
|
: log.statusCode >= 500 ? "red"
|
|
552
638
|
: log.statusCode >= 400 ? "yellow"
|
|
553
639
|
: "green";
|
|
554
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, color: isError ? "red" : "cyan", children: " DETAILS " }), _jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, children: [_jsx(Field, { label: "Time", value: time }), _jsx(Field, { label: "Account", value: log.accountId })] }), _jsxs(Box, { gap: 2, children: [_jsx(Field, { label: "Method", value: log.method ?? "—" }), _jsx(Field, { label: "Path", value: log.path ?? "—" })] }), _jsxs(Box, { gap: 2, children: [_jsx(FieldColored, { label: "Status", value: statusLabel, color: statusColor }), _jsx(Field, { label: "Duration", value: log.durationMs !== undefined ? `${log.durationMs}ms` : "—" }), _jsx(Field, { label: "Type", value: log.type }), _jsx(Field, { label: "Source", value: sourceFullLabel(log.source) })] }), log.details && (_jsx(Box, { children: _jsx(Field, { label: "Details", value: log.details }) })), log.cacheReadTokens !== undefined && (_jsx(Box, { gap: 2, children: _jsx(CacheBreakdown, { read: log.cacheReadTokens, created: log.cacheCreationTokens ?? 0, input: log.inputTokens ?? 0, output: log.outputTokens ?? 0 }) }))] })] }));
|
|
640
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, color: isError ? "red" : isWarn ? "yellow" : "cyan", children: " DETAILS " }), _jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, children: [_jsx(Field, { label: "Time", value: time }), _jsx(Field, { label: "Account", value: log.accountId })] }), _jsxs(Box, { gap: 2, children: [_jsx(Field, { label: "Method", value: log.method ?? "—" }), _jsx(Field, { label: "Path", value: log.path ?? "—" })] }), _jsxs(Box, { gap: 2, children: [_jsx(FieldColored, { label: "Status", value: statusLabel, color: statusColor }), _jsx(Field, { label: "Duration", value: log.durationMs !== undefined ? `${log.durationMs}ms` : "—" }), _jsx(Field, { label: "Type", value: log.type }), _jsx(Field, { label: "Source", value: sourceFullLabel(log.source) })] }), log.details && (_jsx(Box, { children: _jsx(Field, { label: "Details", value: log.details }) })), log.cacheReadTokens !== undefined && (_jsx(Box, { gap: 2, children: _jsx(CacheBreakdown, { read: log.cacheReadTokens, created: log.cacheCreationTokens ?? 0, input: log.inputTokens ?? 0, output: log.outputTokens ?? 0 }) }))] })] }));
|
|
555
641
|
}
|
|
556
642
|
function Field({ label, value }) {
|
|
557
643
|
return (_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [label, ": "] }), _jsx(Text, { color: "white", children: value })] }));
|
package/dist/ui/accountsApi.js
CHANGED
|
@@ -2,28 +2,22 @@
|
|
|
2
2
|
* Tiny authenticated HTTP client for /cc-router/accounts.
|
|
3
3
|
*
|
|
4
4
|
* Used by the Ink dashboard to mutate account settings (enable/disable,
|
|
5
|
-
* set per-account caps, delete) without exiting
|
|
6
|
-
* flow is
|
|
7
|
-
* src/cli/cmd-status.ts `runAddAccountFlow`.
|
|
5
|
+
* set per-account caps, delete) without exiting Ink first. The `addAccount`
|
|
6
|
+
* flow is not here because it runs inquirer.
|
|
8
7
|
*/
|
|
9
8
|
const REQUEST_TIMEOUT_MS = 3_000;
|
|
9
|
+
const MAX_PUBLIC_ROWS = 12;
|
|
10
10
|
export function createAccountsApi(baseUrl, authToken) {
|
|
11
11
|
const base = baseUrl.replace(/\/+$/, "") + "/cc-router/accounts";
|
|
12
|
-
const authHeaders = authToken
|
|
13
|
-
? { authorization: `Bearer ${authToken}` }
|
|
14
|
-
: {};
|
|
12
|
+
const authHeaders = authToken ? { authorization: `Bearer ${authToken}` } : {};
|
|
15
13
|
async function send(method, path, body) {
|
|
16
14
|
const res = await fetch(base + path, {
|
|
17
15
|
method,
|
|
18
|
-
headers: {
|
|
19
|
-
...authHeaders,
|
|
20
|
-
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
21
|
-
},
|
|
16
|
+
headers: { ...authHeaders, ...(body !== undefined ? { "content-type": "application/json" } : {}) },
|
|
22
17
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
23
18
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
24
19
|
});
|
|
25
20
|
if (!res.ok) {
|
|
26
|
-
// Try to surface the server's error message if we can read one
|
|
27
21
|
let detail = "";
|
|
28
22
|
try {
|
|
29
23
|
const data = await res.json();
|
|
@@ -34,15 +28,137 @@ export function createAccountsApi(baseUrl, authToken) {
|
|
|
34
28
|
throw new Error(`HTTP ${res.status}${detail}`);
|
|
35
29
|
}
|
|
36
30
|
}
|
|
31
|
+
async function list() {
|
|
32
|
+
const res = await fetch(base, { method: "GET", headers: authHeaders, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
throw new Error(`HTTP ${res.status}`);
|
|
35
|
+
const payload = await res.json();
|
|
36
|
+
return Array.isArray(payload.accounts) ? payload.accounts.flatMap(publicAccountSafeView) : [];
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
list,
|
|
40
|
+
patch(id, patch) { return send("PATCH", `/${encodeURIComponent(id)}`, patch); },
|
|
41
|
+
setProviderEnabled(provider, enabled) { return send("PATCH", `/providers/${encodeURIComponent(provider)}`, { enabled }); },
|
|
42
|
+
remove(id) { return send("DELETE", `/${encodeURIComponent(id)}`); },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function isRecord(value) {
|
|
46
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
function publicAccountSafeView(value) {
|
|
49
|
+
if (!isRecord(value) || typeof value.id !== "string")
|
|
50
|
+
return [];
|
|
51
|
+
const provider = value.provider === "anthropic_subscription" || value.provider === "openai_subscription"
|
|
52
|
+
? value.provider
|
|
53
|
+
: undefined;
|
|
54
|
+
const rateLimits = publicRateLimits(value.rateLimits);
|
|
55
|
+
const modelCooldowns = publicCooldowns(value.modelCooldowns);
|
|
56
|
+
return [{
|
|
57
|
+
id: publicText(value.id, 128, "unknown-account"),
|
|
58
|
+
...(provider ? { provider } : {}),
|
|
59
|
+
...(rateLimits ? { rateLimits } : {}),
|
|
60
|
+
globalCooldownUntilMs: publicTimestamp(value.globalCooldownUntilMs),
|
|
61
|
+
modelCooldowns,
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
function publicRateLimits(value) {
|
|
65
|
+
if (!isRecord(value))
|
|
66
|
+
return undefined;
|
|
67
|
+
const usage = publicUsage(value.usage);
|
|
37
68
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
status: value.status === "allowed" || value.status === "rate_limited" ? value.status : "unknown",
|
|
70
|
+
fiveHourUtil: publicUtilization(value.fiveHourUtil),
|
|
71
|
+
fiveHourReset: publicTimestamp(value.fiveHourReset),
|
|
72
|
+
sevenDayUtil: publicUtilization(value.sevenDayUtil),
|
|
73
|
+
sevenDayReset: publicTimestamp(value.sevenDayReset),
|
|
74
|
+
claim: publicClaim(value.claim),
|
|
75
|
+
plan: value.plan === "Pro" || value.plan === "Max 5x" || value.plan === "Max 20x" ? value.plan : "",
|
|
76
|
+
requestsLimit: publicInteger(value.requestsLimit),
|
|
77
|
+
lastUpdated: publicTimestamp(value.lastUpdated),
|
|
78
|
+
...(usage ? { usage } : {}),
|
|
47
79
|
};
|
|
48
80
|
}
|
|
81
|
+
function publicUsage(value) {
|
|
82
|
+
if (!isRecord(value))
|
|
83
|
+
return undefined;
|
|
84
|
+
const fiveHour = publicWindow(value.fiveHour);
|
|
85
|
+
const sevenDay = publicWindow(value.sevenDay);
|
|
86
|
+
const extraUsage = isRecord(value.extraUsage)
|
|
87
|
+
? {
|
|
88
|
+
enabled: value.extraUsage.enabled === true,
|
|
89
|
+
spendLimitReached: value.extraUsage.spendLimitReached === true,
|
|
90
|
+
usable: value.extraUsage.usable === true,
|
|
91
|
+
}
|
|
92
|
+
: undefined;
|
|
93
|
+
return {
|
|
94
|
+
...(fiveHour ? { fiveHour } : {}),
|
|
95
|
+
...(sevenDay ? { sevenDay } : {}),
|
|
96
|
+
modelLimits: (Array.isArray(value.modelLimits) ? value.modelLimits : [])
|
|
97
|
+
.flatMap(publicModelLimit)
|
|
98
|
+
.slice(0, MAX_PUBLIC_ROWS),
|
|
99
|
+
...(extraUsage ? { extraUsage } : {}),
|
|
100
|
+
fetchedAt: publicTimestamp(value.fetchedAt),
|
|
101
|
+
fetchStatus: publicFetchStatus(value.fetchStatus),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function publicWindow(value) {
|
|
105
|
+
if (!isRecord(value))
|
|
106
|
+
return undefined;
|
|
107
|
+
return { utilization: publicUtilization(value.utilization), resetAt: publicTimestamp(value.resetAt) };
|
|
108
|
+
}
|
|
109
|
+
function publicModelLimit(value) {
|
|
110
|
+
if (!isRecord(value))
|
|
111
|
+
return [];
|
|
112
|
+
return [{
|
|
113
|
+
modelFamily: publicModelFamily(value.modelFamily),
|
|
114
|
+
displayName: publicText(value.displayName, 80, "Unknown model"),
|
|
115
|
+
utilization: publicUtilization(value.utilization),
|
|
116
|
+
resetAt: publicTimestamp(value.resetAt),
|
|
117
|
+
active: value.active === true,
|
|
118
|
+
severity: publicSeverity(value.severity),
|
|
119
|
+
}];
|
|
120
|
+
}
|
|
121
|
+
function publicCooldowns(value) {
|
|
122
|
+
if (!Array.isArray(value))
|
|
123
|
+
return [];
|
|
124
|
+
return value.flatMap(cooldown => {
|
|
125
|
+
if (!isRecord(cooldown))
|
|
126
|
+
return [];
|
|
127
|
+
const untilMs = publicTimestamp(cooldown.untilMs);
|
|
128
|
+
return untilMs > 0 ? [{ modelFamily: publicModelFamily(cooldown.modelFamily), untilMs }] : [];
|
|
129
|
+
}).slice(0, MAX_PUBLIC_ROWS);
|
|
130
|
+
}
|
|
131
|
+
function publicUtilization(value) {
|
|
132
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
133
|
+
}
|
|
134
|
+
function publicTimestamp(value) {
|
|
135
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
136
|
+
}
|
|
137
|
+
function publicInteger(value) {
|
|
138
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
139
|
+
}
|
|
140
|
+
function publicText(value, maxLength, fallback) {
|
|
141
|
+
if (typeof value !== "string")
|
|
142
|
+
return fallback;
|
|
143
|
+
const normalized = value.replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, maxLength);
|
|
144
|
+
return normalized || fallback;
|
|
145
|
+
}
|
|
146
|
+
function publicModelFamily(value) {
|
|
147
|
+
return typeof value === "string" && /^[a-z0-9-]{1,64}$/.test(value) ? value : "unknown";
|
|
148
|
+
}
|
|
149
|
+
function publicSeverity(value) {
|
|
150
|
+
return value === "warning" || value === "critical" ? value : value ? "unknown" : "";
|
|
151
|
+
}
|
|
152
|
+
function publicFetchStatus(value) {
|
|
153
|
+
return value === "fresh" || value === "stale" || value === "unavailable" ? value : "unavailable";
|
|
154
|
+
}
|
|
155
|
+
function publicClaim(value) {
|
|
156
|
+
if (typeof value !== "string")
|
|
157
|
+
return "unknown";
|
|
158
|
+
const claim = value.trim().toLowerCase();
|
|
159
|
+
if (!claim)
|
|
160
|
+
return "";
|
|
161
|
+
if (claim === "five_hour" || claim === "seven_day" || claim === "seven_day_oauth_apps" || claim === "seven_day_overage_included")
|
|
162
|
+
return claim;
|
|
163
|
+
return claim.startsWith("seven_day_") ? "seven_day_model" : "unknown";
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timo972/cc-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": {
|
|
7
7
|
"cc-router": "dist/cli/index.js"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"dev": "tsx src/cli/index.ts",
|
|
12
|
-
"start": "node dist/cli/index.js",
|
|
13
|
-
"test": "vitest run",
|
|
14
|
-
"test:watch": "vitest",
|
|
15
|
-
"lint": "tsc --noEmit"
|
|
16
|
-
},
|
|
17
9
|
"keywords": [
|
|
18
10
|
"claude",
|
|
19
11
|
"anthropic",
|
|
@@ -57,6 +49,7 @@
|
|
|
57
49
|
},
|
|
58
50
|
"devDependencies": {
|
|
59
51
|
"@types/express": "^4.17.21",
|
|
52
|
+
"@types/express-serve-static-core": "^4.17.33",
|
|
60
53
|
"@types/node": "^20.0.0",
|
|
61
54
|
"@types/react": "^18.3.0",
|
|
62
55
|
"tsx": "^4.19.0",
|
|
@@ -64,6 +57,14 @@
|
|
|
64
57
|
"vitest": "^4.1.2"
|
|
65
58
|
},
|
|
66
59
|
"engines": {
|
|
67
|
-
"node": ">=
|
|
60
|
+
"node": ">=22.0.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc",
|
|
64
|
+
"dev": "tsx src/cli/index.ts",
|
|
65
|
+
"start": "node dist/cli/index.js",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"lint": "tsc --noEmit"
|
|
68
69
|
}
|
|
69
|
-
}
|
|
70
|
+
}
|