@rulvar/core 1.27.0 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -5
- package/dist/index.js +101 -20
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2465,11 +2465,16 @@ declare const DEFAULT_RETRY_POLICY: RetryPolicy;
|
|
|
2465
2465
|
*/
|
|
2466
2466
|
declare function retryClassOf(error: WireError): RetryClass | undefined;
|
|
2467
2467
|
/**
|
|
2468
|
-
* The delay before retry number `retryIndex` (
|
|
2469
|
-
* the first failed attempt has index 0). A provider
|
|
2470
|
-
* retryAfterMs
|
|
2471
|
-
*
|
|
2472
|
-
*
|
|
2468
|
+
* The delay before retry number `retryIndex` (zero based: the delay
|
|
2469
|
+
* after the first failed attempt has index 0). A VALID provider
|
|
2470
|
+
* supplied retryAfterMs (finite and nonnegative) REPLACES the
|
|
2471
|
+
* computed delay (Appendix A); anything else (NaN, Infinity, a
|
|
2472
|
+
* negative) is ignored as adapter noise and the policy backoff
|
|
2473
|
+
* applies, so this boundary stays defensive against custom adapters
|
|
2474
|
+
* (v1.28.0 review P2). Jitter is equal jitter: half the backoff is
|
|
2475
|
+
* deterministic, half random, so a jittered delay never collapses to
|
|
2476
|
+
* zero. The result is always a finite nonnegative integer clamped to
|
|
2477
|
+
* the Node timer maximum (2147483647 ms).
|
|
2473
2478
|
*/
|
|
2474
2479
|
declare function retryDelayMs(policy: RetryPolicy, retryIndex: number, retryAfterMs?: number, random?: () => number): number;
|
|
2475
2480
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -7033,18 +7033,35 @@ function retryClassOf(error) {
|
|
|
7033
7033
|
return "transport";
|
|
7034
7034
|
}
|
|
7035
7035
|
/**
|
|
7036
|
-
* The delay
|
|
7037
|
-
*
|
|
7038
|
-
*
|
|
7039
|
-
*
|
|
7040
|
-
|
|
7036
|
+
* The largest delay a Node timer represents exactly (2^31 above that
|
|
7037
|
+
* a timer overflows and fires almost immediately); every returned
|
|
7038
|
+
* delay is clamped to it so a huge provider value can never turn
|
|
7039
|
+
* into an instant retry storm.
|
|
7040
|
+
*/
|
|
7041
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
7042
|
+
/** Bounds a delay to a finite nonnegative integer a Node timer can honor. */
|
|
7043
|
+
function timerSafe(ms) {
|
|
7044
|
+
if (!Number.isFinite(ms) || ms <= 0) return 0;
|
|
7045
|
+
return Math.min(Math.round(ms), MAX_TIMER_DELAY_MS);
|
|
7046
|
+
}
|
|
7047
|
+
/**
|
|
7048
|
+
* The delay before retry number `retryIndex` (zero based: the delay
|
|
7049
|
+
* after the first failed attempt has index 0). A VALID provider
|
|
7050
|
+
* supplied retryAfterMs (finite and nonnegative) REPLACES the
|
|
7051
|
+
* computed delay (Appendix A); anything else (NaN, Infinity, a
|
|
7052
|
+
* negative) is ignored as adapter noise and the policy backoff
|
|
7053
|
+
* applies, so this boundary stays defensive against custom adapters
|
|
7054
|
+
* (v1.28.0 review P2). Jitter is equal jitter: half the backoff is
|
|
7055
|
+
* deterministic, half random, so a jittered delay never collapses to
|
|
7056
|
+
* zero. The result is always a finite nonnegative integer clamped to
|
|
7057
|
+
* the Node timer maximum (2147483647 ms).
|
|
7041
7058
|
*/
|
|
7042
7059
|
function retryDelayMs(policy, retryIndex, retryAfterMs, random = nativeRandom) {
|
|
7043
|
-
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
7060
|
+
if (retryAfterMs !== void 0 && Number.isFinite(retryAfterMs) && retryAfterMs >= 0) return timerSafe(retryAfterMs);
|
|
7044
7061
|
const { initialMs, factor, maxMs, jitter } = policy.backoff;
|
|
7045
7062
|
const base = Math.min(maxMs, initialMs * factor ** retryIndex);
|
|
7046
|
-
if (jitter !== true) return base;
|
|
7047
|
-
return base / 2 + random() * (base / 2);
|
|
7063
|
+
if (jitter !== true) return timerSafe(base);
|
|
7064
|
+
return timerSafe(base / 2 + random() * (base / 2));
|
|
7048
7065
|
}
|
|
7049
7066
|
//#endregion
|
|
7050
7067
|
//#region src/model/roles.ts
|
|
@@ -8026,10 +8043,6 @@ async function streamTurn(adapter, req, options) {
|
|
|
8026
8043
|
break;
|
|
8027
8044
|
}
|
|
8028
8045
|
case "usage": {
|
|
8029
|
-
if (sawFinish) {
|
|
8030
|
-
usageViolation ??= "a usage event arrived after the finish event";
|
|
8031
|
-
break;
|
|
8032
|
-
}
|
|
8033
8046
|
const cleaned = {};
|
|
8034
8047
|
for (const field of [
|
|
8035
8048
|
"inputTokens",
|
|
@@ -8062,10 +8075,6 @@ async function streamTurn(adapter, req, options) {
|
|
|
8062
8075
|
break;
|
|
8063
8076
|
}
|
|
8064
8077
|
case "finish":
|
|
8065
|
-
if (sawFinish) {
|
|
8066
|
-
usageViolation ??= "a second finish event arrived on one stream";
|
|
8067
|
-
break;
|
|
8068
|
-
}
|
|
8069
8078
|
sawFinish = true;
|
|
8070
8079
|
finish = event.finish;
|
|
8071
8080
|
usage = event.usage;
|
|
@@ -8075,7 +8084,7 @@ async function streamTurn(adapter, req, options) {
|
|
|
8075
8084
|
wireError = event.error;
|
|
8076
8085
|
break;
|
|
8077
8086
|
}
|
|
8078
|
-
if (wireError !== void 0) break;
|
|
8087
|
+
if (sawFinish || wireError !== void 0) break;
|
|
8079
8088
|
}
|
|
8080
8089
|
} catch (thrown) {
|
|
8081
8090
|
if (!combined.aborted) wireError = {
|
|
@@ -8100,6 +8109,12 @@ async function streamTurn(adapter, req, options) {
|
|
|
8100
8109
|
if (usageViolation !== void 0) outcome.usageViolation = usageViolation;
|
|
8101
8110
|
return outcome;
|
|
8102
8111
|
}
|
|
8112
|
+
if (!sawFinish && wireError === void 0) wireError = {
|
|
8113
|
+
code: "agent",
|
|
8114
|
+
message: `adapter '${adapter.id}' stream ended without a terminal finish or error event; the adapter contract requires exactly one per stream, so the partial turn is discarded as a retryable transport fault`,
|
|
8115
|
+
retryable: true,
|
|
8116
|
+
data: { kind: "transport" }
|
|
8117
|
+
};
|
|
8103
8118
|
const outcome = {
|
|
8104
8119
|
turn,
|
|
8105
8120
|
usage,
|
|
@@ -8625,14 +8640,70 @@ async function runAgent(options) {
|
|
|
8625
8640
|
};
|
|
8626
8641
|
const retryPolicy = options.retry?.policy ?? DEFAULT_RETRY_POLICY;
|
|
8627
8642
|
const retryOn = retryPolicy.retryOn ?? DEFAULT_RETRY_POLICY.retryOn ?? [];
|
|
8628
|
-
const
|
|
8643
|
+
const injectedSleep = options.retry?.sleep;
|
|
8629
8644
|
const retryRandom = options.retry?.random ?? Math.random;
|
|
8645
|
+
const abortKind = () => options.budget?.signal?.aborted === true ? "budget" : options.signal?.aborted === true ? "external" : void 0;
|
|
8646
|
+
const abortedOutcome = (aborted) => ({
|
|
8647
|
+
turn: {
|
|
8648
|
+
text: "",
|
|
8649
|
+
toolCalls: []
|
|
8650
|
+
},
|
|
8651
|
+
usage: ZERO_USAGE$1,
|
|
8652
|
+
reported: ZERO_USAGE$1,
|
|
8653
|
+
usageApprox: true,
|
|
8654
|
+
aborted
|
|
8655
|
+
});
|
|
8656
|
+
const backoffWait = async (ms) => {
|
|
8657
|
+
const signals = [];
|
|
8658
|
+
if (options.signal !== void 0) signals.push(options.signal);
|
|
8659
|
+
if (options.budget?.signal !== void 0) signals.push(options.budget.signal);
|
|
8660
|
+
const combined = signals.length === 0 ? void 0 : AbortSignal.any(signals);
|
|
8661
|
+
if (combined?.aborted === true) return;
|
|
8662
|
+
if (injectedSleep === void 0) {
|
|
8663
|
+
await new Promise((resolve) => {
|
|
8664
|
+
let unhook = () => {};
|
|
8665
|
+
const timer = setTimeout(() => {
|
|
8666
|
+
unhook();
|
|
8667
|
+
resolve();
|
|
8668
|
+
}, ms);
|
|
8669
|
+
if (combined !== void 0) {
|
|
8670
|
+
const onAbort = () => {
|
|
8671
|
+
clearTimeout(timer);
|
|
8672
|
+
resolve();
|
|
8673
|
+
};
|
|
8674
|
+
combined.addEventListener("abort", onAbort, { once: true });
|
|
8675
|
+
unhook = () => combined.removeEventListener("abort", onAbort);
|
|
8676
|
+
}
|
|
8677
|
+
});
|
|
8678
|
+
return;
|
|
8679
|
+
}
|
|
8680
|
+
const sleep = Promise.resolve(injectedSleep(ms));
|
|
8681
|
+
if (combined === void 0) {
|
|
8682
|
+
await sleep;
|
|
8683
|
+
return;
|
|
8684
|
+
}
|
|
8685
|
+
let unhook;
|
|
8686
|
+
const wake = new Promise((resolve) => {
|
|
8687
|
+
const onAbort = () => resolve();
|
|
8688
|
+
combined.addEventListener("abort", onAbort, { once: true });
|
|
8689
|
+
unhook = () => combined.removeEventListener("abort", onAbort);
|
|
8690
|
+
});
|
|
8691
|
+
try {
|
|
8692
|
+
await Promise.race([sleep, wake]);
|
|
8693
|
+
} finally {
|
|
8694
|
+
unhook?.();
|
|
8695
|
+
sleep.catch(() => void 0);
|
|
8696
|
+
}
|
|
8697
|
+
};
|
|
8630
8698
|
const dispatchPhase = async (site) => {
|
|
8631
8699
|
for (;;) {
|
|
8632
8700
|
const target = site.chain[site.cursor.index] ?? site.chain[0];
|
|
8633
8701
|
let tries = 0;
|
|
8634
8702
|
inner: for (;;) {
|
|
8635
|
-
const dispatch = () =>
|
|
8703
|
+
const dispatch = () => {
|
|
8704
|
+
const aborted = abortKind();
|
|
8705
|
+
return aborted === void 0 ? streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target)) : Promise.resolve(abortedOutcome(aborted));
|
|
8706
|
+
};
|
|
8636
8707
|
const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch));
|
|
8637
8708
|
recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role, outcome.usageViolation);
|
|
8638
8709
|
tries += 1;
|
|
@@ -8643,6 +8714,11 @@ async function runAgent(options) {
|
|
|
8643
8714
|
};
|
|
8644
8715
|
usageApprox = usageApprox || outcome.usageApprox;
|
|
8645
8716
|
if (retryOn.includes(retryClass) && tries < retryPolicy.attempts) {
|
|
8717
|
+
const abortedBefore = abortKind();
|
|
8718
|
+
if (abortedBefore !== void 0) return {
|
|
8719
|
+
outcome: abortedOutcome(abortedBefore),
|
|
8720
|
+
target
|
|
8721
|
+
};
|
|
8646
8722
|
const retryAfter = (outcome.wireError?.data)?.retryAfterMs;
|
|
8647
8723
|
if (outcome.wireError !== void 0) events?.emit({
|
|
8648
8724
|
type: "agent:error",
|
|
@@ -8651,7 +8727,12 @@ async function runAgent(options) {
|
|
|
8651
8727
|
error: outcome.wireError,
|
|
8652
8728
|
willRetry: true
|
|
8653
8729
|
});
|
|
8654
|
-
await
|
|
8730
|
+
await backoffWait(retryDelayMs(retryPolicy, tries - 1, typeof retryAfter === "number" ? retryAfter : void 0, retryRandom));
|
|
8731
|
+
const abortedAfter = abortKind();
|
|
8732
|
+
if (abortedAfter !== void 0) return {
|
|
8733
|
+
outcome: abortedOutcome(abortedAfter),
|
|
8734
|
+
target
|
|
8735
|
+
};
|
|
8655
8736
|
continue inner;
|
|
8656
8737
|
}
|
|
8657
8738
|
const trigger = failoverTriggerOf(retryClass);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|