sentinelayer-cli 0.34.5 → 0.34.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/session/sync.js +82 -0
package/package.json
CHANGED
package/src/session/sync.js
CHANGED
|
@@ -349,6 +349,65 @@ function recordCircuitSuccess(circuit) {
|
|
|
349
349
|
persistCircuitState();
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
function responseHeader(response, name) {
|
|
353
|
+
const headers = response?.headers;
|
|
354
|
+
if (!headers || !name) return "";
|
|
355
|
+
if (typeof headers.get === "function") {
|
|
356
|
+
return normalizeString(headers.get(name));
|
|
357
|
+
}
|
|
358
|
+
const lower = String(name).toLowerCase();
|
|
359
|
+
if (headers instanceof Map) {
|
|
360
|
+
for (const [key, value] of headers.entries()) {
|
|
361
|
+
if (String(key).toLowerCase() === lower) return normalizeString(value);
|
|
362
|
+
}
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
if (typeof headers === "object") {
|
|
366
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
367
|
+
if (String(key).toLowerCase() === lower) return normalizeString(value);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return "";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function retryAfterMsFromResponse(response, payload, nowMs = Date.now()) {
|
|
374
|
+
const retryAfterMs = [
|
|
375
|
+
payload?.retryAfterMs,
|
|
376
|
+
payload?.error?.retryAfterMs,
|
|
377
|
+
payload?.detail?.retryAfterMs,
|
|
378
|
+
].map(Number).find((value) => Number.isFinite(value) && value > 0);
|
|
379
|
+
if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
380
|
+
return Math.ceil(retryAfterMs);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const retryAfterSeconds = [
|
|
384
|
+
payload?.retryAfterSeconds,
|
|
385
|
+
payload?.error?.retryAfterSeconds,
|
|
386
|
+
payload?.detail?.retryAfterSeconds,
|
|
387
|
+
].map(Number).find((value) => Number.isFinite(value) && value > 0);
|
|
388
|
+
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
|
|
389
|
+
return Math.ceil(retryAfterSeconds * 1000);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const header = responseHeader(response, "Retry-After");
|
|
393
|
+
if (!header) return null;
|
|
394
|
+
const seconds = Number(header);
|
|
395
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
396
|
+
return Math.ceil(seconds * 1000);
|
|
397
|
+
}
|
|
398
|
+
const retryAtMs = Date.parse(header);
|
|
399
|
+
if (Number.isFinite(retryAtMs)) {
|
|
400
|
+
return Math.max(0, Math.ceil(retryAtMs - nowMs));
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function isRateLimitResponse(response, payload) {
|
|
406
|
+
if (response?.status === 429) return true;
|
|
407
|
+
const code = normalizeString(payload?.error?.code || payload?.code || payload?.detail?.code).toUpperCase();
|
|
408
|
+
return code === "RATE_LIMITED";
|
|
409
|
+
}
|
|
410
|
+
|
|
352
411
|
// Test-only helper. Resets both circuits in memory AND on disk so unit
|
|
353
412
|
// tests that exercise hydration don't leak state across runs.
|
|
354
413
|
export function __resetCircuitStateForTests(homeDir) {
|
|
@@ -1357,8 +1416,19 @@ export async function pollSessionEvents(
|
|
|
1357
1416
|
},
|
|
1358
1417
|
normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
|
|
1359
1418
|
fetchImpl,
|
|
1419
|
+
{ readErrorBody: true },
|
|
1360
1420
|
);
|
|
1361
1421
|
if (!response || !response.ok) {
|
|
1422
|
+
if (isRateLimitResponse(response, payload)) {
|
|
1423
|
+
return {
|
|
1424
|
+
ok: false,
|
|
1425
|
+
reason: "rate_limited",
|
|
1426
|
+
status: response?.status || 429,
|
|
1427
|
+
retryAfterMs: retryAfterMsFromResponse(response, payload, normalizedNowMs),
|
|
1428
|
+
events: [],
|
|
1429
|
+
cursor: normalizedSince || null,
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1362
1432
|
recordCircuitFailure(inboundCircuit, normalizedNowMs);
|
|
1363
1433
|
return {
|
|
1364
1434
|
ok: false,
|
|
@@ -1677,8 +1747,20 @@ export async function pollSessionEventsBefore(
|
|
|
1677
1747
|
},
|
|
1678
1748
|
normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
|
|
1679
1749
|
fetchImpl,
|
|
1750
|
+
{ readErrorBody: true },
|
|
1680
1751
|
);
|
|
1681
1752
|
if (!response || !response.ok) {
|
|
1753
|
+
if (isRateLimitResponse(response, payload)) {
|
|
1754
|
+
return {
|
|
1755
|
+
ok: false,
|
|
1756
|
+
reason: "rate_limited",
|
|
1757
|
+
status: response?.status || 429,
|
|
1758
|
+
retryAfterMs: retryAfterMsFromResponse(response, payload, normalizedNowMs),
|
|
1759
|
+
events: [],
|
|
1760
|
+
cursor: null,
|
|
1761
|
+
beforeSequence: Number.isFinite(normalizedBeforeSequence) ? normalizedBeforeSequence : null,
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1682
1764
|
recordCircuitFailure(inboundCircuit, normalizedNowMs);
|
|
1683
1765
|
return {
|
|
1684
1766
|
ok: false,
|