@rulvar/core 1.28.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 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` (0-based: the delay after
2469
- * the first failed attempt has index 0). A provider-supplied
2470
- * retryAfterMs REPLACES the computed delay (Appendix A). Jitter is
2471
- * equal-jitter: half the backoff is deterministic, half random, so a
2472
- * jittered delay never collapses to zero.
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 before retry number `retryIndex` (0-based: the delay after
7037
- * the first failed attempt has index 0). A provider-supplied
7038
- * retryAfterMs REPLACES the computed delay (Appendix A). Jitter is
7039
- * equal-jitter: half the backoff is deterministic, half random, so a
7040
- * jittered delay never collapses to zero.
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
@@ -8623,14 +8640,70 @@ async function runAgent(options) {
8623
8640
  };
8624
8641
  const retryPolicy = options.retry?.policy ?? DEFAULT_RETRY_POLICY;
8625
8642
  const retryOn = retryPolicy.retryOn ?? DEFAULT_RETRY_POLICY.retryOn ?? [];
8626
- const retrySleep = options.retry?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
8643
+ const injectedSleep = options.retry?.sleep;
8627
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
+ };
8628
8698
  const dispatchPhase = async (site) => {
8629
8699
  for (;;) {
8630
8700
  const target = site.chain[site.cursor.index] ?? site.chain[0];
8631
8701
  let tries = 0;
8632
8702
  inner: for (;;) {
8633
- const dispatch = () => streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target));
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
+ };
8634
8707
  const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch));
8635
8708
  recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role, outcome.usageViolation);
8636
8709
  tries += 1;
@@ -8641,6 +8714,11 @@ async function runAgent(options) {
8641
8714
  };
8642
8715
  usageApprox = usageApprox || outcome.usageApprox;
8643
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
+ };
8644
8722
  const retryAfter = (outcome.wireError?.data)?.retryAfterMs;
8645
8723
  if (outcome.wireError !== void 0) events?.emit({
8646
8724
  type: "agent:error",
@@ -8649,7 +8727,12 @@ async function runAgent(options) {
8649
8727
  error: outcome.wireError,
8650
8728
  willRetry: true
8651
8729
  });
8652
- await retrySleep(retryDelayMs(retryPolicy, tries - 1, typeof retryAfter === "number" ? retryAfter : void 0, retryRandom));
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
+ };
8653
8736
  continue inner;
8654
8737
  }
8655
8738
  const trigger = failoverTriggerOf(retryClass);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.28.0",
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",