floe-guard 0.9.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 +238 -5
- 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 +232 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cost_map.json +102 -4
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
|
}
|
|
@@ -220,6 +239,12 @@ var cost_map_default = {
|
|
|
220
239
|
litellm_provider: "anthropic",
|
|
221
240
|
mode: "chat"
|
|
222
241
|
},
|
|
242
|
+
"claude-opus-5": {
|
|
243
|
+
input_cost_per_token: 5e-6,
|
|
244
|
+
output_cost_per_token: 25e-6,
|
|
245
|
+
litellm_provider: "anthropic",
|
|
246
|
+
mode: "chat"
|
|
247
|
+
},
|
|
223
248
|
"claude-sonnet-4-20250514": {
|
|
224
249
|
input_cost_per_token: 3e-6,
|
|
225
250
|
output_cost_per_token: 15e-6,
|
|
@@ -538,6 +563,18 @@ var cost_map_default = {
|
|
|
538
563
|
litellm_provider: "gemini",
|
|
539
564
|
mode: "chat"
|
|
540
565
|
},
|
|
566
|
+
"gemini-robotics-er-1.6-preview": {
|
|
567
|
+
input_cost_per_token: 1e-6,
|
|
568
|
+
output_cost_per_token: 5e-6,
|
|
569
|
+
litellm_provider: "gemini",
|
|
570
|
+
mode: "chat"
|
|
571
|
+
},
|
|
572
|
+
"gemini-robotics-er-2-preview": {
|
|
573
|
+
input_cost_per_token: 2e-6,
|
|
574
|
+
output_cost_per_token: 1e-5,
|
|
575
|
+
litellm_provider: "gemini",
|
|
576
|
+
mode: "chat"
|
|
577
|
+
},
|
|
541
578
|
"gpt-3.5-turbo": {
|
|
542
579
|
input_cost_per_token: 5e-7,
|
|
543
580
|
output_cost_per_token: 15e-7,
|
|
@@ -893,8 +930,8 @@ var cost_map_default = {
|
|
|
893
930
|
mode: "chat"
|
|
894
931
|
},
|
|
895
932
|
"gpt-5.6-luna": {
|
|
896
|
-
input_cost_per_token:
|
|
897
|
-
output_cost_per_token:
|
|
933
|
+
input_cost_per_token: 2e-7,
|
|
934
|
+
output_cost_per_token: 12e-7,
|
|
898
935
|
litellm_provider: "openai",
|
|
899
936
|
mode: "chat"
|
|
900
937
|
},
|
|
@@ -905,8 +942,8 @@ var cost_map_default = {
|
|
|
905
942
|
mode: "chat"
|
|
906
943
|
},
|
|
907
944
|
"gpt-5.6-terra": {
|
|
908
|
-
input_cost_per_token:
|
|
909
|
-
output_cost_per_token:
|
|
945
|
+
input_cost_per_token: 2e-6,
|
|
946
|
+
output_cost_per_token: 12e-6,
|
|
910
947
|
litellm_provider: "openai",
|
|
911
948
|
mode: "chat"
|
|
912
949
|
},
|
|
@@ -1059,6 +1096,86 @@ var cost_map_default = {
|
|
|
1059
1096
|
output_cost_per_token: 0,
|
|
1060
1097
|
litellm_provider: "openai",
|
|
1061
1098
|
mode: "embedding"
|
|
1099
|
+
},
|
|
1100
|
+
__voice__: {
|
|
1101
|
+
"deepgram-nova-3": {
|
|
1102
|
+
mode: "stt",
|
|
1103
|
+
unit: "usd_per_second",
|
|
1104
|
+
rate: 1283333e-10,
|
|
1105
|
+
provider: "deepgram"
|
|
1106
|
+
},
|
|
1107
|
+
"deepgram-nova-3-multilingual": {
|
|
1108
|
+
mode: "stt",
|
|
1109
|
+
unit: "usd_per_second",
|
|
1110
|
+
rate: 1533333e-10,
|
|
1111
|
+
provider: "deepgram"
|
|
1112
|
+
},
|
|
1113
|
+
"deepgram-nova-3-base": {
|
|
1114
|
+
mode: "stt",
|
|
1115
|
+
unit: "usd_per_second",
|
|
1116
|
+
rate: 2416667e-10,
|
|
1117
|
+
provider: "deepgram"
|
|
1118
|
+
},
|
|
1119
|
+
"assemblyai-universal-streaming": {
|
|
1120
|
+
mode: "stt",
|
|
1121
|
+
unit: "usd_per_second",
|
|
1122
|
+
rate: 416667e-10,
|
|
1123
|
+
provider: "assemblyai"
|
|
1124
|
+
},
|
|
1125
|
+
"elevenlabs-multilingual-v2": {
|
|
1126
|
+
mode: "tts",
|
|
1127
|
+
unit: "usd_per_1k_chars",
|
|
1128
|
+
rate: 0.1,
|
|
1129
|
+
provider: "elevenlabs"
|
|
1130
|
+
},
|
|
1131
|
+
"elevenlabs-flash-v2.5": {
|
|
1132
|
+
mode: "tts",
|
|
1133
|
+
unit: "usd_per_1k_chars",
|
|
1134
|
+
rate: 0.05,
|
|
1135
|
+
provider: "elevenlabs"
|
|
1136
|
+
},
|
|
1137
|
+
"elevenlabs-turbo-v2.5": {
|
|
1138
|
+
mode: "tts",
|
|
1139
|
+
unit: "usd_per_1k_chars",
|
|
1140
|
+
rate: 0.05,
|
|
1141
|
+
provider: "elevenlabs"
|
|
1142
|
+
},
|
|
1143
|
+
"cartesia-sonic": {
|
|
1144
|
+
mode: "tts",
|
|
1145
|
+
unit: "usd_per_1k_chars",
|
|
1146
|
+
rate: 0.03,
|
|
1147
|
+
provider: "cartesia"
|
|
1148
|
+
},
|
|
1149
|
+
"cartesia-line": {
|
|
1150
|
+
mode: "telephony",
|
|
1151
|
+
unit: "usd_per_minute",
|
|
1152
|
+
rate: 0.06,
|
|
1153
|
+
provider: "cartesia"
|
|
1154
|
+
},
|
|
1155
|
+
"rime-mist-v2": {
|
|
1156
|
+
mode: "tts",
|
|
1157
|
+
unit: "usd_per_1k_chars",
|
|
1158
|
+
rate: 0.03,
|
|
1159
|
+
provider: "rime"
|
|
1160
|
+
},
|
|
1161
|
+
"twilio-us-inbound-local": {
|
|
1162
|
+
mode: "telephony",
|
|
1163
|
+
unit: "usd_per_minute",
|
|
1164
|
+
rate: 85e-4,
|
|
1165
|
+
provider: "twilio"
|
|
1166
|
+
},
|
|
1167
|
+
"twilio-us-outbound-local": {
|
|
1168
|
+
mode: "telephony",
|
|
1169
|
+
unit: "usd_per_minute",
|
|
1170
|
+
rate: 0.014,
|
|
1171
|
+
provider: "twilio"
|
|
1172
|
+
},
|
|
1173
|
+
"twilio-us-sip-inbound": {
|
|
1174
|
+
mode: "telephony",
|
|
1175
|
+
unit: "usd_per_minute",
|
|
1176
|
+
rate: 4e-3,
|
|
1177
|
+
provider: "twilio"
|
|
1178
|
+
}
|
|
1062
1179
|
}
|
|
1063
1180
|
};
|
|
1064
1181
|
|
|
@@ -1141,6 +1258,12 @@ function isReservation(h) {
|
|
|
1141
1258
|
var BudgetGuard = class {
|
|
1142
1259
|
limitUsd;
|
|
1143
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();
|
|
1144
1267
|
priceOverrides;
|
|
1145
1268
|
failClosed;
|
|
1146
1269
|
nearLimitBps;
|
|
@@ -1681,6 +1804,8 @@ var BudgetGuard = class {
|
|
|
1681
1804
|
const remainingUsd = Math.max(0, this.limitUsd - this.spentUsd);
|
|
1682
1805
|
const expectedCost = Math.max(this.lastLlmCost, this.lastToolCost);
|
|
1683
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;
|
|
1684
1809
|
let tokenUsedBps = null;
|
|
1685
1810
|
let remainingTokens = null;
|
|
1686
1811
|
let nearToken = false;
|
|
@@ -1734,7 +1859,8 @@ var BudgetGuard = class {
|
|
|
1734
1859
|
tokenUsedBps,
|
|
1735
1860
|
remainingTokens,
|
|
1736
1861
|
stepRemainingUsd,
|
|
1737
|
-
stepRemainingTokens
|
|
1862
|
+
stepRemainingTokens,
|
|
1863
|
+
burnRateUsdPerMin
|
|
1738
1864
|
};
|
|
1739
1865
|
}
|
|
1740
1866
|
};
|
|
@@ -1933,6 +2059,107 @@ async function nextPlan(guard, error, current, onDegrade) {
|
|
|
1933
2059
|
guard.check(current.estimatedCost);
|
|
1934
2060
|
return current;
|
|
1935
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
|
+
}
|
|
1936
2163
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1937
2164
|
0 && (module.exports = {
|
|
1938
2165
|
BudgetExceeded,
|
|
@@ -1942,10 +2169,16 @@ async function nextPlan(guard, error, current, onDegrade) {
|
|
|
1942
2169
|
LatencyBudget,
|
|
1943
2170
|
TokenBudgetExceeded,
|
|
1944
2171
|
UnpriceableModelError,
|
|
2172
|
+
UnpriceableVoiceError,
|
|
1945
2173
|
budgetGuardMiddleware,
|
|
2174
|
+
gates,
|
|
2175
|
+
lookupVoiceRate,
|
|
1946
2176
|
priceTokens,
|
|
2177
|
+
priceVoiceLeg,
|
|
1947
2178
|
pricing,
|
|
1948
2179
|
resolvePrice,
|
|
2180
|
+
resolveVoiceRate,
|
|
2181
|
+
voiceLegCost,
|
|
1949
2182
|
withBudgetRetry
|
|
1950
2183
|
});
|
|
1951
2184
|
//# sourceMappingURL=index.cjs.map
|