maxpool 1.5.20 → 1.5.22
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/account-manager.js +41 -0
- package/src/index.js +18 -1
- package/src/tui.js +45 -23
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -2438,6 +2438,47 @@ export class AccountManager {
|
|
|
2438
2438
|
return idx;
|
|
2439
2439
|
}
|
|
2440
2440
|
|
|
2441
|
+
/**
|
|
2442
|
+
* Serialize runtime (client-supplied) PROVIDER accounts so they survive a
|
|
2443
|
+
* restart. They're created lazily from `cc all` request headers
|
|
2444
|
+
* (prepareRuntimeProviders), NOT from config — so without persistence a cold boot
|
|
2445
|
+
* / reload shows only the config OAuth accounts until the next `cc all` request
|
|
2446
|
+
* re-sends the tokens. Persisted to state.json (0600, same protection as the
|
|
2447
|
+
* OAuth-token-bearing config) alongside quota.
|
|
2448
|
+
*/
|
|
2449
|
+
exportRuntimeProviders() {
|
|
2450
|
+
return this.accounts
|
|
2451
|
+
.filter(a => a.runtime && a.type === 'provider' && a.credential)
|
|
2452
|
+
.map(a => ({
|
|
2453
|
+
name: a.name,
|
|
2454
|
+
type: a.type,
|
|
2455
|
+
provider: a.provider,
|
|
2456
|
+
authToken: a.credential,
|
|
2457
|
+
upstream: a.upstream,
|
|
2458
|
+
authHeader: a.authHeader,
|
|
2459
|
+
profiles: a.profiles,
|
|
2460
|
+
priority: a.priority,
|
|
2461
|
+
model: a.model,
|
|
2462
|
+
modelMap: a.modelMap,
|
|
2463
|
+
stripBetaHeaders: a.stripBetaHeaders,
|
|
2464
|
+
}));
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* Restore persisted runtime providers on boot, via the SAME upsert the header
|
|
2469
|
+
* path uses (so a restored provider is byte-identical to a header-created one). A
|
|
2470
|
+
* live `cc all` request refreshes the token before routing (prepareRuntimeProviders
|
|
2471
|
+
* runs ahead of account selection), so a stale restored token never serves a
|
|
2472
|
+
* request. Idempotent — upsertRuntimeAccount matches by name.
|
|
2473
|
+
*/
|
|
2474
|
+
restoreRuntimeProviders(list) {
|
|
2475
|
+
if (!Array.isArray(list)) return;
|
|
2476
|
+
for (const p of list) {
|
|
2477
|
+
if (!p || !p.name || !(p.authToken || p.credential)) continue;
|
|
2478
|
+
this.upsertRuntimeAccount({ ...p, authToken: p.authToken || p.credential });
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2441
2482
|
/**
|
|
2442
2483
|
* Remove an account by index.
|
|
2443
2484
|
*/
|
package/src/index.js
CHANGED
|
@@ -528,6 +528,15 @@ async function serverWorkerCommand() {
|
|
|
528
528
|
// lease holder); a cold/direct worker reads the on-disk state file.
|
|
529
529
|
const savedState = await loadState();
|
|
530
530
|
if (savedState?.quota) accountManager.restoreQuotaState(savedState.quota);
|
|
531
|
+
// Runtime fallback providers (glm-fallback/kimi-fallback) are created lazily from
|
|
532
|
+
// `cc all` request headers, not config — so without restoring them here a restart
|
|
533
|
+
// shows only the config OAuth accounts until the next `cc all` request re-sends the
|
|
534
|
+
// tokens. NOTE: a SEAMLESS reload worker reads state at spawn (before the outgoing
|
|
535
|
+
// worker's final flush), so a provider created <60s before that reload (not yet
|
|
536
|
+
// interval-flushed) is missed until the next `cc all` request — the same
|
|
537
|
+
// self-healing window as an abrupt SIGKILL. A cold restart reads the clean-shutdown
|
|
538
|
+
// final flush and restores fully (the reported zero-request case).
|
|
539
|
+
if (savedState?.runtimeProviders) accountManager.restoreRuntimeProviders(savedState.runtimeProviders);
|
|
531
540
|
// Track the state-file generation we last observed so a stale flush is refused.
|
|
532
541
|
let stateGeneration = Number(savedState?._generation) || 0;
|
|
533
542
|
|
|
@@ -550,7 +559,15 @@ async function serverWorkerCommand() {
|
|
|
550
559
|
// flush REFUSED (dropping the final quota snapshot). The guard only exists to
|
|
551
560
|
// serialize CROSS-worker writes; the final flush is provably intra-worker.
|
|
552
561
|
const expectedGeneration = force ? null : stateGeneration;
|
|
553
|
-
return saveState(
|
|
562
|
+
return saveState(
|
|
563
|
+
{
|
|
564
|
+
quota: accountManager.exportQuotaState(),
|
|
565
|
+
// Runtime providers so glm-fallback/kimi-fallback survive a restart (they're
|
|
566
|
+
// header-derived, not in config). Empty [] when none — harmless.
|
|
567
|
+
runtimeProviders: accountManager.exportRuntimeProviders(),
|
|
568
|
+
},
|
|
569
|
+
{ expectedGeneration },
|
|
570
|
+
)
|
|
554
571
|
.then(written => { if (written != null) stateGeneration = written; })
|
|
555
572
|
.catch(() => {});
|
|
556
573
|
};
|
package/src/tui.js
CHANGED
|
@@ -183,7 +183,18 @@ function bar(ratio, w = 10, resetTs) {
|
|
|
183
183
|
return out;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
// A gray placeholder the EXACT visible width of a bar(), for a quota that's absent
|
|
187
|
+
// or unpollable (label "—" / "n/a" / "probing"). Mirrors bar()'s no-data branch so
|
|
188
|
+
// provider rows stay column-aligned with OAuth rows.
|
|
189
|
+
function emptyBar(label, w = 10) {
|
|
190
|
+
const text = String(label).slice(0, w);
|
|
191
|
+
const pad = Math.max(0, w - text.length);
|
|
192
|
+
const lp = Math.floor(pad / 2);
|
|
193
|
+
const rp = pad - lp;
|
|
194
|
+
return `${ESC}100m${' '.repeat(lp)}${text}${' '.repeat(rp)}${RESET}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const __tuiTest = { formatReset, quotaLabel, bar, emptyBar, strip, loadText, countdown };
|
|
187
198
|
|
|
188
199
|
function timestamp() {
|
|
189
200
|
return new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
@@ -940,8 +951,10 @@ export class TUI {
|
|
|
940
951
|
const rawName = a.name.slice(0, 12).padEnd(12);
|
|
941
952
|
const name = isSel ? bold(rawName) : rawName;
|
|
942
953
|
|
|
943
|
-
// Type
|
|
944
|
-
|
|
954
|
+
// Type — pad to 8 so "provider" (8 chars) doesn't overflow a 7-wide column and
|
|
955
|
+
// shift the whole provider row (incl. its quota bars) 1 char out of alignment
|
|
956
|
+
// with the "oauth"/"apikey" rows.
|
|
957
|
+
const type = gray(a.type.padEnd(8));
|
|
945
958
|
|
|
946
959
|
// Status
|
|
947
960
|
let status;
|
|
@@ -1039,35 +1052,44 @@ export class TUI {
|
|
|
1039
1052
|
}
|
|
1040
1053
|
|
|
1041
1054
|
_renderProviderAcct(sel, cur, name, type, status, a, bw = 11, showBoth = true) {
|
|
1042
|
-
const completed = a.completedRequests || 0;
|
|
1043
|
-
const failed = a.failedRequests || 0;
|
|
1044
|
-
const active = a.inFlight || 0;
|
|
1045
|
-
const last = a.lastStatus ? `${statusColor(a.lastStatus)} ${formatMs(a.lastResponseMs)}` : '-';
|
|
1046
1055
|
const q = a.quota || {};
|
|
1047
1056
|
|
|
1048
|
-
//
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1057
|
+
// Ses/Wk bars in the SAME columns as OAuth rows (right after status) so the
|
|
1058
|
+
// whole table's bars line up. z.ai exposes a 5-hour token window (Ses) and —
|
|
1059
|
+
// when its monitor endpoint includes it — a weekly (Wk, intermittently absent →
|
|
1060
|
+
// "—"). Kimi's coding key has NO pollable quota (web console only) → "n/a" both
|
|
1061
|
+
// + a "console-only" note; not-yet-probed → "probing". Fields are the SEPARATE
|
|
1062
|
+
// provider* set, so a provider reading never leaks into an OAuth bar.
|
|
1063
|
+
let sesCell, wkCell, note = '';
|
|
1053
1064
|
if (q.providerSes != null || q.providerWk != null) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (this.am._quotaProbeStale?.(a))
|
|
1065
|
+
sesCell = q.providerSes != null ? bar(q.providerSes, bw, q.providerSesReset) : emptyBar('—', bw);
|
|
1066
|
+
wkCell = q.providerWk != null ? bar(q.providerWk, bw, q.providerWkReset) : emptyBar('—', bw);
|
|
1067
|
+
if (this.am._quotaProbeStale?.(a)) note = ` ${dim('stale')}`;
|
|
1057
1068
|
} else if (q.providerQuotaSource === 'console-only') {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1069
|
+
sesCell = emptyBar('n/a', bw);
|
|
1070
|
+
wkCell = emptyBar('n/a', bw);
|
|
1071
|
+
note = ` ${dim('console-only')}`;
|
|
1072
|
+
} else {
|
|
1073
|
+
sesCell = emptyBar('probing', bw);
|
|
1074
|
+
wkCell = emptyBar('probing', bw);
|
|
1061
1075
|
}
|
|
1062
1076
|
|
|
1063
|
-
let
|
|
1077
|
+
let line = ` ${sel}${cur} ${name} ${type} ${status} Ses ${sesCell}`;
|
|
1078
|
+
if (showBoth) line += ` Wk ${wkCell}`;
|
|
1079
|
+
line += note;
|
|
1080
|
+
// Same recent-load columns as OAuth (providers track inFlight + load events),
|
|
1081
|
+
// plus a compact Last <status> <ms> — the one signal that matters for a
|
|
1082
|
+
// rarely-hit fallback (loadText reads 0r when idle).
|
|
1083
|
+
line += ` ${dim(loadText(this._accountLoad(a)))}`;
|
|
1084
|
+
if (a.lastStatus) line += ` ${dim('Last')} ${statusColor(a.lastStatus)} ${dim(formatMs(a.lastResponseMs))}`;
|
|
1085
|
+
// Header-derived generic rate-limit (distinct from the monitor-endpoint quota),
|
|
1086
|
+
// if the provider upstream returns x-ratelimit-* headers — only when present.
|
|
1064
1087
|
if (q.genericLimit != null && q.genericRemaining != null) {
|
|
1065
|
-
const used = q.genericLimit - q.genericRemaining;
|
|
1066
1088
|
const reset = formatReset(q.genericReset);
|
|
1067
|
-
|
|
1089
|
+
line += ` ${dim(`Lim ${q.genericLimit - q.genericRemaining}/${q.genericLimit}${reset ? ' ' + reset : ''}`)}`;
|
|
1068
1090
|
}
|
|
1069
|
-
|
|
1070
|
-
return
|
|
1091
|
+
if (a.lastError) line += ` ${red('Err ' + String(a.lastError).slice(0, 18))}`;
|
|
1092
|
+
return line;
|
|
1071
1093
|
}
|
|
1072
1094
|
|
|
1073
1095
|
_accountLoad(account) {
|