floe-guard 0.6.0 → 0.7.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
@@ -514,4 +514,33 @@ interface BudgetGuardMiddleware {
514
514
  */
515
515
  declare function budgetGuardMiddleware(guard: BudgetGuard): BudgetGuardMiddleware;
516
516
 
517
- export { type BudgetAdvisory, BudgetExceeded, BudgetGuard, type BudgetGuardMiddleware, type BudgetGuardOptions, DeadlineExceeded, FloeGuardError, type LatencyAdvisory, LatencyBudget, type LatencyBudgetOptions, type ManualPrice, type PricedModel, type SpendEvent, UnpriceableModelError, budgetGuardMiddleware, priceTokens, pricing, resolvePrice };
517
+ interface RetryPlan<T> {
518
+ /** Operation to run for this retry attempt. */
519
+ call: () => T | Promise<T>;
520
+ /**
521
+ * Estimated retry cost passed to `guard.check()` before the retry runs.
522
+ * Leave undefined to use the guard's default last-call estimate.
523
+ */
524
+ estimatedCost?: number;
525
+ }
526
+ interface BudgetRetryOptions<T> {
527
+ /** Estimated cost for retrying the original call. */
528
+ estimatedCost?: number;
529
+ /** Total attempts, including the first call. Default: 2. */
530
+ maxAttempts?: number;
531
+ /** Choose a cheaper retry plan when `guard.advisory().nearLimit` is true. */
532
+ onDegrade?: (error: unknown, advisory: BudgetAdvisory) => RetryPlan<T> | Promise<RetryPlan<T> | undefined> | undefined;
533
+ /** Decide whether an error is retryable. Defaults to all non-budget errors. */
534
+ retryIf?: (error: unknown) => boolean;
535
+ }
536
+ /**
537
+ * Run `call` with budget-aware retries.
538
+ *
539
+ * The first attempt runs unchanged. If it fails with a retryable error, the
540
+ * helper retries as-is with ample budget, asks `onDegrade` for a cheaper plan
541
+ * when near the limit, and always calls `guard.check(estimatedCost)` before a
542
+ * retry so an over-budget retry is blocked before it runs.
543
+ */
544
+ declare function withBudgetRetry<T>(guard: BudgetGuard, call: () => T | Promise<T>, options?: BudgetRetryOptions<T>): Promise<T>;
545
+
546
+ export { type BudgetAdvisory, BudgetExceeded, BudgetGuard, type BudgetGuardMiddleware, type BudgetGuardOptions, type BudgetRetryOptions, DeadlineExceeded, FloeGuardError, type LatencyAdvisory, LatencyBudget, type LatencyBudgetOptions, type ManualPrice, type PricedModel, type RetryPlan, type SpendEvent, UnpriceableModelError, budgetGuardMiddleware, priceTokens, pricing, resolvePrice, withBudgetRetry };
package/dist/index.js CHANGED
@@ -1624,6 +1624,42 @@ function budgetGuardMiddleware(guard) {
1624
1624
  }
1625
1625
  };
1626
1626
  }
1627
+
1628
+ // src/retry.ts
1629
+ function defaultRetryIf(error) {
1630
+ return !(error instanceof BudgetExceeded);
1631
+ }
1632
+ async function withBudgetRetry(guard, call, options = {}) {
1633
+ const maxAttempts = options.maxAttempts ?? 2;
1634
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
1635
+ throw new RangeError(`maxAttempts must be an integer >= 1, got ${maxAttempts}`);
1636
+ }
1637
+ const retryIf = options.retryIf ?? defaultRetryIf;
1638
+ let plan = { call, estimatedCost: options.estimatedCost };
1639
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1640
+ try {
1641
+ return await plan.call();
1642
+ } catch (error) {
1643
+ if (attempt >= maxAttempts || !retryIf(error)) {
1644
+ throw error;
1645
+ }
1646
+ plan = await nextPlan(guard, error, plan, options.onDegrade);
1647
+ }
1648
+ }
1649
+ throw new Error("unreachable");
1650
+ }
1651
+ async function nextPlan(guard, error, current, onDegrade) {
1652
+ const advisory = guard.advisory();
1653
+ if (advisory.nearLimit && onDegrade !== void 0) {
1654
+ const degraded = await onDegrade(error, advisory);
1655
+ if (degraded !== void 0) {
1656
+ guard.check(degraded.estimatedCost);
1657
+ return degraded;
1658
+ }
1659
+ }
1660
+ guard.check(current.estimatedCost);
1661
+ return current;
1662
+ }
1627
1663
  export {
1628
1664
  BudgetExceeded,
1629
1665
  BudgetGuard,
@@ -1634,6 +1670,7 @@ export {
1634
1670
  budgetGuardMiddleware,
1635
1671
  priceTokens,
1636
1672
  pricing_exports as pricing,
1637
- resolvePrice
1673
+ resolvePrice,
1674
+ withBudgetRetry
1638
1675
  };
1639
1676
  //# sourceMappingURL=index.js.map