floe-guard 0.10.0 → 0.11.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 +136 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -1
- package/dist/index.d.ts +209 -1
- package/dist/index.js +130 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27,10 +27,16 @@ __export(index_exports, {
|
|
|
27
27
|
LatencyBudget: () => LatencyBudget,
|
|
28
28
|
TokenBudgetExceeded: () => TokenBudgetExceeded,
|
|
29
29
|
UnpriceableModelError: () => UnpriceableModelError,
|
|
30
|
+
UnpriceableVoiceError: () => UnpriceableVoiceError,
|
|
30
31
|
budgetGuardMiddleware: () => budgetGuardMiddleware,
|
|
32
|
+
gates: () => gates_exports,
|
|
33
|
+
lookupVoiceRate: () => lookupVoiceRate,
|
|
31
34
|
priceTokens: () => priceTokens,
|
|
35
|
+
priceVoiceLeg: () => priceVoiceLeg,
|
|
32
36
|
pricing: () => pricing_exports,
|
|
33
37
|
resolvePrice: () => resolvePrice,
|
|
38
|
+
resolveVoiceRate: () => resolveVoiceRate,
|
|
39
|
+
voiceLegCost: () => voiceLegCost,
|
|
34
40
|
withBudgetRetry: () => withBudgetRetry
|
|
35
41
|
});
|
|
36
42
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -81,6 +87,19 @@ var UnpriceableModelError = class extends FloeGuardError {
|
|
|
81
87
|
this.model = model;
|
|
82
88
|
}
|
|
83
89
|
};
|
|
90
|
+
var UnpriceableVoiceError = class extends FloeGuardError {
|
|
91
|
+
vendor;
|
|
92
|
+
mode;
|
|
93
|
+
constructor(vendor, mode) {
|
|
94
|
+
const shown = vendor === null ? "None" : `'${vendor}'`;
|
|
95
|
+
super(
|
|
96
|
+
`Cannot price ${mode} vendor ${shown}: not in the bundled voice cost map (or its entry has the wrong unit for a ${mode} leg) and no per-unit override was given. The guard cannot enforce a budget on spend it cannot measure. Pass a per-unit rate to enable enforcement.`
|
|
97
|
+
);
|
|
98
|
+
this.name = "UnpriceableVoiceError";
|
|
99
|
+
this.vendor = vendor;
|
|
100
|
+
this.mode = mode;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
84
103
|
function roundHalfUp(ms) {
|
|
85
104
|
return Math.floor(ms + 0.5);
|
|
86
105
|
}
|
|
@@ -1239,6 +1258,12 @@ function isReservation(h) {
|
|
|
1239
1258
|
var BudgetGuard = class {
|
|
1240
1259
|
limitUsd;
|
|
1241
1260
|
spentUsd = 0;
|
|
1261
|
+
/**
|
|
1262
|
+
* Wall-clock start (ms since epoch) of this guard's spend window, for the $/min
|
|
1263
|
+
* burn rate (advisory().burnRateUsdPerMin). One guard per call/turn ⇒ a per-call
|
|
1264
|
+
* rate. Set at construction; tests set it directly to control elapsed time.
|
|
1265
|
+
*/
|
|
1266
|
+
createdAtMs = Date.now();
|
|
1242
1267
|
priceOverrides;
|
|
1243
1268
|
failClosed;
|
|
1244
1269
|
nearLimitBps;
|
|
@@ -1779,6 +1804,8 @@ var BudgetGuard = class {
|
|
|
1779
1804
|
const remainingUsd = Math.max(0, this.limitUsd - this.spentUsd);
|
|
1780
1805
|
const expectedCost = Math.max(this.lastLlmCost, this.lastToolCost);
|
|
1781
1806
|
const estCallsRemaining = expectedCost > 0 ? Math.floor(remainingUsd / expectedCost + 1e-9) : null;
|
|
1807
|
+
const elapsedMin = (Date.now() - this.createdAtMs) / 6e4;
|
|
1808
|
+
const burnRateUsdPerMin = elapsedMin > 0 ? this.spentUsd / elapsedMin : null;
|
|
1782
1809
|
let tokenUsedBps = null;
|
|
1783
1810
|
let remainingTokens = null;
|
|
1784
1811
|
let nearToken = false;
|
|
@@ -1832,7 +1859,8 @@ var BudgetGuard = class {
|
|
|
1832
1859
|
tokenUsedBps,
|
|
1833
1860
|
remainingTokens,
|
|
1834
1861
|
stepRemainingUsd,
|
|
1835
|
-
stepRemainingTokens
|
|
1862
|
+
stepRemainingTokens,
|
|
1863
|
+
burnRateUsdPerMin
|
|
1836
1864
|
};
|
|
1837
1865
|
}
|
|
1838
1866
|
};
|
|
@@ -2031,6 +2059,107 @@ async function nextPlan(guard, error, current, onDegrade) {
|
|
|
2031
2059
|
guard.check(current.estimatedCost);
|
|
2032
2060
|
return current;
|
|
2033
2061
|
}
|
|
2062
|
+
|
|
2063
|
+
// src/voice-pricing.ts
|
|
2064
|
+
var VOICE_MAP = cost_map_default["__voice__"] ?? {};
|
|
2065
|
+
var unitForMode = {
|
|
2066
|
+
stt: "usd_per_second",
|
|
2067
|
+
tts: "usd_per_1k_chars",
|
|
2068
|
+
telephony: "usd_per_minute"
|
|
2069
|
+
};
|
|
2070
|
+
function finiteNonNegative(value) {
|
|
2071
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
2072
|
+
}
|
|
2073
|
+
function lookupVoiceRate(model, mode) {
|
|
2074
|
+
if (model === null || model === void 0) return null;
|
|
2075
|
+
const entry = Object.prototype.hasOwnProperty.call(VOICE_MAP, model) ? VOICE_MAP[model] : void 0;
|
|
2076
|
+
if (!entry) return null;
|
|
2077
|
+
if (entry.mode !== mode) return null;
|
|
2078
|
+
if (entry.unit !== unitForMode[mode]) return null;
|
|
2079
|
+
const rate = entry.rate;
|
|
2080
|
+
if (!finiteNonNegative(rate)) return null;
|
|
2081
|
+
return rate;
|
|
2082
|
+
}
|
|
2083
|
+
function resolveVoiceRate(model, mode, override) {
|
|
2084
|
+
if (override !== void 0 && override !== null) {
|
|
2085
|
+
if (!finiteNonNegative(override)) {
|
|
2086
|
+
throw new RangeError(
|
|
2087
|
+
`voice ${mode} override must be a finite, non-negative number, got ${override}`
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
return { mode, unit: unitForMode[mode], rate: override, source: "override" };
|
|
2091
|
+
}
|
|
2092
|
+
const rate = lookupVoiceRate(model, mode);
|
|
2093
|
+
if (rate === null) throw new UnpriceableVoiceError(model ?? null, mode);
|
|
2094
|
+
return { mode, unit: unitForMode[mode], rate, source: "cost_map" };
|
|
2095
|
+
}
|
|
2096
|
+
function voiceLegCost(mode, quantity, rate) {
|
|
2097
|
+
if (!Number.isFinite(quantity)) {
|
|
2098
|
+
throw new RangeError(`voice ${mode} quantity must be a finite number, got ${quantity}`);
|
|
2099
|
+
}
|
|
2100
|
+
const q = Math.max(0, quantity);
|
|
2101
|
+
if (mode === "stt") return q * rate;
|
|
2102
|
+
if (mode === "tts") return q / 1e3 * rate;
|
|
2103
|
+
if (mode === "telephony") return q * rate;
|
|
2104
|
+
throw new RangeError(`unknown voice mode ${mode}`);
|
|
2105
|
+
}
|
|
2106
|
+
function priceVoiceLeg(mode, quantity, options = {}) {
|
|
2107
|
+
const model = options.model ?? null;
|
|
2108
|
+
const override = options.override ?? null;
|
|
2109
|
+
if (model === null && override === null) return null;
|
|
2110
|
+
const resolved = resolveVoiceRate(model, mode, override);
|
|
2111
|
+
return voiceLegCost(mode, quantity, resolved.rate);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/gates.ts
|
|
2115
|
+
var gates_exports = {};
|
|
2116
|
+
__export(gates_exports, {
|
|
2117
|
+
budgetExhausted: () => budgetExhausted,
|
|
2118
|
+
preCall: () => preCall,
|
|
2119
|
+
retell: () => retell,
|
|
2120
|
+
vapi: () => vapi
|
|
2121
|
+
});
|
|
2122
|
+
function budgetExhausted(guard, options = {}) {
|
|
2123
|
+
const estimatedCallUsd = options.estimatedCallUsd === void 0 ? 0 : options.estimatedCallUsd;
|
|
2124
|
+
if (!Number.isFinite(estimatedCallUsd) || estimatedCallUsd < 0) {
|
|
2125
|
+
throw new RangeError(
|
|
2126
|
+
`estimatedCallUsd must be a finite, non-negative number, got ${estimatedCallUsd}`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
const remainingUsd = guard.remainingUsd;
|
|
2130
|
+
return remainingUsd <= 0 || remainingUsd < estimatedCallUsd;
|
|
2131
|
+
}
|
|
2132
|
+
function preCall(guard, options = {}) {
|
|
2133
|
+
return !budgetExhausted(guard, options);
|
|
2134
|
+
}
|
|
2135
|
+
function retell(guard, options = {}) {
|
|
2136
|
+
if (budgetExhausted(guard, { estimatedCallUsd: options.estimatedCallUsd })) {
|
|
2137
|
+
return { call_inbound: { reject: true } };
|
|
2138
|
+
}
|
|
2139
|
+
const safeAdmit = { ...options.admit ?? {} };
|
|
2140
|
+
delete safeAdmit.reject;
|
|
2141
|
+
return { call_inbound: safeAdmit };
|
|
2142
|
+
}
|
|
2143
|
+
function vapi(guard, options = {}) {
|
|
2144
|
+
const {
|
|
2145
|
+
assistant,
|
|
2146
|
+
assistantId,
|
|
2147
|
+
errorMessage = "Sorry, this agent is out of budget right now.",
|
|
2148
|
+
estimatedCallUsd
|
|
2149
|
+
} = options;
|
|
2150
|
+
if (budgetExhausted(guard, { estimatedCallUsd })) {
|
|
2151
|
+
return { error: errorMessage };
|
|
2152
|
+
}
|
|
2153
|
+
if (assistantId !== void 0 && assistantId !== null) {
|
|
2154
|
+
return { assistantId };
|
|
2155
|
+
}
|
|
2156
|
+
if (assistant !== void 0 && assistant !== null) {
|
|
2157
|
+
return { assistant };
|
|
2158
|
+
}
|
|
2159
|
+
throw new RangeError(
|
|
2160
|
+
"vapi() admitted the call but has nothing to return: pass assistant or assistantId for the admit path."
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2034
2163
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2035
2164
|
0 && (module.exports = {
|
|
2036
2165
|
BudgetExceeded,
|
|
@@ -2040,10 +2169,16 @@ async function nextPlan(guard, error, current, onDegrade) {
|
|
|
2040
2169
|
LatencyBudget,
|
|
2041
2170
|
TokenBudgetExceeded,
|
|
2042
2171
|
UnpriceableModelError,
|
|
2172
|
+
UnpriceableVoiceError,
|
|
2043
2173
|
budgetGuardMiddleware,
|
|
2174
|
+
gates,
|
|
2175
|
+
lookupVoiceRate,
|
|
2044
2176
|
priceTokens,
|
|
2177
|
+
priceVoiceLeg,
|
|
2045
2178
|
pricing,
|
|
2046
2179
|
resolvePrice,
|
|
2180
|
+
resolveVoiceRate,
|
|
2181
|
+
voiceLegCost,
|
|
2047
2182
|
withBudgetRetry
|
|
2048
2183
|
});
|
|
2049
2184
|
//# sourceMappingURL=index.cjs.map
|