@juspay/neurolink 10.4.2 → 10.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/auth/tokenStore.d.ts +7 -0
- package/dist/auth/tokenStore.js +54 -0
- package/dist/browser/neurolink.min.js +377 -380
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +290 -63
- package/dist/cli/commands/proxyAnalyze.js +10 -1
- package/dist/lib/auth/tokenStore.d.ts +7 -0
- package/dist/lib/auth/tokenStore.js +54 -0
- package/dist/lib/proxy/proxyAnalysis.js +136 -12
- package/dist/lib/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/lib/proxy/proxyTranslationEngine.js +56 -12
- package/dist/lib/proxy/rawStreamCapture.js +47 -1
- package/dist/lib/proxy/requestLogger.d.ts +2 -0
- package/dist/lib/proxy/requestLogger.js +72 -13
- package/dist/lib/proxy/rollingProxyServer.js +79 -8
- package/dist/lib/proxy/rollingWorkerProcess.js +20 -15
- package/dist/lib/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/lib/proxy/runtimeConfig.d.ts +1 -0
- package/dist/lib/proxy/runtimeConfig.js +20 -5
- package/dist/lib/proxy/socketWorkerRuntime.js +41 -13
- package/dist/lib/proxy/sseInterceptor.js +47 -1
- package/dist/lib/proxy/tokenRefresh.d.ts +6 -0
- package/dist/lib/proxy/tokenRefresh.js +70 -15
- package/dist/lib/proxy/usageStats.d.ts +25 -3
- package/dist/lib/proxy/usageStats.js +546 -55
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +261 -297
- package/dist/lib/types/proxy.d.ts +90 -1
- package/dist/proxy/proxyAnalysis.js +136 -12
- package/dist/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/proxy/proxyTranslationEngine.js +56 -12
- package/dist/proxy/rawStreamCapture.js +47 -1
- package/dist/proxy/requestLogger.d.ts +2 -0
- package/dist/proxy/requestLogger.js +72 -13
- package/dist/proxy/rollingProxyServer.js +79 -8
- package/dist/proxy/rollingWorkerProcess.js +20 -15
- package/dist/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/proxy/runtimeConfig.d.ts +1 -0
- package/dist/proxy/runtimeConfig.js +20 -5
- package/dist/proxy/socketWorkerRuntime.js +41 -13
- package/dist/proxy/sseInterceptor.js +47 -1
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +70 -15
- package/dist/proxy/usageStats.d.ts +25 -3
- package/dist/proxy/usageStats.js +546 -55
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/server/routes/claudeProxyRoutes.js +261 -297
- package/dist/types/proxy.d.ts +90 -1
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
import { mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
|
|
10
10
|
import { basename, dirname, join } from "node:path";
|
|
11
11
|
import { AsyncMutex } from "../utils/asyncMutex.js";
|
|
12
|
+
import { redactUrlsInText, sanitizeForLog } from "../utils/logSanitize.js";
|
|
12
13
|
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
|
|
13
14
|
const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
14
15
|
const DEFAULT_FLUSH_INTERVAL_MS = 1_000;
|
|
@@ -16,6 +17,25 @@ const DEFAULT_LOCK_TIMEOUT_MS = 2_000;
|
|
|
16
17
|
const DEFAULT_STALE_LOCK_MS = 30_000;
|
|
17
18
|
const LOCK_RETRY_MS = 25;
|
|
18
19
|
const MAX_CORRUPT_SNAPSHOTS = 3;
|
|
20
|
+
const MAX_RECENT_TERMINAL_ERRORS = 20;
|
|
21
|
+
const MAX_TERMINAL_ERROR_FIELD_LENGTH = 128;
|
|
22
|
+
const MAX_TERMINAL_ERROR_MESSAGE_LENGTH = 500;
|
|
23
|
+
const TERMINAL_ERROR_ESCAPE = String.fromCharCode(27);
|
|
24
|
+
const TERMINAL_ERROR_BELL = String.fromCharCode(7);
|
|
25
|
+
const TERMINAL_ERROR_OSC_PATTERN = new RegExp(`${TERMINAL_ERROR_ESCAPE}\\][\\s\\S]*?(?:${TERMINAL_ERROR_BELL}|${TERMINAL_ERROR_ESCAPE}\\\\)`, "g");
|
|
26
|
+
const TERMINAL_ERROR_ANSI_PATTERN = new RegExp(`${TERMINAL_ERROR_ESCAPE}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
27
|
+
const TERMINAL_ERROR_CATEGORIES = [
|
|
28
|
+
"authentication",
|
|
29
|
+
"client_cancelled",
|
|
30
|
+
"fallback_exhausted",
|
|
31
|
+
"invalid_request",
|
|
32
|
+
"proxy_error",
|
|
33
|
+
"rate_limit",
|
|
34
|
+
"stream_error",
|
|
35
|
+
"unclassified",
|
|
36
|
+
"upstream_error",
|
|
37
|
+
"other",
|
|
38
|
+
];
|
|
19
39
|
class InvalidProxyStatsSnapshotError extends Error {
|
|
20
40
|
}
|
|
21
41
|
function emptyStats(startedAt) {
|
|
@@ -32,6 +52,139 @@ function emptyStats(startedAt) {
|
|
|
32
52
|
accounts: {},
|
|
33
53
|
};
|
|
34
54
|
}
|
|
55
|
+
function emptyTerminalErrorCounts() {
|
|
56
|
+
return Object.fromEntries(TERMINAL_ERROR_CATEGORIES.map((category) => [category, 0]));
|
|
57
|
+
}
|
|
58
|
+
function emptyTerminalErrorJournal(startedAt) {
|
|
59
|
+
return {
|
|
60
|
+
startedAt,
|
|
61
|
+
totalErrors: 0,
|
|
62
|
+
counts: emptyTerminalErrorCounts(),
|
|
63
|
+
recent: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function cloneTerminalErrorSummary(summary) {
|
|
67
|
+
return { ...summary };
|
|
68
|
+
}
|
|
69
|
+
function cloneTerminalErrorJournal(journal) {
|
|
70
|
+
return {
|
|
71
|
+
...journal,
|
|
72
|
+
counts: { ...journal.counts },
|
|
73
|
+
recent: journal.recent.map(cloneTerminalErrorSummary),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function mergeTerminalErrorJournals(left, right) {
|
|
77
|
+
const counts = emptyTerminalErrorCounts();
|
|
78
|
+
for (const category of TERMINAL_ERROR_CATEGORIES) {
|
|
79
|
+
counts[category] = left.counts[category] + right.counts[category];
|
|
80
|
+
}
|
|
81
|
+
const byId = new Map();
|
|
82
|
+
for (const summary of [...left.recent, ...right.recent]) {
|
|
83
|
+
byId.set(summary.id, cloneTerminalErrorSummary(summary));
|
|
84
|
+
}
|
|
85
|
+
const recent = [...byId.values()]
|
|
86
|
+
.sort((a, b) => a.at - b.at || a.id.localeCompare(b.id))
|
|
87
|
+
.slice(-MAX_RECENT_TERMINAL_ERRORS);
|
|
88
|
+
return {
|
|
89
|
+
startedAt: Math.min(left.startedAt, right.startedAt),
|
|
90
|
+
totalErrors: left.totalErrors + right.totalErrors,
|
|
91
|
+
counts,
|
|
92
|
+
recent,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function terminalErrorsPathForStats(filePath) {
|
|
96
|
+
return join(dirname(filePath), "proxy-terminal-errors.json");
|
|
97
|
+
}
|
|
98
|
+
function clipTerminalErrorField(value) {
|
|
99
|
+
const clipped = value?.slice(0, MAX_TERMINAL_ERROR_FIELD_LENGTH);
|
|
100
|
+
return clipped ? clipped : undefined;
|
|
101
|
+
}
|
|
102
|
+
function stripTerminalErrorControlCharacters(value) {
|
|
103
|
+
const withoutAnsi = value
|
|
104
|
+
.replace(TERMINAL_ERROR_OSC_PATTERN, "")
|
|
105
|
+
.replace(TERMINAL_ERROR_ANSI_PATTERN, "");
|
|
106
|
+
return Array.from(withoutAnsi, (character) => {
|
|
107
|
+
const code = character.charCodeAt(0);
|
|
108
|
+
return code < 32 || (code >= 127 && code <= 159) ? " " : character;
|
|
109
|
+
}).join("");
|
|
110
|
+
}
|
|
111
|
+
function terminalErrorCategory(status, errorType) {
|
|
112
|
+
const normalized = errorType?.toLowerCase() ?? "";
|
|
113
|
+
if (status === 499 || normalized.includes("client_cancel")) {
|
|
114
|
+
return "client_cancelled";
|
|
115
|
+
}
|
|
116
|
+
if (normalized.includes("stream_telemetry")) {
|
|
117
|
+
return "proxy_error";
|
|
118
|
+
}
|
|
119
|
+
if (normalized.includes("stream")) {
|
|
120
|
+
return "stream_error";
|
|
121
|
+
}
|
|
122
|
+
if (status === 429 || normalized.includes("rate_limit")) {
|
|
123
|
+
return "rate_limit";
|
|
124
|
+
}
|
|
125
|
+
if (normalized.includes("auth") || normalized.includes("token_refresh")) {
|
|
126
|
+
return "authentication";
|
|
127
|
+
}
|
|
128
|
+
if (status === 404 ||
|
|
129
|
+
normalized.includes("invalid_request") ||
|
|
130
|
+
normalized.includes("not_found") ||
|
|
131
|
+
normalized.includes("construction_rejection")) {
|
|
132
|
+
return "invalid_request";
|
|
133
|
+
}
|
|
134
|
+
if (normalized.includes("fallback_exhausted")) {
|
|
135
|
+
return "fallback_exhausted";
|
|
136
|
+
}
|
|
137
|
+
if (normalized.includes("proxy") ||
|
|
138
|
+
normalized.includes("handler_error") ||
|
|
139
|
+
normalized.includes("runtime")) {
|
|
140
|
+
return "proxy_error";
|
|
141
|
+
}
|
|
142
|
+
if (normalized.includes("upstream") ||
|
|
143
|
+
normalized.includes("network") ||
|
|
144
|
+
normalized.includes("transient") ||
|
|
145
|
+
normalized.includes("generation") ||
|
|
146
|
+
normalized.includes("all_accounts") ||
|
|
147
|
+
normalized === "api_error" ||
|
|
148
|
+
status >= 500) {
|
|
149
|
+
return "upstream_error";
|
|
150
|
+
}
|
|
151
|
+
return normalized ? "other" : "unclassified";
|
|
152
|
+
}
|
|
153
|
+
function createTerminalErrorSummary(args) {
|
|
154
|
+
const { now, status, accountLabel, accountType, details } = args;
|
|
155
|
+
const errorType = clipTerminalErrorField(details?.errorType);
|
|
156
|
+
const message = details?.message
|
|
157
|
+
? stripTerminalErrorControlCharacters(sanitizeForLog(redactUrlsInText(details.message), MAX_TERMINAL_ERROR_MESSAGE_LENGTH + MAX_TERMINAL_ERROR_FIELD_LENGTH))
|
|
158
|
+
.replace(/\s+/g, " ")
|
|
159
|
+
.trim()
|
|
160
|
+
.slice(0, MAX_TERMINAL_ERROR_MESSAGE_LENGTH)
|
|
161
|
+
: undefined;
|
|
162
|
+
return {
|
|
163
|
+
id: randomUUID(),
|
|
164
|
+
at: now,
|
|
165
|
+
status,
|
|
166
|
+
category: terminalErrorCategory(status, errorType),
|
|
167
|
+
...(clipTerminalErrorField(details?.requestId)
|
|
168
|
+
? { requestId: clipTerminalErrorField(details?.requestId) }
|
|
169
|
+
: {}),
|
|
170
|
+
...(clipTerminalErrorField(accountLabel)
|
|
171
|
+
? { account: clipTerminalErrorField(accountLabel) }
|
|
172
|
+
: {}),
|
|
173
|
+
...(clipTerminalErrorField(accountType)
|
|
174
|
+
? { accountType: clipTerminalErrorField(accountType) }
|
|
175
|
+
: {}),
|
|
176
|
+
...(errorType ? { errorType } : {}),
|
|
177
|
+
...(clipTerminalErrorField(details?.errorCode)
|
|
178
|
+
? { errorCode: clipTerminalErrorField(details?.errorCode) }
|
|
179
|
+
: {}),
|
|
180
|
+
...(clipTerminalErrorField(details?.terminalOutcome)
|
|
181
|
+
? {
|
|
182
|
+
terminalOutcome: clipTerminalErrorField(details?.terminalOutcome),
|
|
183
|
+
}
|
|
184
|
+
: {}),
|
|
185
|
+
...(message ? { message } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
35
188
|
function cloneAccount(account) {
|
|
36
189
|
return { ...account };
|
|
37
190
|
}
|
|
@@ -131,6 +284,50 @@ function validStats(value) {
|
|
|
131
284
|
}
|
|
132
285
|
return Object.entries(candidate.accounts).every(([label, account]) => validAccountStats(account) && label === account.label);
|
|
133
286
|
}
|
|
287
|
+
function validOptionalString(value, maxLength) {
|
|
288
|
+
return (value === undefined ||
|
|
289
|
+
(typeof value === "string" && value.length <= maxLength));
|
|
290
|
+
}
|
|
291
|
+
function validTerminalErrorSummary(value) {
|
|
292
|
+
if (!value || typeof value !== "object") {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const candidate = value;
|
|
296
|
+
return (typeof candidate.id === "string" &&
|
|
297
|
+
candidate.id.length > 0 &&
|
|
298
|
+
candidate.id.length <= MAX_TERMINAL_ERROR_FIELD_LENGTH &&
|
|
299
|
+
finiteNonNegativeInteger(candidate.at) &&
|
|
300
|
+
finiteNonNegativeInteger(candidate.status) &&
|
|
301
|
+
typeof candidate.category === "string" &&
|
|
302
|
+
TERMINAL_ERROR_CATEGORIES.includes(candidate.category) &&
|
|
303
|
+
validOptionalString(candidate.requestId, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
304
|
+
validOptionalString(candidate.account, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
305
|
+
validOptionalString(candidate.accountType, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
306
|
+
validOptionalString(candidate.errorType, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
307
|
+
validOptionalString(candidate.errorCode, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
308
|
+
validOptionalString(candidate.terminalOutcome, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
309
|
+
validOptionalString(candidate.message, MAX_TERMINAL_ERROR_MESSAGE_LENGTH));
|
|
310
|
+
}
|
|
311
|
+
function validTerminalErrorJournal(value) {
|
|
312
|
+
if (!value || typeof value !== "object") {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
const candidate = value;
|
|
316
|
+
if (!finiteNonNegativeInteger(candidate.startedAt) ||
|
|
317
|
+
!finiteNonNegativeInteger(candidate.totalErrors) ||
|
|
318
|
+
!candidate.counts ||
|
|
319
|
+
typeof candidate.counts !== "object" ||
|
|
320
|
+
!Array.isArray(candidate.recent) ||
|
|
321
|
+
candidate.recent.length > MAX_RECENT_TERMINAL_ERRORS ||
|
|
322
|
+
!candidate.recent.every(validTerminalErrorSummary)) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
const countEntries = Object.entries(candidate.counts);
|
|
326
|
+
return (countEntries.length === TERMINAL_ERROR_CATEGORIES.length &&
|
|
327
|
+
countEntries.every(([category, count]) => TERMINAL_ERROR_CATEGORIES.includes(category) && finiteNonNegativeInteger(count)) &&
|
|
328
|
+
countEntries.reduce((total, [, count]) => total + Number(count), 0) ===
|
|
329
|
+
candidate.totalErrors);
|
|
330
|
+
}
|
|
134
331
|
function validSnapshot(value) {
|
|
135
332
|
if (!value || typeof value !== "object") {
|
|
136
333
|
return false;
|
|
@@ -141,6 +338,16 @@ function validSnapshot(value) {
|
|
|
141
338
|
finiteNonNegativeInteger(candidate.updatedAt) &&
|
|
142
339
|
validStats(candidate.stats));
|
|
143
340
|
}
|
|
341
|
+
function validTerminalErrorSnapshot(value) {
|
|
342
|
+
if (!value || typeof value !== "object") {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
const candidate = value;
|
|
346
|
+
return (candidate.schemaVersion === SNAPSHOT_SCHEMA_VERSION &&
|
|
347
|
+
finiteNonNegativeInteger(candidate.revision) &&
|
|
348
|
+
finiteNonNegativeInteger(candidate.updatedAt) &&
|
|
349
|
+
validTerminalErrorJournal(candidate.journal));
|
|
350
|
+
}
|
|
144
351
|
function isMissingFileError(error) {
|
|
145
352
|
return error?.code === "ENOENT";
|
|
146
353
|
}
|
|
@@ -244,40 +451,76 @@ export class ProxyUsageStatsStore {
|
|
|
244
451
|
lockTimeoutMs;
|
|
245
452
|
staleLockMs;
|
|
246
453
|
filePath;
|
|
454
|
+
terminalErrorsFilePath;
|
|
247
455
|
stats;
|
|
248
456
|
pending;
|
|
457
|
+
terminalErrors;
|
|
458
|
+
pendingTerminalErrors;
|
|
249
459
|
pendingMutations = 0;
|
|
250
460
|
inFlightMutations = 0;
|
|
461
|
+
pendingTerminalErrorMutations = 0;
|
|
462
|
+
inFlightTerminalErrorMutations = 0;
|
|
251
463
|
revision = 0;
|
|
464
|
+
terminalErrorsRevision = 0;
|
|
252
465
|
flushTimer = null;
|
|
253
466
|
flushMutex = new AsyncMutex();
|
|
254
467
|
lastFlushedAt;
|
|
255
468
|
lastReconciledAt;
|
|
256
469
|
lastRecoveryAt;
|
|
257
470
|
lastError;
|
|
471
|
+
terminalErrorsLastFlushedAt;
|
|
472
|
+
terminalErrorsLastRecoveryAt;
|
|
473
|
+
terminalErrorsLastError;
|
|
474
|
+
snapshotVersion = 0;
|
|
258
475
|
constructor(options = {}) {
|
|
259
476
|
this.now = options.now ?? Date.now;
|
|
260
477
|
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
261
478
|
this.lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
|
|
262
479
|
this.staleLockMs = options.staleLockMs ?? DEFAULT_STALE_LOCK_MS;
|
|
263
480
|
this.filePath = options.filePath;
|
|
481
|
+
this.terminalErrorsFilePath =
|
|
482
|
+
options.terminalErrorsFilePath ??
|
|
483
|
+
(options.filePath
|
|
484
|
+
? terminalErrorsPathForStats(options.filePath)
|
|
485
|
+
: undefined);
|
|
264
486
|
const startedAt = this.now();
|
|
265
487
|
this.stats = emptyStats(startedAt);
|
|
266
488
|
this.pending = emptyStats(startedAt);
|
|
489
|
+
this.terminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
490
|
+
this.pendingTerminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
267
491
|
}
|
|
268
492
|
async initialize(filePath = this.filePath ?? "") {
|
|
269
493
|
this.cancelFlushTimer();
|
|
494
|
+
const configuredStatsPath = this.filePath;
|
|
495
|
+
const configuredTerminalErrorsPath = this.terminalErrorsFilePath;
|
|
270
496
|
this.filePath = filePath || undefined;
|
|
497
|
+
this.terminalErrorsFilePath =
|
|
498
|
+
this.filePath &&
|
|
499
|
+
configuredStatsPath === this.filePath &&
|
|
500
|
+
configuredTerminalErrorsPath
|
|
501
|
+
? configuredTerminalErrorsPath
|
|
502
|
+
: this.filePath
|
|
503
|
+
? terminalErrorsPathForStats(this.filePath)
|
|
504
|
+
: undefined;
|
|
271
505
|
const startedAt = this.now();
|
|
272
506
|
this.stats = emptyStats(startedAt);
|
|
273
507
|
this.pending = emptyStats(startedAt);
|
|
508
|
+
this.terminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
509
|
+
this.pendingTerminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
274
510
|
this.pendingMutations = 0;
|
|
275
511
|
this.inFlightMutations = 0;
|
|
512
|
+
this.pendingTerminalErrorMutations = 0;
|
|
513
|
+
this.inFlightTerminalErrorMutations = 0;
|
|
276
514
|
this.revision = 0;
|
|
515
|
+
this.terminalErrorsRevision = 0;
|
|
277
516
|
this.lastFlushedAt = undefined;
|
|
278
517
|
this.lastReconciledAt = undefined;
|
|
279
518
|
this.lastRecoveryAt = undefined;
|
|
280
519
|
this.lastError = undefined;
|
|
520
|
+
this.terminalErrorsLastFlushedAt = undefined;
|
|
521
|
+
this.terminalErrorsLastRecoveryAt = undefined;
|
|
522
|
+
this.terminalErrorsLastError = undefined;
|
|
523
|
+
this.snapshotVersion = 0;
|
|
281
524
|
if (!this.filePath) {
|
|
282
525
|
return;
|
|
283
526
|
}
|
|
@@ -308,6 +551,31 @@ export class ProxyUsageStatsStore {
|
|
|
308
551
|
this.lastError = this.describeError(error);
|
|
309
552
|
}
|
|
310
553
|
}
|
|
554
|
+
if (!this.terminalErrorsFilePath) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
const snapshot = await this.readTerminalErrorSnapshot();
|
|
559
|
+
if (snapshot) {
|
|
560
|
+
this.applyTerminalErrorSnapshot(snapshot);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
if (isCorruptSnapshotError(error)) {
|
|
565
|
+
try {
|
|
566
|
+
const recoveredSnapshot = await this.recoverCorruptTerminalErrorSnapshot();
|
|
567
|
+
if (recoveredSnapshot) {
|
|
568
|
+
this.applyTerminalErrorSnapshot(recoveredSnapshot);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
catch (recoveryError) {
|
|
572
|
+
this.terminalErrorsLastError = this.describeError(recoveryError);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
this.terminalErrorsLastError = this.describeError(error);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
311
579
|
}
|
|
312
580
|
recordAttempt(accountLabel, accountType) {
|
|
313
581
|
const attemptedAt = this.now();
|
|
@@ -326,10 +594,22 @@ export class ProxyUsageStatsStore {
|
|
|
326
594
|
this.applyAttemptError(this.pending, accountLabel, accountType, status, rateLimitKind, failedAt);
|
|
327
595
|
this.markMutation();
|
|
328
596
|
}
|
|
329
|
-
recordFinalError(
|
|
597
|
+
recordFinalError(status, accountLabel, accountType, details) {
|
|
330
598
|
const failedAt = this.now();
|
|
599
|
+
const summary = createTerminalErrorSummary({
|
|
600
|
+
now: failedAt,
|
|
601
|
+
status,
|
|
602
|
+
accountLabel,
|
|
603
|
+
accountType,
|
|
604
|
+
details,
|
|
605
|
+
});
|
|
331
606
|
this.applyFinalError(this.stats, accountLabel, accountType, failedAt);
|
|
332
607
|
this.applyFinalError(this.pending, accountLabel, accountType, failedAt);
|
|
608
|
+
this.applyTerminalError(this.terminalErrors, summary);
|
|
609
|
+
if (this.terminalErrorsFilePath) {
|
|
610
|
+
this.applyTerminalError(this.pendingTerminalErrors, summary);
|
|
611
|
+
this.pendingTerminalErrorMutations += 1;
|
|
612
|
+
}
|
|
333
613
|
this.markMutation();
|
|
334
614
|
}
|
|
335
615
|
getStats() {
|
|
@@ -339,6 +619,18 @@ export class ProxyUsageStatsStore {
|
|
|
339
619
|
const account = this.stats.accounts[label];
|
|
340
620
|
return account ? cloneAccount(account) : undefined;
|
|
341
621
|
}
|
|
622
|
+
getTerminalErrors() {
|
|
623
|
+
return cloneTerminalErrorJournal(this.terminalErrors);
|
|
624
|
+
}
|
|
625
|
+
getUsageSnapshot() {
|
|
626
|
+
const version = this.snapshotVersion;
|
|
627
|
+
return {
|
|
628
|
+
stats: this.getStats(),
|
|
629
|
+
statsVersion: version,
|
|
630
|
+
terminalErrors: this.getTerminalErrors(),
|
|
631
|
+
terminalErrorsVersion: version,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
342
634
|
getPersistenceStatus() {
|
|
343
635
|
return {
|
|
344
636
|
enabled: !!this.filePath,
|
|
@@ -351,70 +643,163 @@ export class ProxyUsageStatsStore {
|
|
|
351
643
|
lastReconciledAt: this.lastReconciledAt ?? null,
|
|
352
644
|
lastRecoveryAt: this.lastRecoveryAt ?? null,
|
|
353
645
|
lastError: this.lastError ?? null,
|
|
646
|
+
terminalErrorsFilePath: this.terminalErrorsFilePath ?? null,
|
|
647
|
+
terminalErrorsRevision: this.terminalErrorsRevision,
|
|
648
|
+
terminalErrorsPending: this.pendingTerminalErrorMutations,
|
|
649
|
+
terminalErrorsInFlight: this.inFlightTerminalErrorMutations,
|
|
650
|
+
terminalErrorsUnpersisted: this.pendingTerminalErrorMutations +
|
|
651
|
+
this.inFlightTerminalErrorMutations,
|
|
652
|
+
terminalErrorsLastFlushedAt: this.terminalErrorsLastFlushedAt ?? null,
|
|
653
|
+
terminalErrorsLastRecoveryAt: this.terminalErrorsLastRecoveryAt ?? null,
|
|
654
|
+
terminalErrorsLastError: this.terminalErrorsLastError ?? null,
|
|
354
655
|
};
|
|
355
656
|
}
|
|
356
657
|
async flush() {
|
|
357
658
|
if (!this.filePath ||
|
|
358
|
-
(this.pendingMutations === 0 &&
|
|
659
|
+
(this.pendingMutations === 0 &&
|
|
660
|
+
this.inFlightMutations === 0 &&
|
|
661
|
+
this.pendingTerminalErrorMutations === 0 &&
|
|
662
|
+
this.inFlightTerminalErrorMutations === 0)) {
|
|
359
663
|
return;
|
|
360
664
|
}
|
|
361
665
|
this.cancelFlushTimer();
|
|
362
666
|
await this.flushMutex.runExclusive(async () => {
|
|
363
|
-
if (!this.filePath
|
|
667
|
+
if (!this.filePath) {
|
|
364
668
|
return;
|
|
365
669
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
this.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
670
|
+
let statsDelta;
|
|
671
|
+
let terminalErrorDelta;
|
|
672
|
+
if (this.pendingMutations > 0) {
|
|
673
|
+
statsDelta = {
|
|
674
|
+
value: this.pending,
|
|
675
|
+
mutations: this.pendingMutations,
|
|
676
|
+
};
|
|
677
|
+
this.pending = emptyStats(this.now());
|
|
678
|
+
this.pendingMutations = 0;
|
|
679
|
+
this.inFlightMutations = statsDelta.mutations;
|
|
680
|
+
}
|
|
681
|
+
if (this.terminalErrorsFilePath &&
|
|
682
|
+
this.pendingTerminalErrorMutations > 0) {
|
|
683
|
+
terminalErrorDelta = {
|
|
684
|
+
value: this.pendingTerminalErrors,
|
|
685
|
+
mutations: this.pendingTerminalErrorMutations,
|
|
686
|
+
};
|
|
687
|
+
this.pendingTerminalErrors = emptyTerminalErrorJournal(this.now());
|
|
688
|
+
this.pendingTerminalErrorMutations = 0;
|
|
689
|
+
this.inFlightTerminalErrorMutations = terminalErrorDelta.mutations;
|
|
690
|
+
}
|
|
691
|
+
const countersPersisted = statsDelta
|
|
692
|
+
? await this.persistStatsDelta(statsDelta.value, statsDelta.mutations)
|
|
693
|
+
: true;
|
|
694
|
+
if (terminalErrorDelta) {
|
|
695
|
+
if (countersPersisted) {
|
|
696
|
+
await this.persistTerminalErrorDelta(terminalErrorDelta.value, terminalErrorDelta.mutations);
|
|
378
697
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
await this.quarantineCorruptSnapshot();
|
|
384
|
-
existing = null;
|
|
698
|
+
else {
|
|
699
|
+
this.pendingTerminalErrors = mergeTerminalErrorJournals(terminalErrorDelta.value, this.pendingTerminalErrors);
|
|
700
|
+
this.pendingTerminalErrorMutations += terminalErrorDelta.mutations;
|
|
701
|
+
this.inFlightTerminalErrorMutations = 0;
|
|
385
702
|
}
|
|
386
|
-
const persisted = existing?.stats ?? emptyStats(delta.startedAt);
|
|
387
|
-
const merged = mergeStats(persisted, delta);
|
|
388
|
-
const revision = (existing?.revision ?? 0) + 1;
|
|
389
|
-
const updatedAt = this.now();
|
|
390
|
-
await writeJsonSnapshotAtomically(this.filePath, {
|
|
391
|
-
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
392
|
-
revision,
|
|
393
|
-
updatedAt,
|
|
394
|
-
stats: merged,
|
|
395
|
-
}, 0o600);
|
|
396
|
-
this.revision = revision;
|
|
397
|
-
this.lastFlushedAt = updatedAt;
|
|
398
|
-
this.lastReconciledAt = updatedAt;
|
|
399
|
-
this.lastError = undefined;
|
|
400
|
-
this.stats = mergeStats(merged, this.pending);
|
|
401
|
-
this.inFlightMutations = 0;
|
|
402
703
|
}
|
|
403
|
-
|
|
404
|
-
this.pending = mergeStats(delta, this.pending);
|
|
405
|
-
this.pendingMutations += deltaMutations;
|
|
406
|
-
this.inFlightMutations = 0;
|
|
407
|
-
this.lastError = this.describeError(error);
|
|
704
|
+
if (this.pendingMutations > 0 || this.pendingTerminalErrorMutations > 0) {
|
|
408
705
|
this.scheduleFlush();
|
|
409
706
|
}
|
|
410
|
-
finally {
|
|
411
|
-
await releaseLock?.();
|
|
412
|
-
}
|
|
413
707
|
});
|
|
414
708
|
}
|
|
415
|
-
async
|
|
709
|
+
async persistStatsDelta(delta, deltaMutations) {
|
|
416
710
|
if (!this.filePath) {
|
|
417
|
-
return
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
let releaseLock;
|
|
714
|
+
try {
|
|
715
|
+
releaseLock = await acquireFileLock(`${this.filePath}.lock`, this.lockTimeoutMs, this.staleLockMs, this.now);
|
|
716
|
+
let existing;
|
|
717
|
+
try {
|
|
718
|
+
existing = await this.readSnapshot();
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
if (!isCorruptSnapshotError(error)) {
|
|
722
|
+
throw error;
|
|
723
|
+
}
|
|
724
|
+
await this.quarantineCorruptSnapshot();
|
|
725
|
+
existing = null;
|
|
726
|
+
}
|
|
727
|
+
const persisted = existing?.stats ?? emptyStats(delta.startedAt);
|
|
728
|
+
const merged = mergeStats(persisted, delta);
|
|
729
|
+
const revision = (existing?.revision ?? 0) + 1;
|
|
730
|
+
const updatedAt = this.now();
|
|
731
|
+
await writeJsonSnapshotAtomically(this.filePath, {
|
|
732
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
733
|
+
revision,
|
|
734
|
+
updatedAt,
|
|
735
|
+
stats: merged,
|
|
736
|
+
}, 0o600);
|
|
737
|
+
this.revision = revision;
|
|
738
|
+
this.lastFlushedAt = updatedAt;
|
|
739
|
+
this.lastReconciledAt = updatedAt;
|
|
740
|
+
this.lastError = undefined;
|
|
741
|
+
this.stats = mergeStats(merged, this.pending);
|
|
742
|
+
this.inFlightMutations = 0;
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
this.pending = mergeStats(delta, this.pending);
|
|
747
|
+
this.pendingMutations += deltaMutations;
|
|
748
|
+
this.inFlightMutations = 0;
|
|
749
|
+
this.lastError = this.describeError(error);
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
finally {
|
|
753
|
+
await releaseLock?.();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async persistTerminalErrorDelta(delta, deltaMutations) {
|
|
757
|
+
if (!this.terminalErrorsFilePath) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
let releaseLock;
|
|
761
|
+
try {
|
|
762
|
+
releaseLock = await acquireFileLock(`${this.terminalErrorsFilePath}.lock`, this.lockTimeoutMs, this.staleLockMs, this.now);
|
|
763
|
+
let existing;
|
|
764
|
+
try {
|
|
765
|
+
existing = await this.readTerminalErrorSnapshot();
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
if (!isCorruptSnapshotError(error)) {
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
await this.quarantineCorruptTerminalErrorSnapshot();
|
|
772
|
+
existing = null;
|
|
773
|
+
}
|
|
774
|
+
const persisted = existing?.journal ?? emptyTerminalErrorJournal(delta.startedAt);
|
|
775
|
+
const merged = mergeTerminalErrorJournals(persisted, delta);
|
|
776
|
+
const revision = (existing?.revision ?? 0) + 1;
|
|
777
|
+
const updatedAt = this.now();
|
|
778
|
+
await writeJsonSnapshotAtomically(this.terminalErrorsFilePath, {
|
|
779
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
780
|
+
revision,
|
|
781
|
+
updatedAt,
|
|
782
|
+
journal: merged,
|
|
783
|
+
}, 0o600);
|
|
784
|
+
this.terminalErrorsRevision = revision;
|
|
785
|
+
this.terminalErrorsLastFlushedAt = updatedAt;
|
|
786
|
+
this.terminalErrorsLastError = undefined;
|
|
787
|
+
this.terminalErrors = mergeTerminalErrorJournals(merged, this.pendingTerminalErrors);
|
|
788
|
+
this.inFlightTerminalErrorMutations = 0;
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
this.pendingTerminalErrors = mergeTerminalErrorJournals(delta, this.pendingTerminalErrors);
|
|
792
|
+
this.pendingTerminalErrorMutations += deltaMutations;
|
|
793
|
+
this.inFlightTerminalErrorMutations = 0;
|
|
794
|
+
this.terminalErrorsLastError = this.describeError(error);
|
|
795
|
+
}
|
|
796
|
+
finally {
|
|
797
|
+
await releaseLock?.();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async reconcileUsageSnapshot() {
|
|
801
|
+
if (!this.filePath) {
|
|
802
|
+
return this.getUsageSnapshot();
|
|
418
803
|
}
|
|
419
804
|
return this.flushMutex.runExclusive(async () => {
|
|
420
805
|
try {
|
|
@@ -430,30 +815,59 @@ export class ProxyUsageStatsStore {
|
|
|
430
815
|
catch (error) {
|
|
431
816
|
this.lastError = this.describeError(error);
|
|
432
817
|
}
|
|
433
|
-
|
|
818
|
+
if (this.terminalErrorsFilePath) {
|
|
819
|
+
try {
|
|
820
|
+
const snapshot = await this.readTerminalErrorSnapshot();
|
|
821
|
+
if (snapshot) {
|
|
822
|
+
this.terminalErrors = mergeTerminalErrorJournals(snapshot.journal, this.pendingTerminalErrors);
|
|
823
|
+
this.terminalErrorsRevision = snapshot.revision;
|
|
824
|
+
this.terminalErrorsLastFlushedAt = snapshot.updatedAt;
|
|
825
|
+
}
|
|
826
|
+
this.terminalErrorsLastError = undefined;
|
|
827
|
+
}
|
|
828
|
+
catch (error) {
|
|
829
|
+
this.terminalErrorsLastError = this.describeError(error);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
this.snapshotVersion += 1;
|
|
833
|
+
return this.getUsageSnapshot();
|
|
434
834
|
});
|
|
435
835
|
}
|
|
836
|
+
async reconcile() {
|
|
837
|
+
return (await this.reconcileUsageSnapshot()).stats;
|
|
838
|
+
}
|
|
436
839
|
resetMemory() {
|
|
437
840
|
this.cancelFlushTimer();
|
|
438
841
|
const startedAt = this.now();
|
|
439
842
|
this.stats = emptyStats(startedAt);
|
|
440
843
|
this.pending = emptyStats(startedAt);
|
|
844
|
+
this.terminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
845
|
+
this.pendingTerminalErrors = emptyTerminalErrorJournal(startedAt);
|
|
441
846
|
this.pendingMutations = 0;
|
|
442
847
|
this.inFlightMutations = 0;
|
|
848
|
+
this.pendingTerminalErrorMutations = 0;
|
|
849
|
+
this.inFlightTerminalErrorMutations = 0;
|
|
443
850
|
this.revision = 0;
|
|
851
|
+
this.terminalErrorsRevision = 0;
|
|
444
852
|
this.lastFlushedAt = undefined;
|
|
445
853
|
this.lastReconciledAt = undefined;
|
|
446
854
|
this.lastRecoveryAt = undefined;
|
|
447
855
|
this.lastError = undefined;
|
|
856
|
+
this.terminalErrorsLastFlushedAt = undefined;
|
|
857
|
+
this.terminalErrorsLastRecoveryAt = undefined;
|
|
858
|
+
this.terminalErrorsLastError = undefined;
|
|
859
|
+
this.snapshotVersion = 0;
|
|
448
860
|
}
|
|
449
861
|
async resetForTests() {
|
|
450
862
|
await this.flushMutex.runExclusive(async () => {
|
|
451
863
|
this.resetMemory();
|
|
452
864
|
this.filePath = undefined;
|
|
865
|
+
this.terminalErrorsFilePath = undefined;
|
|
453
866
|
});
|
|
454
867
|
}
|
|
455
868
|
markMutation() {
|
|
456
869
|
this.pendingMutations += 1;
|
|
870
|
+
this.snapshotVersion += 1;
|
|
457
871
|
this.scheduleFlush();
|
|
458
872
|
}
|
|
459
873
|
applyAttempt(target, accountLabel, accountType, attemptedAt) {
|
|
@@ -497,6 +911,14 @@ export class ProxyUsageStatsStore {
|
|
|
497
911
|
account.lastErrorAt = failedAt;
|
|
498
912
|
}
|
|
499
913
|
}
|
|
914
|
+
applyTerminalError(target, summary) {
|
|
915
|
+
target.totalErrors += 1;
|
|
916
|
+
target.counts[summary.category] += 1;
|
|
917
|
+
target.recent.push(cloneTerminalErrorSummary(summary));
|
|
918
|
+
if (target.recent.length > MAX_RECENT_TERMINAL_ERRORS) {
|
|
919
|
+
target.recent.splice(0, target.recent.length - MAX_RECENT_TERMINAL_ERRORS);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
500
922
|
ensureAccount(target, label, type) {
|
|
501
923
|
if (!target.accounts[label]) {
|
|
502
924
|
target.accounts[label] = {
|
|
@@ -553,12 +975,38 @@ export class ProxyUsageStatsStore {
|
|
|
553
975
|
}
|
|
554
976
|
return parsed;
|
|
555
977
|
}
|
|
978
|
+
async readTerminalErrorSnapshot() {
|
|
979
|
+
if (!this.terminalErrorsFilePath) {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
let raw;
|
|
983
|
+
try {
|
|
984
|
+
raw = await readFile(this.terminalErrorsFilePath, "utf8");
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
if (isMissingFileError(error)) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
throw error;
|
|
991
|
+
}
|
|
992
|
+
const parsed = JSON.parse(raw);
|
|
993
|
+
if (!validTerminalErrorSnapshot(parsed)) {
|
|
994
|
+
throw new InvalidProxyStatsSnapshotError(`Invalid proxy terminal-error snapshot ${this.terminalErrorsFilePath}`);
|
|
995
|
+
}
|
|
996
|
+
return parsed;
|
|
997
|
+
}
|
|
556
998
|
applySnapshot(snapshot) {
|
|
557
999
|
this.stats = cloneStats(snapshot.stats);
|
|
558
1000
|
this.pending = emptyStats(this.now());
|
|
559
1001
|
this.revision = snapshot.revision;
|
|
560
1002
|
this.lastFlushedAt = snapshot.updatedAt;
|
|
561
1003
|
}
|
|
1004
|
+
applyTerminalErrorSnapshot(snapshot) {
|
|
1005
|
+
this.terminalErrors = cloneTerminalErrorJournal(snapshot.journal);
|
|
1006
|
+
this.pendingTerminalErrors = emptyTerminalErrorJournal(this.now());
|
|
1007
|
+
this.terminalErrorsRevision = snapshot.revision;
|
|
1008
|
+
this.terminalErrorsLastFlushedAt = snapshot.updatedAt;
|
|
1009
|
+
}
|
|
562
1010
|
async recoverCorruptSnapshot() {
|
|
563
1011
|
if (!this.filePath) {
|
|
564
1012
|
return null;
|
|
@@ -580,6 +1028,27 @@ export class ProxyUsageStatsStore {
|
|
|
580
1028
|
await releaseLock();
|
|
581
1029
|
}
|
|
582
1030
|
}
|
|
1031
|
+
async recoverCorruptTerminalErrorSnapshot() {
|
|
1032
|
+
if (!this.terminalErrorsFilePath) {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
const releaseLock = await acquireFileLock(`${this.terminalErrorsFilePath}.lock`, this.lockTimeoutMs, this.staleLockMs, this.now);
|
|
1036
|
+
try {
|
|
1037
|
+
try {
|
|
1038
|
+
return await this.readTerminalErrorSnapshot();
|
|
1039
|
+
}
|
|
1040
|
+
catch (error) {
|
|
1041
|
+
if (!isCorruptSnapshotError(error)) {
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
await this.quarantineCorruptTerminalErrorSnapshot();
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
finally {
|
|
1049
|
+
await releaseLock();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
583
1052
|
async quarantineCorruptSnapshot() {
|
|
584
1053
|
if (!this.filePath) {
|
|
585
1054
|
return;
|
|
@@ -597,14 +1066,30 @@ export class ProxyUsageStatsStore {
|
|
|
597
1066
|
}
|
|
598
1067
|
this.lastRecoveryAt = recoveredAt;
|
|
599
1068
|
this.lastError = undefined;
|
|
600
|
-
await this.pruneCorruptSnapshots();
|
|
1069
|
+
await this.pruneCorruptSnapshots(this.filePath);
|
|
601
1070
|
}
|
|
602
|
-
async
|
|
603
|
-
if (!this.
|
|
1071
|
+
async quarantineCorruptTerminalErrorSnapshot() {
|
|
1072
|
+
if (!this.terminalErrorsFilePath) {
|
|
604
1073
|
return;
|
|
605
1074
|
}
|
|
606
|
-
const
|
|
607
|
-
const
|
|
1075
|
+
const recoveredAt = this.now();
|
|
1076
|
+
const quarantinePath = `${this.terminalErrorsFilePath}.corrupt.${recoveredAt}.${randomUUID()}`;
|
|
1077
|
+
try {
|
|
1078
|
+
await rename(this.terminalErrorsFilePath, quarantinePath);
|
|
1079
|
+
}
|
|
1080
|
+
catch (error) {
|
|
1081
|
+
if (!isMissingFileError(error)) {
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
this.terminalErrorsLastRecoveryAt = recoveredAt;
|
|
1087
|
+
this.terminalErrorsLastError = undefined;
|
|
1088
|
+
await this.pruneCorruptSnapshots(this.terminalErrorsFilePath);
|
|
1089
|
+
}
|
|
1090
|
+
async pruneCorruptSnapshots(filePath) {
|
|
1091
|
+
const directory = dirname(filePath);
|
|
1092
|
+
const prefix = `${basename(filePath)}.corrupt.`;
|
|
608
1093
|
const quarantined = (await readdir(directory))
|
|
609
1094
|
.filter((entry) => entry.startsWith(prefix))
|
|
610
1095
|
.sort()
|
|
@@ -630,8 +1115,8 @@ export function recordFinalSuccess(accountLabel, accountType) {
|
|
|
630
1115
|
export function recordAttemptError(accountLabel, accountType, status, rateLimitKind) {
|
|
631
1116
|
defaultStore.recordAttemptError(accountLabel, accountType, status, rateLimitKind);
|
|
632
1117
|
}
|
|
633
|
-
export function recordFinalError(
|
|
634
|
-
defaultStore.recordFinalError(
|
|
1118
|
+
export function recordFinalError(status, accountLabel, accountType, details) {
|
|
1119
|
+
defaultStore.recordFinalError(status, accountLabel, accountType, details);
|
|
635
1120
|
}
|
|
636
1121
|
export function getStats() {
|
|
637
1122
|
return defaultStore.getStats();
|
|
@@ -639,9 +1124,15 @@ export function getStats() {
|
|
|
639
1124
|
export async function getReconciledStats() {
|
|
640
1125
|
return defaultStore.reconcile();
|
|
641
1126
|
}
|
|
1127
|
+
export async function getReconciledUsageSnapshot() {
|
|
1128
|
+
return defaultStore.reconcileUsageSnapshot();
|
|
1129
|
+
}
|
|
642
1130
|
export function getAccountStats(label) {
|
|
643
1131
|
return defaultStore.getAccountStats(label);
|
|
644
1132
|
}
|
|
1133
|
+
export function getTerminalErrors() {
|
|
1134
|
+
return defaultStore.getTerminalErrors();
|
|
1135
|
+
}
|
|
645
1136
|
export function getUsageStatsPersistenceStatus() {
|
|
646
1137
|
return defaultStore.getPersistenceStatus();
|
|
647
1138
|
}
|