@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.
Files changed (204) hide show
  1. package/README.md +15 -26
  2. package/dist/index-766sfrh5.js +1 -0
  3. package/dist/index-8kj7sa1p.js +12 -0
  4. package/dist/index-8rqfbhfw.js +221 -0
  5. package/dist/index-b3q3j39x.js +2 -0
  6. package/dist/index-bmd7x5aq.js +2 -0
  7. package/dist/index-eqh53k8v.js +2 -0
  8. package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
  9. package/dist/index-jyr8y8k4.js +7 -0
  10. package/dist/index-rv08jmd4.js +2 -0
  11. package/dist/index.d.ts +578 -145
  12. package/dist/index.js +1 -110
  13. package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
  14. package/dist/port-file-Do3lQIEk.d.ts +29 -0
  15. package/dist/protocol-BOBdkqqm.d.ts +198 -0
  16. package/dist/rpc/port-file.d.ts +4 -17
  17. package/dist/rpc/port-file.js +1 -1
  18. package/dist/rpc/protocol.d.ts +2 -2
  19. package/dist/rpc/protocol.js +1 -0
  20. package/dist/rpc/rpc-client.d.ts +5 -4
  21. package/dist/rpc/rpc-client.js +1 -1
  22. package/dist/rpc/rpc-dir.d.ts +2 -0
  23. package/dist/rpc/rpc-dir.js +1 -1
  24. package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
  25. package/dist/sidebar-state.d.ts +2 -2
  26. package/dist/sidebar-state.js +1 -1
  27. package/dist/token-util-wdza526f.js +1 -0
  28. package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
  29. package/dist/tui-preferences.js +5 -2
  30. package/dist/tui.d.ts +9 -4
  31. package/dist/v2/server.d.ts +54 -0
  32. package/dist/v2/server.js +4 -0
  33. package/package.json +21 -9
  34. package/src/tui/entry.mjs +26 -25
  35. package/src/tui-compiled/raw/logger.js +24 -0
  36. package/src/tui-compiled/raw/rpc/protocol.js +3 -0
  37. package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
  38. package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
  39. package/src/tui-compiled/raw/selector.js +21 -0
  40. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
  41. package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
  42. package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
  43. package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
  44. package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
  45. package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
  46. package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
  47. package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
  48. package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
  49. package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
  50. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  51. package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
  52. package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
  53. package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
  54. package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
  55. package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
  56. package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
  57. package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
  58. package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
  59. package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
  60. package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
  61. package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
  62. package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
  63. package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
  64. package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  65. package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
  66. package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
  67. package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
  68. package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
  69. package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
  70. package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
  71. package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
  72. package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  73. package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
  74. package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
  75. package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  76. package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
  77. package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
  78. package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
  79. package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
  80. package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  81. package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
  82. package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
  83. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
  84. package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
  85. package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
  86. package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
  87. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
  88. package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
  89. package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
  90. package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
  91. package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
  92. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  93. package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
  94. package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
  95. package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  96. package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  97. package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
  98. package/src/tui-compiled/raw/sidebar-state.js +960 -0
  99. package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
  100. package/src/tui-compiled/raw/tui-preferences.js +135 -0
  101. package/src/tui-compiled/raw/tui.tsx +135 -143
  102. package/src/tui-compiled/runtime/logger.js +24 -0
  103. package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
  104. package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
  105. package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
  106. package/src/tui-compiled/runtime/selector.js +21 -0
  107. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
  108. package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
  109. package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
  110. package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
  111. package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
  112. package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
  113. package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
  114. package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
  115. package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
  116. package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
  117. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  118. package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
  119. package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
  120. package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
  121. package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
  122. package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
  123. package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
  124. package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
  125. package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
  126. package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
  127. package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
  128. package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
  129. package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
  130. package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
  131. package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  132. package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
  133. package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
  134. package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
  135. package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
  136. package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
  137. package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
  138. package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
  139. package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  140. package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
  141. package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
  142. package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  143. package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
  144. package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
  145. package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
  146. package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
  147. package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  148. package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
  149. package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
  150. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
  151. package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
  152. package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
  153. package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
  154. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
  155. package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
  156. package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
  157. package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
  158. package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
  159. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  160. package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
  161. package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
  162. package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  163. package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  164. package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
  165. package/src/tui-compiled/runtime/sidebar-state.js +960 -0
  166. package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
  167. package/src/tui-compiled/runtime/tui-preferences.js +135 -0
  168. package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
  169. package/dist/index-4effjmr6.js +0 -2
  170. package/dist/index-9ehhb051.js +0 -2
  171. package/dist/index-g3hsdjh4.js +0 -2
  172. package/dist/index-hh52v8sb.js +0 -2
  173. package/dist/index-jwn4r3sw.js +0 -1
  174. package/dist/index-sq36b120.js +0 -211
  175. package/dist/internal-BTndPmW2.d.ts +0 -914
  176. package/dist/quota-normalize-08hpp51v.js +0 -1
  177. package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
  178. package/dist/token-util-ypgz7rge.js +0 -1
  179. package/src/tui-compiled/raw/core-account-paths.ts +0 -114
  180. package/src/tui-compiled/raw/core-custody-state.ts +0 -243
  181. package/src/tui-compiled/raw/logger.ts +0 -53
  182. package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
  183. package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
  184. package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
  185. package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
  186. package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
  187. package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
  188. package/src/tui-compiled/raw/tui-preferences.ts +0 -328
  189. package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
  190. package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
  191. package/src/tui-compiled/runtime/logger.ts +0 -53
  192. package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
  193. package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
  194. package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
  195. package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
  196. package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
  197. package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
  198. package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
  199. package/src/tui-compiled/shared/internal.ts +0 -6
  200. package/src/tui-compiled/shared/logger.ts +0 -264
  201. package/src/tui-compiled/shared/paths.ts +0 -33
  202. package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
  203. package/src/tui-compiled/shared/util/error.ts +0 -9
  204. 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
+ }