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 +47 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +45 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -142,6 +142,22 @@ interface BudgetAdvisory {
|
|
|
142
142
|
spentUsd: number;
|
|
143
143
|
/** Hosted reports the tightest cap across all scopes; local is always "local". */
|
|
144
144
|
scope: "local";
|
|
145
|
+
/**
|
|
146
|
+
* The guard's own next-call estimate (the costlier of the last LLM and last
|
|
147
|
+
* tool call — the same value the default reservation uses). 0 until the first
|
|
148
|
+
* call is recorded, so a planner can't divide by a cold estimate.
|
|
149
|
+
*
|
|
150
|
+
* Optional so adding it stays a non-breaking, additive change for any code
|
|
151
|
+
* that constructs a `BudgetAdvisory` literal; `advisory()` always sets it.
|
|
152
|
+
*/
|
|
153
|
+
expectedCost?: number;
|
|
154
|
+
/**
|
|
155
|
+
* How many more calls the remaining budget buys at expectedCost:
|
|
156
|
+
* floor(remainingUsd / expectedCost). null when expectedCost is 0 (no call
|
|
157
|
+
* recorded yet) — unknown, not zero. Optional for the same additive reason as
|
|
158
|
+
* expectedCost; `advisory()` always sets it.
|
|
159
|
+
*/
|
|
160
|
+
estCallsRemaining?: number | null;
|
|
145
161
|
}
|
|
146
162
|
declare class BudgetGuard {
|
|
147
163
|
readonly limitUsd: number;
|
|
@@ -514,4 +530,33 @@ interface BudgetGuardMiddleware {
|
|
|
514
530
|
*/
|
|
515
531
|
declare function budgetGuardMiddleware(guard: BudgetGuard): BudgetGuardMiddleware;
|
|
516
532
|
|
|
517
|
-
|
|
533
|
+
interface RetryPlan<T> {
|
|
534
|
+
/** Operation to run for this retry attempt. */
|
|
535
|
+
call: () => T | Promise<T>;
|
|
536
|
+
/**
|
|
537
|
+
* Estimated retry cost passed to `guard.check()` before the retry runs.
|
|
538
|
+
* Leave undefined to use the guard's default last-call estimate.
|
|
539
|
+
*/
|
|
540
|
+
estimatedCost?: number;
|
|
541
|
+
}
|
|
542
|
+
interface BudgetRetryOptions<T> {
|
|
543
|
+
/** Estimated cost for retrying the original call. */
|
|
544
|
+
estimatedCost?: number;
|
|
545
|
+
/** Total attempts, including the first call. Default: 2. */
|
|
546
|
+
maxAttempts?: number;
|
|
547
|
+
/** Choose a cheaper retry plan when `guard.advisory().nearLimit` is true. */
|
|
548
|
+
onDegrade?: (error: unknown, advisory: BudgetAdvisory) => RetryPlan<T> | Promise<RetryPlan<T> | undefined> | undefined;
|
|
549
|
+
/** Decide whether an error is retryable. Defaults to all non-budget errors. */
|
|
550
|
+
retryIf?: (error: unknown) => boolean;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Run `call` with budget-aware retries.
|
|
554
|
+
*
|
|
555
|
+
* The first attempt runs unchanged. If it fails with a retryable error, the
|
|
556
|
+
* helper retries as-is with ample budget, asks `onDegrade` for a cheaper plan
|
|
557
|
+
* when near the limit, and always calls `guard.check(estimatedCost)` before a
|
|
558
|
+
* retry so an over-budget retry is blocked before it runs.
|
|
559
|
+
*/
|
|
560
|
+
declare function withBudgetRetry<T>(guard: BudgetGuard, call: () => T | Promise<T>, options?: BudgetRetryOptions<T>): Promise<T>;
|
|
561
|
+
|
|
562
|
+
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.d.ts
CHANGED
|
@@ -142,6 +142,22 @@ interface BudgetAdvisory {
|
|
|
142
142
|
spentUsd: number;
|
|
143
143
|
/** Hosted reports the tightest cap across all scopes; local is always "local". */
|
|
144
144
|
scope: "local";
|
|
145
|
+
/**
|
|
146
|
+
* The guard's own next-call estimate (the costlier of the last LLM and last
|
|
147
|
+
* tool call — the same value the default reservation uses). 0 until the first
|
|
148
|
+
* call is recorded, so a planner can't divide by a cold estimate.
|
|
149
|
+
*
|
|
150
|
+
* Optional so adding it stays a non-breaking, additive change for any code
|
|
151
|
+
* that constructs a `BudgetAdvisory` literal; `advisory()` always sets it.
|
|
152
|
+
*/
|
|
153
|
+
expectedCost?: number;
|
|
154
|
+
/**
|
|
155
|
+
* How many more calls the remaining budget buys at expectedCost:
|
|
156
|
+
* floor(remainingUsd / expectedCost). null when expectedCost is 0 (no call
|
|
157
|
+
* recorded yet) — unknown, not zero. Optional for the same additive reason as
|
|
158
|
+
* expectedCost; `advisory()` always sets it.
|
|
159
|
+
*/
|
|
160
|
+
estCallsRemaining?: number | null;
|
|
145
161
|
}
|
|
146
162
|
declare class BudgetGuard {
|
|
147
163
|
readonly limitUsd: number;
|
|
@@ -514,4 +530,33 @@ interface BudgetGuardMiddleware {
|
|
|
514
530
|
*/
|
|
515
531
|
declare function budgetGuardMiddleware(guard: BudgetGuard): BudgetGuardMiddleware;
|
|
516
532
|
|
|
517
|
-
|
|
533
|
+
interface RetryPlan<T> {
|
|
534
|
+
/** Operation to run for this retry attempt. */
|
|
535
|
+
call: () => T | Promise<T>;
|
|
536
|
+
/**
|
|
537
|
+
* Estimated retry cost passed to `guard.check()` before the retry runs.
|
|
538
|
+
* Leave undefined to use the guard's default last-call estimate.
|
|
539
|
+
*/
|
|
540
|
+
estimatedCost?: number;
|
|
541
|
+
}
|
|
542
|
+
interface BudgetRetryOptions<T> {
|
|
543
|
+
/** Estimated cost for retrying the original call. */
|
|
544
|
+
estimatedCost?: number;
|
|
545
|
+
/** Total attempts, including the first call. Default: 2. */
|
|
546
|
+
maxAttempts?: number;
|
|
547
|
+
/** Choose a cheaper retry plan when `guard.advisory().nearLimit` is true. */
|
|
548
|
+
onDegrade?: (error: unknown, advisory: BudgetAdvisory) => RetryPlan<T> | Promise<RetryPlan<T> | undefined> | undefined;
|
|
549
|
+
/** Decide whether an error is retryable. Defaults to all non-budget errors. */
|
|
550
|
+
retryIf?: (error: unknown) => boolean;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Run `call` with budget-aware retries.
|
|
554
|
+
*
|
|
555
|
+
* The first attempt runs unchanged. If it fails with a retryable error, the
|
|
556
|
+
* helper retries as-is with ample budget, asks `onDegrade` for a cheaper plan
|
|
557
|
+
* when near the limit, and always calls `guard.check(estimatedCost)` before a
|
|
558
|
+
* retry so an over-budget retry is blocked before it runs.
|
|
559
|
+
*/
|
|
560
|
+
declare function withBudgetRetry<T>(guard: BudgetGuard, call: () => T | Promise<T>, options?: BudgetRetryOptions<T>): Promise<T>;
|
|
561
|
+
|
|
562
|
+
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
|
@@ -1451,6 +1451,9 @@ var BudgetGuard = class {
|
|
|
1451
1451
|
*/
|
|
1452
1452
|
advisory() {
|
|
1453
1453
|
const usedBps = this.limitUsd <= 0 ? 1e4 : Math.max(0, Math.min(1e4, Math.floor(this.spentUsd / this.limitUsd * 1e4 + 1e-9)));
|
|
1454
|
+
const remainingUsd = Math.max(0, this.limitUsd - this.spentUsd);
|
|
1455
|
+
const expectedCost = Math.max(this.lastLlmCost, this.lastToolCost);
|
|
1456
|
+
const estCallsRemaining = expectedCost > 0 ? Math.floor(remainingUsd / expectedCost + 1e-9) : null;
|
|
1454
1457
|
return {
|
|
1455
1458
|
nearLimit: usedBps >= this.nearLimitBps,
|
|
1456
1459
|
usedBps,
|
|
@@ -1458,10 +1461,12 @@ var BudgetGuard = class {
|
|
|
1458
1461
|
// in-flight reservations. Unlike the remainingUsd getter (which subtracts
|
|
1459
1462
|
// `reserved`), the advisory is a soft utilization signal about money already
|
|
1460
1463
|
// spent, while the getter reports what a new call can still claim.
|
|
1461
|
-
remainingUsd
|
|
1464
|
+
remainingUsd,
|
|
1462
1465
|
limitUsd: this.limitUsd,
|
|
1463
1466
|
spentUsd: this.spentUsd,
|
|
1464
|
-
scope: "local"
|
|
1467
|
+
scope: "local",
|
|
1468
|
+
expectedCost,
|
|
1469
|
+
estCallsRemaining
|
|
1465
1470
|
};
|
|
1466
1471
|
}
|
|
1467
1472
|
};
|
|
@@ -1624,6 +1629,42 @@ function budgetGuardMiddleware(guard) {
|
|
|
1624
1629
|
}
|
|
1625
1630
|
};
|
|
1626
1631
|
}
|
|
1632
|
+
|
|
1633
|
+
// src/retry.ts
|
|
1634
|
+
function defaultRetryIf(error) {
|
|
1635
|
+
return !(error instanceof BudgetExceeded);
|
|
1636
|
+
}
|
|
1637
|
+
async function withBudgetRetry(guard, call, options = {}) {
|
|
1638
|
+
const maxAttempts = options.maxAttempts ?? 2;
|
|
1639
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
1640
|
+
throw new RangeError(`maxAttempts must be an integer >= 1, got ${maxAttempts}`);
|
|
1641
|
+
}
|
|
1642
|
+
const retryIf = options.retryIf ?? defaultRetryIf;
|
|
1643
|
+
let plan = { call, estimatedCost: options.estimatedCost };
|
|
1644
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1645
|
+
try {
|
|
1646
|
+
return await plan.call();
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
if (attempt >= maxAttempts || !retryIf(error)) {
|
|
1649
|
+
throw error;
|
|
1650
|
+
}
|
|
1651
|
+
plan = await nextPlan(guard, error, plan, options.onDegrade);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
throw new Error("unreachable");
|
|
1655
|
+
}
|
|
1656
|
+
async function nextPlan(guard, error, current, onDegrade) {
|
|
1657
|
+
const advisory = guard.advisory();
|
|
1658
|
+
if (advisory.nearLimit && onDegrade !== void 0) {
|
|
1659
|
+
const degraded = await onDegrade(error, advisory);
|
|
1660
|
+
if (degraded !== void 0) {
|
|
1661
|
+
guard.check(degraded.estimatedCost);
|
|
1662
|
+
return degraded;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
guard.check(current.estimatedCost);
|
|
1666
|
+
return current;
|
|
1667
|
+
}
|
|
1627
1668
|
export {
|
|
1628
1669
|
BudgetExceeded,
|
|
1629
1670
|
BudgetGuard,
|
|
@@ -1634,6 +1675,7 @@ export {
|
|
|
1634
1675
|
budgetGuardMiddleware,
|
|
1635
1676
|
priceTokens,
|
|
1636
1677
|
pricing_exports as pricing,
|
|
1637
|
-
resolvePrice
|
|
1678
|
+
resolvePrice,
|
|
1679
|
+
withBudgetRetry
|
|
1638
1680
|
};
|
|
1639
1681
|
//# sourceMappingURL=index.js.map
|