floe-guard 0.6.0 → 0.8.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.cjs CHANGED
@@ -29,7 +29,8 @@ __export(index_exports, {
29
29
  budgetGuardMiddleware: () => budgetGuardMiddleware,
30
30
  priceTokens: () => priceTokens,
31
31
  pricing: () => pricing_exports,
32
- resolvePrice: () => resolvePrice
32
+ resolvePrice: () => resolvePrice,
33
+ withBudgetRetry: () => withBudgetRetry
33
34
  });
34
35
  module.exports = __toCommonJS(index_exports);
35
36
 
@@ -1480,6 +1481,9 @@ var BudgetGuard = class {
1480
1481
  */
1481
1482
  advisory() {
1482
1483
  const usedBps = this.limitUsd <= 0 ? 1e4 : Math.max(0, Math.min(1e4, Math.floor(this.spentUsd / this.limitUsd * 1e4 + 1e-9)));
1484
+ const remainingUsd = Math.max(0, this.limitUsd - this.spentUsd);
1485
+ const expectedCost = Math.max(this.lastLlmCost, this.lastToolCost);
1486
+ const estCallsRemaining = expectedCost > 0 ? Math.floor(remainingUsd / expectedCost + 1e-9) : null;
1483
1487
  return {
1484
1488
  nearLimit: usedBps >= this.nearLimitBps,
1485
1489
  usedBps,
@@ -1487,10 +1491,12 @@ var BudgetGuard = class {
1487
1491
  // in-flight reservations. Unlike the remainingUsd getter (which subtracts
1488
1492
  // `reserved`), the advisory is a soft utilization signal about money already
1489
1493
  // spent, while the getter reports what a new call can still claim.
1490
- remainingUsd: Math.max(0, this.limitUsd - this.spentUsd),
1494
+ remainingUsd,
1491
1495
  limitUsd: this.limitUsd,
1492
1496
  spentUsd: this.spentUsd,
1493
- scope: "local"
1497
+ scope: "local",
1498
+ expectedCost,
1499
+ estCallsRemaining
1494
1500
  };
1495
1501
  }
1496
1502
  };
@@ -1653,6 +1659,42 @@ function budgetGuardMiddleware(guard) {
1653
1659
  }
1654
1660
  };
1655
1661
  }
1662
+
1663
+ // src/retry.ts
1664
+ function defaultRetryIf(error) {
1665
+ return !(error instanceof BudgetExceeded);
1666
+ }
1667
+ async function withBudgetRetry(guard, call, options = {}) {
1668
+ const maxAttempts = options.maxAttempts ?? 2;
1669
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
1670
+ throw new RangeError(`maxAttempts must be an integer >= 1, got ${maxAttempts}`);
1671
+ }
1672
+ const retryIf = options.retryIf ?? defaultRetryIf;
1673
+ let plan = { call, estimatedCost: options.estimatedCost };
1674
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1675
+ try {
1676
+ return await plan.call();
1677
+ } catch (error) {
1678
+ if (attempt >= maxAttempts || !retryIf(error)) {
1679
+ throw error;
1680
+ }
1681
+ plan = await nextPlan(guard, error, plan, options.onDegrade);
1682
+ }
1683
+ }
1684
+ throw new Error("unreachable");
1685
+ }
1686
+ async function nextPlan(guard, error, current, onDegrade) {
1687
+ const advisory = guard.advisory();
1688
+ if (advisory.nearLimit && onDegrade !== void 0) {
1689
+ const degraded = await onDegrade(error, advisory);
1690
+ if (degraded !== void 0) {
1691
+ guard.check(degraded.estimatedCost);
1692
+ return degraded;
1693
+ }
1694
+ }
1695
+ guard.check(current.estimatedCost);
1696
+ return current;
1697
+ }
1656
1698
  // Annotate the CommonJS export names for ESM import in node:
1657
1699
  0 && (module.exports = {
1658
1700
  BudgetExceeded,
@@ -1664,6 +1706,7 @@ function budgetGuardMiddleware(guard) {
1664
1706
  budgetGuardMiddleware,
1665
1707
  priceTokens,
1666
1708
  pricing,
1667
- resolvePrice
1709
+ resolvePrice,
1710
+ withBudgetRetry
1668
1711
  });
1669
1712
  //# sourceMappingURL=index.cjs.map