@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-8rqfbhfw.js +221 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatQuota,
|
|
3
|
+
projectQuota,
|
|
4
|
+
quotaWindowName
|
|
5
|
+
} from "./shared/d5a8f1061ee2f114-index.js";
|
|
6
|
+
const QUOTA_WINDOW_KEYS = ["primary", "secondary"], LEGACY_WINDOW_MINUTES = {
|
|
7
|
+
primary: 300,
|
|
8
|
+
secondary: 10080
|
|
9
|
+
};
|
|
10
|
+
export function formatWindowLabel(windowMinutes, fallbackKey) {
|
|
11
|
+
return quotaWindowName({ scope: "all", label: fallbackKey, windowMinutes });
|
|
12
|
+
}
|
|
13
|
+
export function getPresentQuotaWindows(quota) {
|
|
14
|
+
if (!quota)
|
|
15
|
+
return [];
|
|
16
|
+
const rows = [];
|
|
17
|
+
for (const key of QUOTA_WINDOW_KEYS) {
|
|
18
|
+
const window = quota[key];
|
|
19
|
+
if (!window)
|
|
20
|
+
continue;
|
|
21
|
+
const configuredMinutes = window.windowMinutes, windowMinutes = configuredMinutes !== void 0 && Number.isFinite(configuredMinutes) && configuredMinutes > 0 ? configuredMinutes : LEGACY_WINDOW_MINUTES[key];
|
|
22
|
+
rows.push({
|
|
23
|
+
key,
|
|
24
|
+
label: formatWindowLabel(configuredMinutes, key),
|
|
25
|
+
window,
|
|
26
|
+
windowMs: windowMinutes * 60000
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return rows;
|
|
30
|
+
}
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
32
|
+
import { constants, copyFileSync, mkdirSync, statSync } from "node:fs";
|
|
33
|
+
import { readFile } from "node:fs/promises";
|
|
34
|
+
import { homedir, tmpdir } from "node:os";
|
|
35
|
+
import { dirname, join } from "node:path";
|
|
36
|
+
import {
|
|
37
|
+
createSidebarFile
|
|
38
|
+
} from "./shared/7a91bb630e6f4804-index.js";
|
|
39
|
+
import { createLogger } from "./logger.js";
|
|
40
|
+
const logSb = createLogger("sidebar"), STATE_FILE_ENV = "OPENCODE_OPENAI_AUTH_SIDEBAR_STATE_FILE";
|
|
41
|
+
function defaultStateFile() {
|
|
42
|
+
const base = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
|
|
43
|
+
return join(base, "cortexkit", "openai-auth", "sidebar-state.json");
|
|
44
|
+
}
|
|
45
|
+
let legacySidebarStateFile = join(tmpdir(), "opencode-openai-auth", "sidebar-state.json");
|
|
46
|
+
export function setLegacySidebarStateFileForTest(file) {
|
|
47
|
+
legacySidebarStateFile = file;
|
|
48
|
+
importedDefaultFiles.clear();
|
|
49
|
+
}
|
|
50
|
+
const SESSION_HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
51
|
+
export const STICKY_ASSIGNMENT_MAX_AGE_MS = 604800000, STICKY_ASSIGNMENT_MAX_ENTRIES = 256;
|
|
52
|
+
const STICKY_ASSIGNMENT_LAST_SEEN_TOUCH_MS = 3600000;
|
|
53
|
+
export function hashSidebarSessionId(sessionId) {
|
|
54
|
+
return createHash("sha256").update(sessionId).digest("hex");
|
|
55
|
+
}
|
|
56
|
+
function normalizeResetCredits(value) {
|
|
57
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
58
|
+
}
|
|
59
|
+
function resetCreditsField(value) {
|
|
60
|
+
const credits = normalizeResetCredits(value);
|
|
61
|
+
return credits !== void 0 ? { resetCredits: credits } : {};
|
|
62
|
+
}
|
|
63
|
+
function normalizeActiveRouting(value) {
|
|
64
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
65
|
+
return;
|
|
66
|
+
const normalized = {};
|
|
67
|
+
for (const [sessionId, rawEntry] of Object.entries(value)) {
|
|
68
|
+
if (rawEntry === null || typeof rawEntry !== "object" || Array.isArray(rawEntry))
|
|
69
|
+
continue;
|
|
70
|
+
const entry = rawEntry;
|
|
71
|
+
if (typeof entry.activeId !== "string" || typeof entry.route !== "string" || typeof entry.updatedAt !== "number" || !Number.isFinite(entry.updatedAt))
|
|
72
|
+
continue;
|
|
73
|
+
normalized[sessionId] = {
|
|
74
|
+
activeId: entry.activeId,
|
|
75
|
+
route: entry.route,
|
|
76
|
+
updatedAt: entry.updatedAt
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
80
|
+
}
|
|
81
|
+
function normalizeStickyAssignments(value) {
|
|
82
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
83
|
+
return;
|
|
84
|
+
const normalized = {};
|
|
85
|
+
for (const [sessionHash, rawAssignment] of Object.entries(value)) {
|
|
86
|
+
if (!SESSION_HASH_PATTERN.test(sessionHash) || rawAssignment === null || typeof rawAssignment !== "object" || Array.isArray(rawAssignment))
|
|
87
|
+
continue;
|
|
88
|
+
const assignment = rawAssignment;
|
|
89
|
+
if (typeof assignment.accountId !== "string" || assignment.accountId.length === 0 || typeof assignment.assignedAt !== "number" || !Number.isFinite(assignment.assignedAt) || assignment.assignedAt < 0 || typeof assignment.lastSeenAt !== "number" || !Number.isFinite(assignment.lastSeenAt) || assignment.lastSeenAt < 0 || typeof assignment.inputBytes !== "number" || !Number.isFinite(assignment.inputBytes) || assignment.inputBytes < 0)
|
|
90
|
+
continue;
|
|
91
|
+
const quotaCheckedAt = assignment.quotaCheckedAt;
|
|
92
|
+
if (quotaCheckedAt !== void 0 && (typeof quotaCheckedAt !== "number" || !Number.isFinite(quotaCheckedAt) || quotaCheckedAt < 0))
|
|
93
|
+
continue;
|
|
94
|
+
const wireAccountId = assignment.wireAccountId;
|
|
95
|
+
normalized[sessionHash] = {
|
|
96
|
+
accountId: assignment.accountId,
|
|
97
|
+
assignedAt: assignment.assignedAt,
|
|
98
|
+
lastSeenAt: assignment.lastSeenAt,
|
|
99
|
+
inputBytes: assignment.inputBytes,
|
|
100
|
+
...quotaCheckedAt === void 0 ? {} : { quotaCheckedAt },
|
|
101
|
+
...typeof wireAccountId === "string" ? { wireAccountId } : {}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
105
|
+
}
|
|
106
|
+
export function getSidebarStateFile() {
|
|
107
|
+
const override = process.env[STATE_FILE_ENV];
|
|
108
|
+
if (override)
|
|
109
|
+
return override;
|
|
110
|
+
const file = defaultStateFile();
|
|
111
|
+
if (!importedDefaultFiles.has(file)) {
|
|
112
|
+
importedDefaultFiles.add(file);
|
|
113
|
+
importLegacySidebarState(file, legacySidebarStateFile);
|
|
114
|
+
}
|
|
115
|
+
return file;
|
|
116
|
+
}
|
|
117
|
+
const importedDefaultFiles = new Set;
|
|
118
|
+
function importLegacySidebarState(file, legacyFile) {
|
|
119
|
+
try {
|
|
120
|
+
mkdirSync(dirname(file), { recursive: !0, mode: 448 });
|
|
121
|
+
copyFileSync(legacyFile, file, constants.COPYFILE_EXCL);
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
export const DEFAULT_SIDEBAR_STATE = {
|
|
125
|
+
main: { quota: null, killed: !1 },
|
|
126
|
+
fallbacks: [],
|
|
127
|
+
activeId: void 0,
|
|
128
|
+
route: "main",
|
|
129
|
+
lastUpdated: 0
|
|
130
|
+
};
|
|
131
|
+
export function normalizeSidebarState(raw) {
|
|
132
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
133
|
+
return DEFAULT_SIDEBAR_STATE;
|
|
134
|
+
const r = raw, rawMain = r.main;
|
|
135
|
+
let main;
|
|
136
|
+
if (rawMain !== null && typeof rawMain === "object" && !Array.isArray(rawMain)) {
|
|
137
|
+
const m = rawMain;
|
|
138
|
+
main = {
|
|
139
|
+
quota: "quota" in m ? m.quota : null,
|
|
140
|
+
killed: typeof m.killed === "boolean" ? m.killed : !1,
|
|
141
|
+
...typeof m.mainAccountId === "string" ? { mainAccountId: m.mainAccountId } : {},
|
|
142
|
+
...typeof m.quotaBackedOff === "boolean" ? { quotaBackedOff: m.quotaBackedOff } : {},
|
|
143
|
+
...typeof m.quotaBackoffUntil === "number" ? { quotaBackoffUntil: m.quotaBackoffUntil } : {},
|
|
144
|
+
...typeof m.refreshBackedOff === "boolean" ? { refreshBackedOff: m.refreshBackedOff } : {},
|
|
145
|
+
...typeof m.refreshBackoffUntil === "number" ? { refreshBackoffUntil: m.refreshBackoffUntil } : {},
|
|
146
|
+
...resetCreditsField(m.resetCredits)
|
|
147
|
+
};
|
|
148
|
+
} else
|
|
149
|
+
main = { quota: null, killed: !1 };
|
|
150
|
+
const fallbacks = normalizeAccountList(r.fallbacks), vaultAccounts = Array.isArray(r.vaultAccounts) ? normalizeAccountList(r.vaultAccounts) : void 0, activeId = typeof r.activeId === "string" ? r.activeId : void 0, route = typeof r.route === "string" ? r.route : DEFAULT_SIDEBAR_STATE.route, lastUpdated = typeof r.lastUpdated === "number" ? r.lastUpdated : 0, planType = typeof r.planType === "string" ? r.planType : void 0, credits = typeof r.credits === "number" ? r.credits : void 0, activeRouting = normalizeActiveRouting(r.activeRouting), stickyAssignments = normalizeStickyAssignments(r.stickyAssignments);
|
|
151
|
+
return {
|
|
152
|
+
main,
|
|
153
|
+
fallbacks,
|
|
154
|
+
activeId,
|
|
155
|
+
route,
|
|
156
|
+
lastUpdated,
|
|
157
|
+
...activeRouting !== void 0 ? { activeRouting } : {},
|
|
158
|
+
...stickyAssignments !== void 0 ? { stickyAssignments } : {},
|
|
159
|
+
...planType !== void 0 ? { planType } : {},
|
|
160
|
+
...credits !== void 0 ? { credits } : {},
|
|
161
|
+
...r.accountPool === !0 ? { accountPool: !0 } : {},
|
|
162
|
+
...vaultAccounts !== void 0 ? { vaultAccounts } : {}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function normalizeAccountList(raw) {
|
|
166
|
+
if (!Array.isArray(raw))
|
|
167
|
+
return [];
|
|
168
|
+
return raw.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && typeof entry.id === "string").map((e) => ({
|
|
169
|
+
id: e.id,
|
|
170
|
+
label: typeof e.label === "string" ? e.label : void 0,
|
|
171
|
+
...typeof e.accountId === "string" ? { accountId: e.accountId } : {},
|
|
172
|
+
quota: "quota" in e ? e.quota : null,
|
|
173
|
+
killed: typeof e.killed === "boolean" ? e.killed : !1,
|
|
174
|
+
enabled: typeof e.enabled === "boolean" ? e.enabled : !0,
|
|
175
|
+
...resetCreditsField(e.resetCredits)
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
function routingAccountsOf(state) {
|
|
179
|
+
return [...state.fallbacks, ...state.vaultAccounts ?? []];
|
|
180
|
+
}
|
|
181
|
+
export async function getSidebarState(stateFile = getSidebarStateFile()) {
|
|
182
|
+
try {
|
|
183
|
+
const raw = await readFile(stateFile, "utf8");
|
|
184
|
+
return normalizeSidebarState(JSON.parse(raw));
|
|
185
|
+
} catch {
|
|
186
|
+
return DEFAULT_SIDEBAR_STATE;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export const ACTIVE_ROUTING_MAX_AGE_MS = 3600000, ACTIVE_ROUTING_MAX_ENTRIES = 128;
|
|
190
|
+
export function isUsableRoutingEntry(entry, accounts, now = Date.now()) {
|
|
191
|
+
if (!(entry.updatedAt >= now - ACTIVE_ROUTING_MAX_AGE_MS && entry.updatedAt <= now))
|
|
192
|
+
return !1;
|
|
193
|
+
if (accounts === void 0)
|
|
194
|
+
return !0;
|
|
195
|
+
return entry.activeId === "main" || accounts.some((account) => account.enabled !== !1 && account.killed !== !0 && account.id === entry.activeId);
|
|
196
|
+
}
|
|
197
|
+
export function spendControlExhaustedResetAt(quota, now = Date.now()) {
|
|
198
|
+
const spendControl = quota?.spendControl;
|
|
199
|
+
if (spendControl?.reached !== !0 || typeof spendControl.resetsAt !== "string")
|
|
200
|
+
return;
|
|
201
|
+
const resetAtMs = Date.parse(spendControl.resetsAt);
|
|
202
|
+
if (!Number.isFinite(resetAtMs) || resetAtMs <= now)
|
|
203
|
+
return;
|
|
204
|
+
return { resetsAt: spendControl.resetsAt, resetAtMs };
|
|
205
|
+
}
|
|
206
|
+
export function exhaustedQuotaResetAt(quota, now = Date.now()) {
|
|
207
|
+
let earliest;
|
|
208
|
+
for (const key of QUOTA_WINDOW_KEYS) {
|
|
209
|
+
const window = quota?.[key];
|
|
210
|
+
if (typeof window?.resetsAt !== "string" || !Number.isFinite(window.usedPercent) || window.usedPercent < 100)
|
|
211
|
+
continue;
|
|
212
|
+
const resetAtMs = Date.parse(window.resetsAt);
|
|
213
|
+
if (!Number.isFinite(resetAtMs) || resetAtMs <= now)
|
|
214
|
+
continue;
|
|
215
|
+
if (!earliest || resetAtMs < earliest.resetAtMs)
|
|
216
|
+
earliest = { resetsAt: window.resetsAt, resetAtMs };
|
|
217
|
+
}
|
|
218
|
+
const spendReset = spendControlExhaustedResetAt(quota, now);
|
|
219
|
+
if (spendReset && (!earliest || spendReset.resetAtMs < earliest.resetAtMs))
|
|
220
|
+
earliest = spendReset;
|
|
221
|
+
return earliest;
|
|
222
|
+
}
|
|
223
|
+
export function isQuotaExhausted(quota, now = Date.now()) {
|
|
224
|
+
return exhaustedQuotaResetAt(quota, now) !== void 0;
|
|
225
|
+
}
|
|
226
|
+
export function resolveSessionStickyAccount(state, sessionId, now = Date.now()) {
|
|
227
|
+
if (!sessionId || state.route !== "sticky-balanced")
|
|
228
|
+
return;
|
|
229
|
+
const assignment = state.stickyAssignments?.[hashSidebarSessionId(sessionId)];
|
|
230
|
+
if (!assignment || assignment.lastSeenAt < now - STICKY_ASSIGNMENT_MAX_AGE_MS)
|
|
231
|
+
return;
|
|
232
|
+
if (assignment.accountId === "main")
|
|
233
|
+
return state.main?.killed || isQuotaExhausted(state.main?.quota, now) ? void 0 : "main";
|
|
234
|
+
const fallback = routingAccountsOf(state).find((account) => account.id === assignment.accountId);
|
|
235
|
+
if (!fallback || fallback.enabled === !1 || fallback.killed === !0 || isQuotaExhausted(fallback.quota, now))
|
|
236
|
+
return;
|
|
237
|
+
return fallback.id;
|
|
238
|
+
}
|
|
239
|
+
export function resolveSessionSidebarRouting(state, sessionId, now = Date.now()) {
|
|
240
|
+
if (!sessionId)
|
|
241
|
+
return { activeId: state.activeId ?? "main", route: state.route };
|
|
242
|
+
const own = sessionId ? state.activeRouting?.[sessionId] : void 0, accounts = routingAccountsOf(state), ownQuota = own?.activeId === "main" ? state.main.quota : accounts.find((account) => account.id === own?.activeId)?.quota;
|
|
243
|
+
if (own && isUsableRoutingEntry(own, accounts, now) && !isQuotaExhausted(ownQuota, now))
|
|
244
|
+
return { activeId: own.activeId, route: own.route };
|
|
245
|
+
const stickyAccountId = resolveSessionStickyAccount(state, sessionId, now);
|
|
246
|
+
if (stickyAccountId)
|
|
247
|
+
return { activeId: stickyAccountId, route: state.route };
|
|
248
|
+
const vaultMode = state.vaultAccounts !== void 0, enabledFallbacks = (vaultMode ? state.vaultAccounts ?? [] : state.fallbacks).filter((account) => account.enabled && !account.killed), fallback = enabledFallbacks.find((account) => !isQuotaExhausted(account.quota, now)) ?? enabledFallbacks[0];
|
|
249
|
+
return {
|
|
250
|
+
activeId: (vaultMode || state.route === "fallback-first") && fallback ? fallback.id : "main",
|
|
251
|
+
route: state.route
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function pruneActiveRouting(activeRouting, accounts, now = Date.now(), removedSessionId) {
|
|
255
|
+
if (!activeRouting)
|
|
256
|
+
return;
|
|
257
|
+
const kept = Object.entries(activeRouting).filter(([sessionId, entry]) => sessionId !== removedSessionId && isUsableRoutingEntry(entry, accounts, now)), bounded = kept.length <= ACTIVE_ROUTING_MAX_ENTRIES ? kept : kept.sort((left, right) => right[1].updatedAt - left[1].updatedAt).slice(0, ACTIVE_ROUTING_MAX_ENTRIES);
|
|
258
|
+
return bounded.length > 0 ? Object.fromEntries(bounded) : void 0;
|
|
259
|
+
}
|
|
260
|
+
export function pruneStickyAssignments(assignments, validAccountIds, now = Date.now(), removedSessionHash) {
|
|
261
|
+
if (!assignments)
|
|
262
|
+
return;
|
|
263
|
+
let accountNotInRoster = 0, expired = 0, explicitRemoval = 0;
|
|
264
|
+
const kept = [];
|
|
265
|
+
for (const [sessionHash, assignment] of Object.entries(assignments)) {
|
|
266
|
+
if (sessionHash === removedSessionHash) {
|
|
267
|
+
explicitRemoval += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (assignment.lastSeenAt < now - STICKY_ASSIGNMENT_MAX_AGE_MS) {
|
|
271
|
+
expired += 1;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (validAccountIds !== void 0 && !validAccountIds.has(assignment.accountId)) {
|
|
275
|
+
accountNotInRoster += 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
kept.push([sessionHash, assignment]);
|
|
279
|
+
}
|
|
280
|
+
const removed = accountNotInRoster + expired + explicitRemoval;
|
|
281
|
+
if (removed > 0)
|
|
282
|
+
logSb.debug("pruned sticky assignments", {
|
|
283
|
+
pid: process.pid,
|
|
284
|
+
removed,
|
|
285
|
+
reasons: {
|
|
286
|
+
"account-not-in-roster": accountNotInRoster,
|
|
287
|
+
expired,
|
|
288
|
+
"explicit-removal": explicitRemoval
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
return kept.length > 0 ? Object.fromEntries(kept) : void 0;
|
|
292
|
+
}
|
|
293
|
+
function limitStickyAssignments(assignments, protectedSessionHash) {
|
|
294
|
+
const overflow = Object.keys(assignments).length - STICKY_ASSIGNMENT_MAX_ENTRIES;
|
|
295
|
+
if (overflow <= 0)
|
|
296
|
+
return assignments;
|
|
297
|
+
const evicted = new Set(Object.entries(assignments).filter(([sessionHash]) => sessionHash !== protectedSessionHash).sort(([leftHash, left], [rightHash, right]) => left.lastSeenAt - right.lastSeenAt || leftHash.localeCompare(rightHash)).slice(0, overflow).map(([sessionHash]) => sessionHash));
|
|
298
|
+
return Object.fromEntries(Object.entries(assignments).filter(([sessionHash]) => !evicted.has(sessionHash)));
|
|
299
|
+
}
|
|
300
|
+
function stickyAssignmentsEqual(left, right) {
|
|
301
|
+
if (left === right)
|
|
302
|
+
return !0;
|
|
303
|
+
if (!left || !right)
|
|
304
|
+
return !1;
|
|
305
|
+
const leftEntries = Object.entries(left);
|
|
306
|
+
if (leftEntries.length !== Object.keys(right).length)
|
|
307
|
+
return !1;
|
|
308
|
+
return leftEntries.every(([sessionHash, assignment]) => Object.is(right[sessionHash], assignment));
|
|
309
|
+
}
|
|
310
|
+
function isValidStickyAssignment(assignment, validPinnedAccountIds, excludedAccountIds, now) {
|
|
311
|
+
return assignment !== void 0 && validPinnedAccountIds.has(assignment.accountId) && !excludedAccountIds.has(assignment.accountId) && assignment.lastSeenAt >= now - STICKY_ASSIGNMENT_MAX_AGE_MS;
|
|
312
|
+
}
|
|
313
|
+
function stickyAssignmentNeedsMetadataUpdate(assignment, requestBytes, now, wireAccountId) {
|
|
314
|
+
return requestBytes > assignment.inputBytes || now - assignment.lastSeenAt >= STICKY_ASSIGNMENT_LAST_SEEN_TOUCH_MS || assignment.wireAccountId === void 0 && wireAccountId !== void 0;
|
|
315
|
+
}
|
|
316
|
+
function hasStickyIdentityMismatch(assignment, wireAccountId) {
|
|
317
|
+
return typeof assignment.wireAccountId === "string" && typeof wireAccountId === "string" && assignment.wireAccountId !== wireAccountId;
|
|
318
|
+
}
|
|
319
|
+
function readonlyPendingBytes(pendingBytes) {
|
|
320
|
+
const snapshot = new Map(pendingBytes), view = {
|
|
321
|
+
get size() {
|
|
322
|
+
return snapshot.size;
|
|
323
|
+
},
|
|
324
|
+
has: (key) => snapshot.has(key),
|
|
325
|
+
get: (key) => snapshot.get(key),
|
|
326
|
+
entries: () => snapshot.entries(),
|
|
327
|
+
keys: () => snapshot.keys(),
|
|
328
|
+
values: () => snapshot.values(),
|
|
329
|
+
forEach: (callback, thisArg) => {
|
|
330
|
+
snapshot.forEach((value, key) => {
|
|
331
|
+
callback.call(thisArg, value, key, view);
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
[Symbol.iterator]: () => snapshot[Symbol.iterator]()
|
|
335
|
+
};
|
|
336
|
+
return Object.freeze(view);
|
|
337
|
+
}
|
|
338
|
+
function pendingBytesForAssignments(assignments, quotaCheckedAtByAccount, excludedSessionHash) {
|
|
339
|
+
const pendingBytes = new Map;
|
|
340
|
+
for (const [sessionHash, assignment] of Object.entries(assignments ?? {})) {
|
|
341
|
+
if (sessionHash === excludedSessionHash)
|
|
342
|
+
continue;
|
|
343
|
+
if (assignment.quotaCheckedAt !== quotaCheckedAtByAccount[assignment.accountId])
|
|
344
|
+
continue;
|
|
345
|
+
pendingBytes.set(assignment.accountId, (pendingBytes.get(assignment.accountId) ?? 0) + assignment.inputBytes);
|
|
346
|
+
}
|
|
347
|
+
return readonlyPendingBytes(pendingBytes);
|
|
348
|
+
}
|
|
349
|
+
function usableRoutingAccountIds(accounts) {
|
|
350
|
+
if (accounts === void 0)
|
|
351
|
+
return;
|
|
352
|
+
return new Set([
|
|
353
|
+
"main",
|
|
354
|
+
...accounts.filter((account) => account.enabled !== !1 && account.killed !== !0).map((account) => account.id)
|
|
355
|
+
]);
|
|
356
|
+
}
|
|
357
|
+
let sidebarWriteChain = Promise.resolve();
|
|
358
|
+
function enqueueSidebarWrite(operation) {
|
|
359
|
+
const result = sidebarWriteChain.then(operation);
|
|
360
|
+
sidebarWriteChain = result.catch(() => {});
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
const sidebarFiles = new Map;
|
|
364
|
+
function sidebarFileFor(file) {
|
|
365
|
+
let sidebarFile = sidebarFiles.get(file);
|
|
366
|
+
if (!sidebarFile) {
|
|
367
|
+
sidebarFile = createSidebarFile({
|
|
368
|
+
path: file,
|
|
369
|
+
defaultValue: DEFAULT_SIDEBAR_STATE,
|
|
370
|
+
normalize: normalizeSidebarState,
|
|
371
|
+
secureDir: file === defaultStateFile(),
|
|
372
|
+
logger: logSb
|
|
373
|
+
});
|
|
374
|
+
sidebarFiles.set(file, sidebarFile);
|
|
375
|
+
}
|
|
376
|
+
return sidebarFile;
|
|
377
|
+
}
|
|
378
|
+
const failingSidebarFiles = new Set;
|
|
379
|
+
async function logWriteFailure(file, write) {
|
|
380
|
+
try {
|
|
381
|
+
await write;
|
|
382
|
+
} catch (e) {
|
|
383
|
+
const fields = {
|
|
384
|
+
pid: process.pid,
|
|
385
|
+
error: e instanceof Error ? e.message : String(e)
|
|
386
|
+
};
|
|
387
|
+
if (failingSidebarFiles.has(file))
|
|
388
|
+
logSb.debug("sidebar write failed again", fields);
|
|
389
|
+
else {
|
|
390
|
+
failingSidebarFiles.add(file);
|
|
391
|
+
logSb.warn("sidebar write failed", fields);
|
|
392
|
+
}
|
|
393
|
+
throw e;
|
|
394
|
+
}
|
|
395
|
+
if (failingSidebarFiles.delete(file))
|
|
396
|
+
logSb.info("sidebar write recovered", { pid: process.pid });
|
|
397
|
+
}
|
|
398
|
+
export function setSidebarState(state, file = getSidebarStateFile()) {
|
|
399
|
+
return enqueueSidebarWrite(() => logWriteFailure(file, sidebarFileFor(file).write(normalizeSidebarState(state))));
|
|
400
|
+
}
|
|
401
|
+
function readSidebarState(file) {
|
|
402
|
+
return sidebarFileFor(file).read();
|
|
403
|
+
}
|
|
404
|
+
function writeMergedSidebarState(file, merge, hooks) {
|
|
405
|
+
const refused = Symbol("sidebar authority changed");
|
|
406
|
+
return logWriteFailure(file, sidebarFileFor(file).update((latest) => {
|
|
407
|
+
if (hooks?.canWrite && !hooks.canWrite())
|
|
408
|
+
return;
|
|
409
|
+
const next = merge(latest);
|
|
410
|
+
return next === void 0 ? void 0 : normalizeSidebarState(next);
|
|
411
|
+
}, {
|
|
412
|
+
beforeRecheck: async (...args) => {
|
|
413
|
+
await hooks?.beforeRecheck?.(...args);
|
|
414
|
+
if (hooks?.canWrite && !hooks.canWrite())
|
|
415
|
+
throw refused;
|
|
416
|
+
}
|
|
417
|
+
}).catch((error) => {
|
|
418
|
+
if (error !== refused)
|
|
419
|
+
throw error;
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
function latestQuotaCheckedAt(quota) {
|
|
423
|
+
let latest;
|
|
424
|
+
for (const checkedAt of [
|
|
425
|
+
quota?.primary?.checkedAt,
|
|
426
|
+
quota?.secondary?.checkedAt,
|
|
427
|
+
quota?.checkedAt
|
|
428
|
+
])
|
|
429
|
+
if (typeof checkedAt === "number" && Number.isFinite(checkedAt))
|
|
430
|
+
latest = latest === void 0 ? checkedAt : Math.max(latest, checkedAt);
|
|
431
|
+
return latest;
|
|
432
|
+
}
|
|
433
|
+
function freshestQuota(incoming, existing) {
|
|
434
|
+
const incomingCheckedAt = latestQuotaCheckedAt(incoming), existingCheckedAt = latestQuotaCheckedAt(existing);
|
|
435
|
+
if (existingCheckedAt !== void 0 && (incomingCheckedAt === void 0 || existingCheckedAt > incomingCheckedAt))
|
|
436
|
+
return existing;
|
|
437
|
+
return incoming;
|
|
438
|
+
}
|
|
439
|
+
function sameAccountIdentity(incoming, existing) {
|
|
440
|
+
return incoming !== void 0 && existing !== void 0 && incoming === existing;
|
|
441
|
+
}
|
|
442
|
+
function finiteWindowCheckedAt(window, fallback) {
|
|
443
|
+
const checkedAt = window?.checkedAt;
|
|
444
|
+
if (typeof checkedAt === "number" && Number.isFinite(checkedAt))
|
|
445
|
+
return checkedAt;
|
|
446
|
+
if (typeof fallback === "number" && Number.isFinite(fallback))
|
|
447
|
+
return fallback;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
function freshestWindow(incoming, existing, existingSnapshotIsFresher, incomingSnapshotCheckedAt, existingSnapshotCheckedAt) {
|
|
451
|
+
if (incoming && existing) {
|
|
452
|
+
const incomingAt = finiteWindowCheckedAt(incoming, incomingSnapshotCheckedAt), existingAt = finiteWindowCheckedAt(existing, existingSnapshotCheckedAt);
|
|
453
|
+
if (existingAt !== void 0 && (incomingAt === void 0 || existingAt > incomingAt))
|
|
454
|
+
return existing;
|
|
455
|
+
return incoming;
|
|
456
|
+
}
|
|
457
|
+
return existingSnapshotIsFresher ? existing : incoming;
|
|
458
|
+
}
|
|
459
|
+
function mergeSpendControl(fresher, older) {
|
|
460
|
+
if (fresher.spendControl !== void 0)
|
|
461
|
+
return fresher.spendControl;
|
|
462
|
+
if (fresher.spendControlCleared === !0)
|
|
463
|
+
return;
|
|
464
|
+
return older.spendControl;
|
|
465
|
+
}
|
|
466
|
+
function mergeQuotaByWindow(incoming, existing) {
|
|
467
|
+
if (!incoming)
|
|
468
|
+
return existing;
|
|
469
|
+
if (!existing)
|
|
470
|
+
return incoming;
|
|
471
|
+
const incomingAt = latestQuotaCheckedAt(incoming), existingAt = latestQuotaCheckedAt(existing), existingSnapshotIsFresher = existingAt !== void 0 && (incomingAt === void 0 || existingAt > incomingAt), primary = freshestWindow(incoming.primary, existing.primary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), secondary = freshestWindow(incoming.secondary, existing.secondary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), spendControl = existingSnapshotIsFresher ? mergeSpendControl(existing, incoming) : mergeSpendControl(incoming, existing), spendControlCleared = spendControl === void 0 && (incoming.spendControlCleared === !0 || existing.spendControlCleared === !0);
|
|
472
|
+
let checkedAt;
|
|
473
|
+
for (const stamp of [
|
|
474
|
+
finiteWindowCheckedAt(primary),
|
|
475
|
+
finiteWindowCheckedAt(secondary)
|
|
476
|
+
])
|
|
477
|
+
if (stamp !== void 0)
|
|
478
|
+
checkedAt = checkedAt === void 0 ? stamp : Math.max(checkedAt, stamp);
|
|
479
|
+
const {
|
|
480
|
+
spendControl: _incomingSpendControl,
|
|
481
|
+
spendControlCleared: _incomingCleared,
|
|
482
|
+
...incomingRest
|
|
483
|
+
} = incoming;
|
|
484
|
+
return {
|
|
485
|
+
...incomingRest,
|
|
486
|
+
primary,
|
|
487
|
+
secondary,
|
|
488
|
+
...spendControl !== void 0 ? { spendControl } : {},
|
|
489
|
+
...spendControlCleared ? { spendControlCleared: !0 } : {},
|
|
490
|
+
checkedAt: checkedAt ?? incoming.checkedAt
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
export function setSidebarMachineState(machineState, file = getSidebarStateFile(), hooks) {
|
|
494
|
+
return enqueueSidebarWrite(async () => {
|
|
495
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
496
|
+
const latestFallbacks = new Map(latest.fallbacks.map((account) => [account.id, account])), mainSameIdentity = sameAccountIdentity(machineState.main.mainAccountId, latest.main.mainAccountId), mergedMainQuota = mainSameIdentity ? mergeQuotaByWindow(machineState.main.quota, latest.main.quota) : freshestQuota(machineState.main.quota, latest.main.quota), now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(routingAccountsOf(machineState)), now);
|
|
497
|
+
return {
|
|
498
|
+
...latest,
|
|
499
|
+
...machineState,
|
|
500
|
+
main: {
|
|
501
|
+
...machineState.main,
|
|
502
|
+
quota: mergedMainQuota,
|
|
503
|
+
mainAccountId: mainSameIdentity ? machineState.main.mainAccountId : mergedMainQuota === latest.main.quota && mergedMainQuota !== machineState.main.quota ? latest.main.mainAccountId : machineState.main.mainAccountId
|
|
504
|
+
},
|
|
505
|
+
fallbacks: machineState.fallbacks.map((account) => {
|
|
506
|
+
const existing = latestFallbacks.get(account.id), fallbackSameIdentity = sameAccountIdentity(account.accountId, existing?.accountId), mergedQuota = fallbackSameIdentity ? mergeQuotaByWindow(account.quota, existing?.quota ?? null) : freshestQuota(account.quota, existing?.quota ?? null);
|
|
507
|
+
return {
|
|
508
|
+
...account,
|
|
509
|
+
quota: mergedQuota,
|
|
510
|
+
accountId: fallbackSameIdentity ? account.accountId : mergedQuota === existing?.quota && mergedQuota !== account.quota ? existing?.accountId : account.accountId
|
|
511
|
+
};
|
|
512
|
+
}),
|
|
513
|
+
activeId: latest.activeId,
|
|
514
|
+
activeRouting: latest.activeRouting,
|
|
515
|
+
stickyAssignments,
|
|
516
|
+
lastUpdated: Math.max(now, latest.lastUpdated + 1),
|
|
517
|
+
accountPool: machineState.accountPool === !0 ? !0 : void 0,
|
|
518
|
+
vaultAccounts: machineState.vaultAccounts
|
|
519
|
+
};
|
|
520
|
+
}, hooks);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
export function upsertSidebarActiveRouting(input, accounts, file = getSidebarStateFile(), hooks) {
|
|
524
|
+
return enqueueSidebarWrite(async () => {
|
|
525
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
526
|
+
const activeRouting = pruneActiveRouting({
|
|
527
|
+
...latest.activeRouting,
|
|
528
|
+
[input.sessionId]: {
|
|
529
|
+
activeId: input.activeId,
|
|
530
|
+
route: input.route,
|
|
531
|
+
updatedAt: input.updatedAt
|
|
532
|
+
}
|
|
533
|
+
}, accounts, Date.now()), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(accounts), Date.now());
|
|
534
|
+
return {
|
|
535
|
+
...latest,
|
|
536
|
+
activeId: input.activeId,
|
|
537
|
+
route: input.route,
|
|
538
|
+
activeRouting,
|
|
539
|
+
stickyAssignments,
|
|
540
|
+
lastUpdated: Math.max(Date.now(), latest.lastUpdated + 1)
|
|
541
|
+
};
|
|
542
|
+
}, hooks);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
export function setSidebarLegacyRouting(input, file = getSidebarStateFile(), hooks) {
|
|
546
|
+
return enqueueSidebarWrite(async () => {
|
|
547
|
+
await writeMergedSidebarState(file, (latest) => ({
|
|
548
|
+
...latest,
|
|
549
|
+
activeId: input.activeId,
|
|
550
|
+
route: input.route,
|
|
551
|
+
lastUpdated: Math.max(Date.now(), latest.lastUpdated + 1)
|
|
552
|
+
}), hooks);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
export function removeSidebarActiveRouting(sessionId, accounts, file = getSidebarStateFile(), hooks) {
|
|
556
|
+
return enqueueSidebarWrite(async () => {
|
|
557
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
558
|
+
const now = Date.now(), activeRouting = pruneActiveRouting(latest.activeRouting, accounts, now, sessionId), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(accounts), now, hashSidebarSessionId(sessionId));
|
|
559
|
+
return {
|
|
560
|
+
...latest,
|
|
561
|
+
activeRouting,
|
|
562
|
+
stickyAssignments,
|
|
563
|
+
lastUpdated: Math.max(Date.now(), latest.lastUpdated + 1)
|
|
564
|
+
};
|
|
565
|
+
}, hooks);
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function stickyAssignmentIsCurrent(existing, input) {
|
|
569
|
+
return isValidStickyAssignment(existing, new Set(input.validPinnedAccountIds), new Set(input.excludeAccountIds), input.now) && !hasStickyIdentityMismatch(existing, input.wireAccountIdByAccount?.[existing.accountId]) && !stickyAssignmentNeedsMetadataUpdate(existing, input.requestBytes, input.now, input.wireAccountIdByAccount?.[existing.accountId]);
|
|
570
|
+
}
|
|
571
|
+
export function planSidebarStickyAssignment(latest, input) {
|
|
572
|
+
const sessionHash = hashSidebarSessionId(input.sessionId), validPinnedAccountIds = new Set(input.validPinnedAccountIds), excludedAccountIds = new Set(input.excludeAccountIds), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, validPinnedAccountIds, input.now), assignmentsPruned = !stickyAssignmentsEqual(latest.stickyAssignments, stickyAssignments), current = stickyAssignments?.[sessionHash], currentIdentityMismatch = current !== void 0 && hasStickyIdentityMismatch(current, input.wireAccountIdByAccount?.[current.accountId]);
|
|
573
|
+
if (isValidStickyAssignment(current, validPinnedAccountIds, excludedAccountIds, input.now) && !currentIdentityMismatch) {
|
|
574
|
+
const metadataNeedsUpdate = stickyAssignmentNeedsMetadataUpdate(current, input.requestBytes, input.now, input.wireAccountIdByAccount?.[current.accountId]), assignment = metadataNeedsUpdate ? {
|
|
575
|
+
...current,
|
|
576
|
+
inputBytes: Math.max(current.inputBytes, input.requestBytes),
|
|
577
|
+
...current.wireAccountId === void 0 && input.wireAccountIdByAccount?.[current.accountId] !== void 0 ? {
|
|
578
|
+
wireAccountId: input.wireAccountIdByAccount?.[current.accountId]
|
|
579
|
+
} : {},
|
|
580
|
+
...input.now - current.lastSeenAt >= STICKY_ASSIGNMENT_LAST_SEEN_TOUCH_MS ? { lastSeenAt: input.now } : {}
|
|
581
|
+
} : current;
|
|
582
|
+
if (!assignmentsPruned && !metadataNeedsUpdate)
|
|
583
|
+
return { assignment, next: void 0 };
|
|
584
|
+
return {
|
|
585
|
+
assignment,
|
|
586
|
+
next: {
|
|
587
|
+
...latest,
|
|
588
|
+
stickyAssignments: {
|
|
589
|
+
...stickyAssignments,
|
|
590
|
+
[sessionHash]: assignment
|
|
591
|
+
},
|
|
592
|
+
lastUpdated: Math.max(input.now, latest.lastUpdated + 1)
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const choice = input.choose(pendingBytesForAssignments(stickyAssignments, input.quotaCheckedAtByAccount, currentIdentityMismatch ? sessionHash : void 0));
|
|
597
|
+
if (!choice) {
|
|
598
|
+
if (!assignmentsPruned)
|
|
599
|
+
return { assignment: void 0, next: void 0 };
|
|
600
|
+
return {
|
|
601
|
+
assignment: void 0,
|
|
602
|
+
next: {
|
|
603
|
+
...latest,
|
|
604
|
+
stickyAssignments,
|
|
605
|
+
lastUpdated: Math.max(input.now, latest.lastUpdated + 1)
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const assignment = {
|
|
610
|
+
accountId: choice.accountId,
|
|
611
|
+
assignedAt: input.now,
|
|
612
|
+
lastSeenAt: input.now,
|
|
613
|
+
inputBytes: input.requestBytes,
|
|
614
|
+
...choice.quotaCheckedAt === void 0 ? {} : { quotaCheckedAt: choice.quotaCheckedAt },
|
|
615
|
+
...input.wireAccountIdByAccount?.[choice.accountId] === void 0 ? {} : {
|
|
616
|
+
wireAccountId: input.wireAccountIdByAccount?.[choice.accountId]
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
return {
|
|
620
|
+
assignment,
|
|
621
|
+
next: {
|
|
622
|
+
...latest,
|
|
623
|
+
stickyAssignments: limitStickyAssignments({
|
|
624
|
+
...stickyAssignments,
|
|
625
|
+
[sessionHash]: assignment
|
|
626
|
+
}, sessionHash),
|
|
627
|
+
lastUpdated: Math.max(input.now, latest.lastUpdated + 1)
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
export async function resolveSidebarStickyAssignment(input, file = getSidebarStateFile(), hooks) {
|
|
632
|
+
const sessionHash = hashSidebarSessionId(input.sessionId), existing = (await readSidebarState(file)).stickyAssignments?.[sessionHash];
|
|
633
|
+
if (stickyAssignmentIsCurrent(existing, input))
|
|
634
|
+
return existing;
|
|
635
|
+
let resolved;
|
|
636
|
+
await enqueueSidebarWrite(async () => {
|
|
637
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
638
|
+
const plan = planSidebarStickyAssignment(latest, input);
|
|
639
|
+
resolved = plan.assignment;
|
|
640
|
+
return plan.next;
|
|
641
|
+
}, hooks);
|
|
642
|
+
});
|
|
643
|
+
return resolved;
|
|
644
|
+
}
|
|
645
|
+
export function planSidebarStickyAssignmentFromSnapshot(state, input) {
|
|
646
|
+
const existing = state.stickyAssignments?.[hashSidebarSessionId(input.sessionId)];
|
|
647
|
+
if (stickyAssignmentIsCurrent(existing, input))
|
|
648
|
+
return { assignment: existing, next: void 0 };
|
|
649
|
+
return planSidebarStickyAssignment(state, input);
|
|
650
|
+
}
|
|
651
|
+
export function persistSidebarStickyAssignment(input, file = getSidebarStateFile()) {
|
|
652
|
+
const sessionHash = hashSidebarSessionId(input.sessionId), validPinnedAccountIds = new Set(input.validPinnedAccountIds);
|
|
653
|
+
return enqueueSidebarWrite(async () => {
|
|
654
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
655
|
+
const now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, validPinnedAccountIds, now), current = stickyAssignments?.[sessionHash];
|
|
656
|
+
if (current && current.assignedAt > input.assignment.assignedAt) {
|
|
657
|
+
if (stickyAssignmentsEqual(latest.stickyAssignments, stickyAssignments))
|
|
658
|
+
return;
|
|
659
|
+
return {
|
|
660
|
+
...latest,
|
|
661
|
+
stickyAssignments,
|
|
662
|
+
lastUpdated: Math.max(now, latest.lastUpdated + 1)
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const assignment = current !== void 0 && current.accountId === input.assignment.accountId && current.assignedAt === input.assignment.assignedAt ? {
|
|
666
|
+
...input.assignment,
|
|
667
|
+
inputBytes: Math.max(current.inputBytes, input.assignment.inputBytes),
|
|
668
|
+
lastSeenAt: Math.max(current.lastSeenAt, input.assignment.lastSeenAt)
|
|
669
|
+
} : input.assignment;
|
|
670
|
+
return {
|
|
671
|
+
...latest,
|
|
672
|
+
stickyAssignments: limitStickyAssignments({ ...stickyAssignments, [sessionHash]: assignment }, sessionHash),
|
|
673
|
+
lastUpdated: Math.max(now, latest.lastUpdated + 1)
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
export async function clearSidebarStickyAssignment(sessionId, file = getSidebarStateFile()) {
|
|
679
|
+
const sessionHash = hashSidebarSessionId(sessionId);
|
|
680
|
+
let removed = !1;
|
|
681
|
+
await enqueueSidebarWrite(async () => {
|
|
682
|
+
await writeMergedSidebarState(file, (latest) => {
|
|
683
|
+
const assignments = latest.stickyAssignments;
|
|
684
|
+
if (assignments?.[sessionHash] === void 0) {
|
|
685
|
+
removed = !1;
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
removed = !0;
|
|
689
|
+
const { [sessionHash]: _removed, ...remaining } = assignments;
|
|
690
|
+
return {
|
|
691
|
+
...latest,
|
|
692
|
+
...Object.keys(remaining).length > 0 ? { stickyAssignments: remaining } : { stickyAssignments: void 0 },
|
|
693
|
+
lastUpdated: Math.max(Date.now(), latest.lastUpdated + 1)
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
return removed;
|
|
698
|
+
}
|
|
699
|
+
export function drainSidebarWrites() {
|
|
700
|
+
return sidebarWriteChain;
|
|
701
|
+
}
|
|
702
|
+
export const HOT_PATH_READ_BUDGET_MS = 500;
|
|
703
|
+
export function settleWithinBudget(read, budgetMs, stale) {
|
|
704
|
+
return new Promise((resolve, reject) => {
|
|
705
|
+
const timer = setTimeout(() => resolve(stale()), budgetMs);
|
|
706
|
+
timer.unref?.();
|
|
707
|
+
read.then((value) => {
|
|
708
|
+
clearTimeout(timer);
|
|
709
|
+
resolve(value);
|
|
710
|
+
}, (error) => {
|
|
711
|
+
clearTimeout(timer);
|
|
712
|
+
reject(error);
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
export function createSidebarStateCache(file, options = {}) {
|
|
717
|
+
const readBudgetMs = options.readBudgetMs ?? HOT_PATH_READ_BUDGET_MS;
|
|
718
|
+
let snapshot, inflight;
|
|
719
|
+
const statKey = () => {
|
|
720
|
+
try {
|
|
721
|
+
const stat = statSync(file);
|
|
722
|
+
return `${stat.ino}:${stat.size}:${stat.mtimeMs}`;
|
|
723
|
+
} catch {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
}, startRead = (key) => {
|
|
727
|
+
if (inflight && inflight.key === key)
|
|
728
|
+
return inflight.promise;
|
|
729
|
+
const readAt = Date.now(), promise = getSidebarState(file).then((state) => {
|
|
730
|
+
if (!snapshot || snapshot.readAt <= readAt)
|
|
731
|
+
snapshot = { state, readAt, key };
|
|
732
|
+
return { state, readAt };
|
|
733
|
+
}), entry = { key, promise };
|
|
734
|
+
inflight = entry;
|
|
735
|
+
const clear = () => {
|
|
736
|
+
if (inflight === entry)
|
|
737
|
+
inflight = void 0;
|
|
738
|
+
};
|
|
739
|
+
promise.then(clear, clear);
|
|
740
|
+
return promise;
|
|
741
|
+
};
|
|
742
|
+
return {
|
|
743
|
+
get() {
|
|
744
|
+
const key = statKey();
|
|
745
|
+
if (snapshot && snapshot.key === key)
|
|
746
|
+
return Promise.resolve({
|
|
747
|
+
state: snapshot.state,
|
|
748
|
+
readAt: snapshot.readAt
|
|
749
|
+
});
|
|
750
|
+
return settleWithinBudget(startRead(key), readBudgetMs, () => snapshot ? { state: snapshot.state, readAt: snapshot.readAt } : { state: DEFAULT_SIDEBAR_STATE, readAt: 0 });
|
|
751
|
+
},
|
|
752
|
+
read() {
|
|
753
|
+
return startRead(statKey());
|
|
754
|
+
},
|
|
755
|
+
refreshInBackground() {
|
|
756
|
+
const key = statKey();
|
|
757
|
+
if (snapshot?.key === key)
|
|
758
|
+
return;
|
|
759
|
+
startRead(key);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
export function rememberStickyPin(overlay, sessionHash, assignment) {
|
|
764
|
+
const entry = { assignment };
|
|
765
|
+
overlay.delete(sessionHash);
|
|
766
|
+
overlay.set(sessionHash, entry);
|
|
767
|
+
while (overlay.size > STICKY_ASSIGNMENT_MAX_ENTRIES) {
|
|
768
|
+
const oldest = overlay.keys().next().value;
|
|
769
|
+
if (oldest === void 0)
|
|
770
|
+
break;
|
|
771
|
+
overlay.delete(oldest);
|
|
772
|
+
}
|
|
773
|
+
return entry;
|
|
774
|
+
}
|
|
775
|
+
export function applyStickyPinOverlay(snapshot, overlay) {
|
|
776
|
+
if (overlay.size === 0)
|
|
777
|
+
return snapshot.state;
|
|
778
|
+
const assignments = {
|
|
779
|
+
...snapshot.state.stickyAssignments
|
|
780
|
+
};
|
|
781
|
+
let applied = !1;
|
|
782
|
+
for (const [sessionHash, entry] of overlay) {
|
|
783
|
+
if (entry.persistedAt !== void 0 && snapshot.readAt > entry.persistedAt) {
|
|
784
|
+
overlay.delete(sessionHash);
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
assignments[sessionHash] = entry.assignment;
|
|
788
|
+
applied = !0;
|
|
789
|
+
}
|
|
790
|
+
if (!applied)
|
|
791
|
+
return snapshot.state;
|
|
792
|
+
return { ...snapshot.state, stickyAssignments: assignments };
|
|
793
|
+
}
|
|
794
|
+
export function createSidebarBookkeepingQueue(options) {
|
|
795
|
+
const baseDelayMs = options.retryDelayMs ?? 500, maxDelayMs = options.maxRetryDelayMs ?? 30000, generation = new Map, retries = new Map, failing = new Set;
|
|
796
|
+
let sequence = 0, delayMs = baseDelayMs, timer, stopped = !1;
|
|
797
|
+
const schedule = () => {
|
|
798
|
+
if (timer || stopped || retries.size === 0)
|
|
799
|
+
return;
|
|
800
|
+
timer = setTimeout(() => {
|
|
801
|
+
timer = void 0;
|
|
802
|
+
const due = [...retries];
|
|
803
|
+
retries.clear();
|
|
804
|
+
delayMs = Math.min(delayMs * 2, maxDelayMs);
|
|
805
|
+
for (const [key, retry] of due) {
|
|
806
|
+
const current = generation.get(key);
|
|
807
|
+
if (current !== void 0)
|
|
808
|
+
attempt(key, retry.label, retry.write, current);
|
|
809
|
+
}
|
|
810
|
+
}, delayMs);
|
|
811
|
+
timer.unref?.();
|
|
812
|
+
}, attempt = (key, label, write, ownGeneration) => {
|
|
813
|
+
let running;
|
|
814
|
+
try {
|
|
815
|
+
running = write();
|
|
816
|
+
} catch (error) {
|
|
817
|
+
running = Promise.reject(error);
|
|
818
|
+
}
|
|
819
|
+
running.then(() => {
|
|
820
|
+
if (generation.get(key) !== ownGeneration)
|
|
821
|
+
return;
|
|
822
|
+
generation.delete(key);
|
|
823
|
+
if (failing.delete(key))
|
|
824
|
+
options.logger.info("sidebar bookkeeping write recovered", {
|
|
825
|
+
pid: process.pid,
|
|
826
|
+
write: label
|
|
827
|
+
});
|
|
828
|
+
if (failing.size === 0)
|
|
829
|
+
delayMs = baseDelayMs;
|
|
830
|
+
options.onWritten?.();
|
|
831
|
+
}, (error) => {
|
|
832
|
+
if (generation.get(key) !== ownGeneration)
|
|
833
|
+
return;
|
|
834
|
+
const fields = {
|
|
835
|
+
pid: process.pid,
|
|
836
|
+
write: label,
|
|
837
|
+
error: error instanceof Error ? error.message : String(error)
|
|
838
|
+
};
|
|
839
|
+
if (stopped) {
|
|
840
|
+
generation.delete(key);
|
|
841
|
+
options.logger.debug("sidebar bookkeeping write dropped", fields);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (failing.has(key))
|
|
845
|
+
options.logger.debug("sidebar bookkeeping write failed again", fields);
|
|
846
|
+
else {
|
|
847
|
+
failing.add(key);
|
|
848
|
+
options.logger.warn("sidebar bookkeeping write failed; retrying in the background", fields);
|
|
849
|
+
}
|
|
850
|
+
retries.set(key, { label, write });
|
|
851
|
+
schedule();
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
return {
|
|
855
|
+
enqueue(key, label, write) {
|
|
856
|
+
sequence += 1;
|
|
857
|
+
generation.set(key, sequence);
|
|
858
|
+
retries.delete(key);
|
|
859
|
+
attempt(key, label, write, sequence);
|
|
860
|
+
},
|
|
861
|
+
cancel(key) {
|
|
862
|
+
generation.delete(key);
|
|
863
|
+
retries.delete(key);
|
|
864
|
+
failing.delete(key);
|
|
865
|
+
},
|
|
866
|
+
stop() {
|
|
867
|
+
stopped = !0;
|
|
868
|
+
if (timer)
|
|
869
|
+
clearTimeout(timer);
|
|
870
|
+
timer = void 0;
|
|
871
|
+
retries.clear();
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
export function resolveActiveAccount(state) {
|
|
876
|
+
const activeId = state.activeId;
|
|
877
|
+
if (state.vaultAccounts !== void 0) {
|
|
878
|
+
const enabled = state.vaultAccounts.filter((account) => account.enabled), vaultAccount = enabled.find((account) => account.id === activeId) ?? enabled[0];
|
|
879
|
+
return vaultAccount ? {
|
|
880
|
+
id: vaultAccount.id,
|
|
881
|
+
name: vaultAccount.label ?? vaultAccount.id,
|
|
882
|
+
quota: vaultAccount.quota,
|
|
883
|
+
killed: vaultAccount.killed
|
|
884
|
+
} : { id: "vault", name: "vault", quota: null, killed: !1 };
|
|
885
|
+
}
|
|
886
|
+
if (activeId && activeId !== "main") {
|
|
887
|
+
const fallback = state.fallbacks.find((account) => account.enabled && account.id === activeId);
|
|
888
|
+
if (fallback)
|
|
889
|
+
return {
|
|
890
|
+
id: fallback.id,
|
|
891
|
+
name: fallback.label ?? fallback.id,
|
|
892
|
+
quota: fallback.quota,
|
|
893
|
+
killed: fallback.killed
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
id: "main",
|
|
898
|
+
name: "main",
|
|
899
|
+
quota: state.main.quota,
|
|
900
|
+
killed: state.main.killed
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
export function sidebarQuotaProjection(quota, now = Date.now()) {
|
|
904
|
+
if (!quota)
|
|
905
|
+
return;
|
|
906
|
+
return projectQuota({
|
|
907
|
+
limits: QUOTA_WINDOW_KEYS.flatMap((label) => {
|
|
908
|
+
const window = quota[label];
|
|
909
|
+
return window ? [
|
|
910
|
+
{
|
|
911
|
+
kind: "reading",
|
|
912
|
+
scope: "all",
|
|
913
|
+
label,
|
|
914
|
+
usedPercent: window.usedPercent,
|
|
915
|
+
checkedAt: window.checkedAt ?? quota.checkedAt ?? now,
|
|
916
|
+
windowMinutes: window.windowMinutes,
|
|
917
|
+
resetsAt: window.resetsAt
|
|
918
|
+
}
|
|
919
|
+
] : [];
|
|
920
|
+
}),
|
|
921
|
+
...quota.spendControl ? {
|
|
922
|
+
budget: {
|
|
923
|
+
...quota.spendControl,
|
|
924
|
+
kind: "reading",
|
|
925
|
+
checkedAt: quota.checkedAt ?? now
|
|
926
|
+
}
|
|
927
|
+
} : {}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
export function getCollapsedQuotaSummary(quota, now = Date.now()) {
|
|
931
|
+
const primaryUsedPercent = quota?.primary?.usedPercent ?? null, secondaryUsedPercent = quota?.secondary?.usedPercent ?? null, projection = sidebarQuotaProjection(quota, now);
|
|
932
|
+
return {
|
|
933
|
+
primaryUsedPercent,
|
|
934
|
+
secondaryUsedPercent,
|
|
935
|
+
text: !projection || projection.limits.length === 0 && !projection.budget ? null : formatQuota(projection, { now, form: "compact" })
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
const PACING_MIN_ELAPSED_MS = 300000, PACING_MIN_ELAPSED_FRACTION = 0.01, ON_PACE_DELTA = 1;
|
|
939
|
+
export function computeQuotaPacing(window, windowMs, now) {
|
|
940
|
+
if (!window.resetsAt)
|
|
941
|
+
return null;
|
|
942
|
+
const resetsAt = new Date(window.resetsAt).getTime();
|
|
943
|
+
if (!Number.isFinite(resetsAt))
|
|
944
|
+
return null;
|
|
945
|
+
const start = resetsAt - windowMs, elapsed = now - start;
|
|
946
|
+
if (elapsed < PACING_MIN_ELAPSED_MS)
|
|
947
|
+
return null;
|
|
948
|
+
if (elapsed < windowMs * PACING_MIN_ELAPSED_FRACTION)
|
|
949
|
+
return null;
|
|
950
|
+
if (elapsed >= windowMs)
|
|
951
|
+
return null;
|
|
952
|
+
const used = window.usedPercent, pacePercent = Math.min(Math.max(elapsed / windowMs * 100, 0), 100), deltaPercent = used - pacePercent, state = Math.abs(deltaPercent) < ON_PACE_DELTA ? "on-pace" : deltaPercent > 0 ? "deficit" : "reserve";
|
|
953
|
+
let runsOutAt = null;
|
|
954
|
+
if (used > 0) {
|
|
955
|
+
const msToFull = elapsed * 100 / used, runOut = start + msToFull;
|
|
956
|
+
if (runOut < resetsAt)
|
|
957
|
+
runsOutAt = new Date(runOut).toISOString();
|
|
958
|
+
}
|
|
959
|
+
return { pacePercent, deltaPercent, state, runsOutAt };
|
|
960
|
+
}
|