floe-guard 0.4.0 → 0.6.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/README.md +19 -3
- package/dist/index.cjs +341 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -14
- package/dist/index.d.ts +89 -14
- package/dist/index.js +341 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cost_map.json +216 -84
package/dist/index.js
CHANGED
|
@@ -275,6 +275,222 @@ var cost_map_default = {
|
|
|
275
275
|
litellm_provider: "openai",
|
|
276
276
|
mode: "chat"
|
|
277
277
|
},
|
|
278
|
+
"gemini-2.0-flash": {
|
|
279
|
+
input_cost_per_token: 1e-7,
|
|
280
|
+
output_cost_per_token: 4e-7,
|
|
281
|
+
litellm_provider: "gemini",
|
|
282
|
+
mode: "chat"
|
|
283
|
+
},
|
|
284
|
+
"gemini-2.0-flash-001": {
|
|
285
|
+
input_cost_per_token: 1e-7,
|
|
286
|
+
output_cost_per_token: 4e-7,
|
|
287
|
+
litellm_provider: "gemini",
|
|
288
|
+
mode: "chat"
|
|
289
|
+
},
|
|
290
|
+
"gemini-2.0-flash-lite": {
|
|
291
|
+
input_cost_per_token: 75e-9,
|
|
292
|
+
output_cost_per_token: 3e-7,
|
|
293
|
+
litellm_provider: "gemini",
|
|
294
|
+
mode: "chat"
|
|
295
|
+
},
|
|
296
|
+
"gemini-2.0-flash-lite-001": {
|
|
297
|
+
input_cost_per_token: 75e-9,
|
|
298
|
+
output_cost_per_token: 3e-7,
|
|
299
|
+
litellm_provider: "gemini",
|
|
300
|
+
mode: "chat"
|
|
301
|
+
},
|
|
302
|
+
"gemini-2.5-computer-use-preview-10-2025": {
|
|
303
|
+
input_cost_per_token: 125e-8,
|
|
304
|
+
output_cost_per_token: 1e-5,
|
|
305
|
+
litellm_provider: "gemini",
|
|
306
|
+
mode: "chat"
|
|
307
|
+
},
|
|
308
|
+
"gemini-2.5-flash": {
|
|
309
|
+
input_cost_per_token: 3e-7,
|
|
310
|
+
output_cost_per_token: 25e-7,
|
|
311
|
+
litellm_provider: "gemini",
|
|
312
|
+
mode: "chat"
|
|
313
|
+
},
|
|
314
|
+
"gemini-2.5-flash-lite": {
|
|
315
|
+
input_cost_per_token: 1e-7,
|
|
316
|
+
output_cost_per_token: 4e-7,
|
|
317
|
+
litellm_provider: "gemini",
|
|
318
|
+
mode: "chat"
|
|
319
|
+
},
|
|
320
|
+
"gemini-2.5-flash-lite-preview-06-17": {
|
|
321
|
+
input_cost_per_token: 1e-7,
|
|
322
|
+
output_cost_per_token: 4e-7,
|
|
323
|
+
litellm_provider: "gemini",
|
|
324
|
+
mode: "chat"
|
|
325
|
+
},
|
|
326
|
+
"gemini-2.5-flash-lite-preview-09-2025": {
|
|
327
|
+
input_cost_per_token: 1e-7,
|
|
328
|
+
output_cost_per_token: 4e-7,
|
|
329
|
+
litellm_provider: "gemini",
|
|
330
|
+
mode: "chat"
|
|
331
|
+
},
|
|
332
|
+
"gemini-2.5-flash-native-audio-latest": {
|
|
333
|
+
input_cost_per_token: 3e-7,
|
|
334
|
+
output_cost_per_token: 25e-7,
|
|
335
|
+
litellm_provider: "gemini",
|
|
336
|
+
mode: "chat"
|
|
337
|
+
},
|
|
338
|
+
"gemini-2.5-flash-native-audio-preview-09-2025": {
|
|
339
|
+
input_cost_per_token: 3e-7,
|
|
340
|
+
output_cost_per_token: 25e-7,
|
|
341
|
+
litellm_provider: "gemini",
|
|
342
|
+
mode: "chat"
|
|
343
|
+
},
|
|
344
|
+
"gemini-2.5-flash-native-audio-preview-12-2025": {
|
|
345
|
+
input_cost_per_token: 3e-7,
|
|
346
|
+
output_cost_per_token: 25e-7,
|
|
347
|
+
litellm_provider: "gemini",
|
|
348
|
+
mode: "chat"
|
|
349
|
+
},
|
|
350
|
+
"gemini-2.5-flash-preview-09-2025": {
|
|
351
|
+
input_cost_per_token: 3e-7,
|
|
352
|
+
output_cost_per_token: 25e-7,
|
|
353
|
+
litellm_provider: "gemini",
|
|
354
|
+
mode: "chat"
|
|
355
|
+
},
|
|
356
|
+
"gemini-2.5-pro": {
|
|
357
|
+
input_cost_per_token: 125e-8,
|
|
358
|
+
output_cost_per_token: 1e-5,
|
|
359
|
+
litellm_provider: "gemini",
|
|
360
|
+
mode: "chat"
|
|
361
|
+
},
|
|
362
|
+
"gemini-2.5-pro-preview-tts": {
|
|
363
|
+
input_cost_per_token: 125e-8,
|
|
364
|
+
output_cost_per_token: 1e-5,
|
|
365
|
+
litellm_provider: "gemini",
|
|
366
|
+
mode: "chat"
|
|
367
|
+
},
|
|
368
|
+
"gemini-3-flash-preview": {
|
|
369
|
+
input_cost_per_token: 5e-7,
|
|
370
|
+
output_cost_per_token: 3e-6,
|
|
371
|
+
litellm_provider: "gemini",
|
|
372
|
+
mode: "chat"
|
|
373
|
+
},
|
|
374
|
+
"gemini-3-pro-preview": {
|
|
375
|
+
input_cost_per_token: 2e-6,
|
|
376
|
+
output_cost_per_token: 12e-6,
|
|
377
|
+
litellm_provider: "gemini",
|
|
378
|
+
mode: "chat"
|
|
379
|
+
},
|
|
380
|
+
"gemini-3.1-flash-lite": {
|
|
381
|
+
input_cost_per_token: 25e-8,
|
|
382
|
+
output_cost_per_token: 15e-7,
|
|
383
|
+
litellm_provider: "gemini",
|
|
384
|
+
mode: "chat"
|
|
385
|
+
},
|
|
386
|
+
"gemini-3.1-flash-lite-preview": {
|
|
387
|
+
input_cost_per_token: 25e-8,
|
|
388
|
+
output_cost_per_token: 15e-7,
|
|
389
|
+
litellm_provider: "gemini",
|
|
390
|
+
mode: "chat"
|
|
391
|
+
},
|
|
392
|
+
"gemini-3.1-flash-live-preview": {
|
|
393
|
+
input_cost_per_token: 75e-8,
|
|
394
|
+
output_cost_per_token: 45e-7,
|
|
395
|
+
litellm_provider: "gemini",
|
|
396
|
+
mode: "chat"
|
|
397
|
+
},
|
|
398
|
+
"gemini-3.1-pro-preview": {
|
|
399
|
+
input_cost_per_token: 2e-6,
|
|
400
|
+
output_cost_per_token: 12e-6,
|
|
401
|
+
litellm_provider: "gemini",
|
|
402
|
+
mode: "chat"
|
|
403
|
+
},
|
|
404
|
+
"gemini-3.1-pro-preview-customtools": {
|
|
405
|
+
input_cost_per_token: 2e-6,
|
|
406
|
+
output_cost_per_token: 12e-6,
|
|
407
|
+
litellm_provider: "gemini",
|
|
408
|
+
mode: "chat"
|
|
409
|
+
},
|
|
410
|
+
"gemini-3.5-flash": {
|
|
411
|
+
input_cost_per_token: 15e-7,
|
|
412
|
+
output_cost_per_token: 9e-6,
|
|
413
|
+
litellm_provider: "gemini",
|
|
414
|
+
mode: "chat"
|
|
415
|
+
},
|
|
416
|
+
"gemini-3.5-flash-lite": {
|
|
417
|
+
input_cost_per_token: 3e-7,
|
|
418
|
+
output_cost_per_token: 25e-7,
|
|
419
|
+
litellm_provider: "gemini",
|
|
420
|
+
mode: "chat"
|
|
421
|
+
},
|
|
422
|
+
"gemini-3.6-flash": {
|
|
423
|
+
input_cost_per_token: 15e-7,
|
|
424
|
+
output_cost_per_token: 75e-7,
|
|
425
|
+
litellm_provider: "gemini",
|
|
426
|
+
mode: "chat"
|
|
427
|
+
},
|
|
428
|
+
"gemini-embedding-001": {
|
|
429
|
+
input_cost_per_token: 15e-8,
|
|
430
|
+
output_cost_per_token: 0,
|
|
431
|
+
litellm_provider: "gemini",
|
|
432
|
+
mode: "embedding"
|
|
433
|
+
},
|
|
434
|
+
"gemini-embedding-2": {
|
|
435
|
+
input_cost_per_token: 2e-7,
|
|
436
|
+
output_cost_per_token: 0,
|
|
437
|
+
litellm_provider: "gemini",
|
|
438
|
+
mode: "embedding"
|
|
439
|
+
},
|
|
440
|
+
"gemini-embedding-2-preview": {
|
|
441
|
+
input_cost_per_token: 2e-7,
|
|
442
|
+
output_cost_per_token: 0,
|
|
443
|
+
litellm_provider: "gemini",
|
|
444
|
+
mode: "embedding"
|
|
445
|
+
},
|
|
446
|
+
"gemini-exp-1206": {
|
|
447
|
+
input_cost_per_token: 3e-7,
|
|
448
|
+
output_cost_per_token: 25e-7,
|
|
449
|
+
litellm_provider: "gemini",
|
|
450
|
+
mode: "chat"
|
|
451
|
+
},
|
|
452
|
+
"gemini-flash-latest": {
|
|
453
|
+
input_cost_per_token: 3e-7,
|
|
454
|
+
output_cost_per_token: 25e-7,
|
|
455
|
+
litellm_provider: "gemini",
|
|
456
|
+
mode: "chat"
|
|
457
|
+
},
|
|
458
|
+
"gemini-flash-lite-latest": {
|
|
459
|
+
input_cost_per_token: 1e-7,
|
|
460
|
+
output_cost_per_token: 4e-7,
|
|
461
|
+
litellm_provider: "gemini",
|
|
462
|
+
mode: "chat"
|
|
463
|
+
},
|
|
464
|
+
"gemini-gemma-2-27b-it": {
|
|
465
|
+
input_cost_per_token: 35e-8,
|
|
466
|
+
output_cost_per_token: 105e-8,
|
|
467
|
+
litellm_provider: "gemini",
|
|
468
|
+
mode: "chat"
|
|
469
|
+
},
|
|
470
|
+
"gemini-gemma-2-9b-it": {
|
|
471
|
+
input_cost_per_token: 35e-8,
|
|
472
|
+
output_cost_per_token: 105e-8,
|
|
473
|
+
litellm_provider: "gemini",
|
|
474
|
+
mode: "chat"
|
|
475
|
+
},
|
|
476
|
+
"gemini-omni-flash-preview": {
|
|
477
|
+
input_cost_per_token: 15e-7,
|
|
478
|
+
output_cost_per_token: 9e-6,
|
|
479
|
+
litellm_provider: "gemini",
|
|
480
|
+
mode: "chat"
|
|
481
|
+
},
|
|
482
|
+
"gemini-pro-latest": {
|
|
483
|
+
input_cost_per_token: 125e-8,
|
|
484
|
+
output_cost_per_token: 1e-5,
|
|
485
|
+
litellm_provider: "gemini",
|
|
486
|
+
mode: "chat"
|
|
487
|
+
},
|
|
488
|
+
"gemini-robotics-er-1.5-preview": {
|
|
489
|
+
input_cost_per_token: 3e-7,
|
|
490
|
+
output_cost_per_token: 25e-7,
|
|
491
|
+
litellm_provider: "gemini",
|
|
492
|
+
mode: "chat"
|
|
493
|
+
},
|
|
278
494
|
"gpt-3.5-turbo": {
|
|
279
495
|
input_cost_per_token: 5e-7,
|
|
280
496
|
output_cost_per_token: 15e-7,
|
|
@@ -449,18 +665,6 @@ var cost_map_default = {
|
|
|
449
665
|
litellm_provider: "openai",
|
|
450
666
|
mode: "chat"
|
|
451
667
|
},
|
|
452
|
-
"gpt-4o-mini-realtime-preview": {
|
|
453
|
-
input_cost_per_token: 6e-7,
|
|
454
|
-
output_cost_per_token: 24e-7,
|
|
455
|
-
litellm_provider: "openai",
|
|
456
|
-
mode: "chat"
|
|
457
|
-
},
|
|
458
|
-
"gpt-4o-mini-realtime-preview-2024-12-17": {
|
|
459
|
-
input_cost_per_token: 6e-7,
|
|
460
|
-
output_cost_per_token: 24e-7,
|
|
461
|
-
litellm_provider: "openai",
|
|
462
|
-
mode: "chat"
|
|
463
|
-
},
|
|
464
668
|
"gpt-4o-mini-search-preview": {
|
|
465
669
|
input_cost_per_token: 15e-8,
|
|
466
670
|
output_cost_per_token: 6e-7,
|
|
@@ -473,24 +677,6 @@ var cost_map_default = {
|
|
|
473
677
|
litellm_provider: "openai",
|
|
474
678
|
mode: "chat"
|
|
475
679
|
},
|
|
476
|
-
"gpt-4o-realtime-preview": {
|
|
477
|
-
input_cost_per_token: 5e-6,
|
|
478
|
-
output_cost_per_token: 2e-5,
|
|
479
|
-
litellm_provider: "openai",
|
|
480
|
-
mode: "chat"
|
|
481
|
-
},
|
|
482
|
-
"gpt-4o-realtime-preview-2024-12-17": {
|
|
483
|
-
input_cost_per_token: 5e-6,
|
|
484
|
-
output_cost_per_token: 2e-5,
|
|
485
|
-
litellm_provider: "openai",
|
|
486
|
-
mode: "chat"
|
|
487
|
-
},
|
|
488
|
-
"gpt-4o-realtime-preview-2025-06-03": {
|
|
489
|
-
input_cost_per_token: 5e-6,
|
|
490
|
-
output_cost_per_token: 2e-5,
|
|
491
|
-
litellm_provider: "openai",
|
|
492
|
-
mode: "chat"
|
|
493
|
-
},
|
|
494
680
|
"gpt-4o-search-preview": {
|
|
495
681
|
input_cost_per_token: 25e-7,
|
|
496
682
|
output_cost_per_token: 1e-5,
|
|
@@ -713,60 +899,6 @@ var cost_map_default = {
|
|
|
713
899
|
litellm_provider: "openai",
|
|
714
900
|
mode: "chat"
|
|
715
901
|
},
|
|
716
|
-
"gpt-realtime": {
|
|
717
|
-
input_cost_per_token: 4e-6,
|
|
718
|
-
output_cost_per_token: 16e-6,
|
|
719
|
-
litellm_provider: "openai",
|
|
720
|
-
mode: "chat"
|
|
721
|
-
},
|
|
722
|
-
"gpt-realtime-1.5": {
|
|
723
|
-
input_cost_per_token: 4e-6,
|
|
724
|
-
output_cost_per_token: 16e-6,
|
|
725
|
-
litellm_provider: "openai",
|
|
726
|
-
mode: "chat"
|
|
727
|
-
},
|
|
728
|
-
"gpt-realtime-2": {
|
|
729
|
-
input_cost_per_token: 4e-6,
|
|
730
|
-
output_cost_per_token: 16e-6,
|
|
731
|
-
litellm_provider: "openai",
|
|
732
|
-
mode: "chat"
|
|
733
|
-
},
|
|
734
|
-
"gpt-realtime-2.1": {
|
|
735
|
-
input_cost_per_token: 4e-6,
|
|
736
|
-
output_cost_per_token: 24e-6,
|
|
737
|
-
litellm_provider: "openai",
|
|
738
|
-
mode: "chat"
|
|
739
|
-
},
|
|
740
|
-
"gpt-realtime-2.1-mini": {
|
|
741
|
-
input_cost_per_token: 6e-7,
|
|
742
|
-
output_cost_per_token: 24e-7,
|
|
743
|
-
litellm_provider: "openai",
|
|
744
|
-
mode: "chat"
|
|
745
|
-
},
|
|
746
|
-
"gpt-realtime-2025-08-28": {
|
|
747
|
-
input_cost_per_token: 4e-6,
|
|
748
|
-
output_cost_per_token: 16e-6,
|
|
749
|
-
litellm_provider: "openai",
|
|
750
|
-
mode: "chat"
|
|
751
|
-
},
|
|
752
|
-
"gpt-realtime-mini": {
|
|
753
|
-
input_cost_per_token: 6e-7,
|
|
754
|
-
output_cost_per_token: 24e-7,
|
|
755
|
-
litellm_provider: "openai",
|
|
756
|
-
mode: "chat"
|
|
757
|
-
},
|
|
758
|
-
"gpt-realtime-mini-2025-10-06": {
|
|
759
|
-
input_cost_per_token: 6e-7,
|
|
760
|
-
output_cost_per_token: 24e-7,
|
|
761
|
-
litellm_provider: "openai",
|
|
762
|
-
mode: "chat"
|
|
763
|
-
},
|
|
764
|
-
"gpt-realtime-mini-2025-12-15": {
|
|
765
|
-
input_cost_per_token: 6e-7,
|
|
766
|
-
output_cost_per_token: 24e-7,
|
|
767
|
-
litellm_provider: "openai",
|
|
768
|
-
mode: "chat"
|
|
769
|
-
},
|
|
770
902
|
"llama-3.1-8b-instant": {
|
|
771
903
|
input_cost_per_token: 5e-8,
|
|
772
904
|
output_cost_per_token: 8e-8,
|
|
@@ -963,13 +1095,26 @@ var BudgetGuard = class {
|
|
|
963
1095
|
failClosed;
|
|
964
1096
|
nearLimitBps;
|
|
965
1097
|
onBlock;
|
|
966
|
-
/**
|
|
967
|
-
|
|
1098
|
+
/**
|
|
1099
|
+
* Costs of the most recent priced LLM call and tool call, tracked
|
|
1100
|
+
* SEPARATELY: the default next-call prediction is the max of the two, so a
|
|
1101
|
+
* cheap tool call can't shrink the estimate right before an expensive LLM
|
|
1102
|
+
* call (or vice versa) — conservative beats one-call-too-late.
|
|
1103
|
+
*/
|
|
1104
|
+
lastLlmCost = 0;
|
|
1105
|
+
lastToolCost = 0;
|
|
968
1106
|
/** USD held for in-flight calls (reserved, not yet settled). Counts toward the ceiling. */
|
|
969
1107
|
reserved = 0;
|
|
970
1108
|
/** Per-call ledger, oldest first; a ring buffer when maxLogEvents is set. */
|
|
971
1109
|
spendEvents = [];
|
|
972
1110
|
maxLogEvents;
|
|
1111
|
+
/**
|
|
1112
|
+
* Per-tool running totals (settleTool/recordTool) — the tool side of the one
|
|
1113
|
+
* shared ceiling, exposed via the toolCosts getter. null-prototype: tool
|
|
1114
|
+
* names are caller-supplied strings, so a "__proto__" name is stored as
|
|
1115
|
+
* plain data instead of mutating the object's prototype.
|
|
1116
|
+
*/
|
|
1117
|
+
toolCostTotals = /* @__PURE__ */ Object.create(null);
|
|
973
1118
|
/**
|
|
974
1119
|
* @param limitUsd the spend ceiling, in USD. `0` blocks the very first call.
|
|
975
1120
|
*/
|
|
@@ -1001,7 +1146,8 @@ var BudgetGuard = class {
|
|
|
1001
1146
|
* Throw {@link BudgetExceeded} if the next call would cross the ceiling.
|
|
1002
1147
|
*
|
|
1003
1148
|
* Call this immediately before each LLM request. The "next call" is estimated
|
|
1004
|
-
*
|
|
1149
|
+
* conservatively as the costlier of the last LLM call and the last tool call
|
|
1150
|
+
* (override with `estimatedNextCost`); the
|
|
1005
1151
|
* first call is always allowed unless the ceiling is already met. In-flight
|
|
1006
1152
|
* reservations count toward the total, so this stays correct alongside
|
|
1007
1153
|
* {@link BudgetGuard.reserve}.
|
|
@@ -1010,7 +1156,7 @@ var BudgetGuard = class {
|
|
|
1010
1156
|
* `settle()`, which hold the estimate across the await.
|
|
1011
1157
|
*/
|
|
1012
1158
|
check(estimatedNextCost) {
|
|
1013
|
-
const rawEstimate = estimatedNextCost === void 0 ? this.
|
|
1159
|
+
const rawEstimate = estimatedNextCost === void 0 ? this.defaultEstimate() : estimatedNextCost;
|
|
1014
1160
|
if (!Number.isFinite(rawEstimate)) {
|
|
1015
1161
|
throw new RangeError(
|
|
1016
1162
|
`estimatedNextCost must be a finite number, got ${rawEstimate}`
|
|
@@ -1031,10 +1177,11 @@ var BudgetGuard = class {
|
|
|
1031
1177
|
* the same stale total. Throws {@link BudgetExceeded} (without reserving) if
|
|
1032
1178
|
* the reservation would cross the ceiling. Returns the reservation handle to
|
|
1033
1179
|
* pass to {@link BudgetGuard.settle} (or {@link BudgetGuard.release} on error).
|
|
1034
|
-
* `estimatedCost` defaults to the last call
|
|
1180
|
+
* `estimatedCost` defaults to the costlier of the last LLM call and the last
|
|
1181
|
+
* tool call.
|
|
1035
1182
|
*/
|
|
1036
1183
|
reserve(estimatedCost) {
|
|
1037
|
-
const rawEstimate = estimatedCost === void 0 ? this.
|
|
1184
|
+
const rawEstimate = estimatedCost === void 0 ? this.defaultEstimate() : estimatedCost;
|
|
1038
1185
|
if (!Number.isFinite(rawEstimate)) {
|
|
1039
1186
|
throw new RangeError(
|
|
1040
1187
|
`estimatedCost must be a finite number, got ${rawEstimate}`
|
|
@@ -1086,13 +1233,13 @@ var BudgetGuard = class {
|
|
|
1086
1233
|
throw err;
|
|
1087
1234
|
}
|
|
1088
1235
|
if (reserved) {
|
|
1089
|
-
this.
|
|
1236
|
+
this.consumeReservation(reserved);
|
|
1090
1237
|
}
|
|
1091
1238
|
this.spentUsd += cost;
|
|
1092
1239
|
if (this.spentUsd - this.limitUsd > 0 && this.spentUsd - this.limitUsd < EPS) {
|
|
1093
1240
|
this.spentUsd = this.limitUsd;
|
|
1094
1241
|
}
|
|
1095
|
-
this.
|
|
1242
|
+
this.lastLlmCost = cost;
|
|
1096
1243
|
this.appendEvent({
|
|
1097
1244
|
timestamp: Date.now() / 1e3,
|
|
1098
1245
|
kind: "llm",
|
|
@@ -1122,24 +1269,64 @@ var BudgetGuard = class {
|
|
|
1122
1269
|
});
|
|
1123
1270
|
}
|
|
1124
1271
|
/**
|
|
1125
|
-
*
|
|
1272
|
+
* Atomically check the ceiling AND hold a tool call's cost in flight.
|
|
1126
1273
|
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1274
|
+
* The tool-spend counterpart of {@link BudgetGuard.reserve} — and STRONGER
|
|
1275
|
+
* than the LLM path, because a paid tool's price is usually known exactly
|
|
1276
|
+
* before the call, so the pre-call hard-stop is precise rather than
|
|
1277
|
+
* estimated:
|
|
1278
|
+
*
|
|
1279
|
+
* const handle = guard.reserveTool(0.02); // throws BEFORE Apollo runs
|
|
1280
|
+
* const result = await apollo.peopleLookup(...);
|
|
1281
|
+
* guard.settleTool("apollo.people_lookup", 0.02, { reserved: handle });
|
|
1282
|
+
*
|
|
1283
|
+
* Throws {@link BudgetExceeded} (without reserving) if the call would cross
|
|
1284
|
+
* the ceiling. The estimate is required — tools have no last-cost prediction
|
|
1285
|
+
* worth falling back to. Pass the returned handle to
|
|
1286
|
+
* {@link BudgetGuard.settleTool}, or {@link BudgetGuard.release} on failure.
|
|
1134
1287
|
*/
|
|
1135
|
-
|
|
1288
|
+
reserveTool(estimatedCost) {
|
|
1289
|
+
if (estimatedCost === void 0) {
|
|
1290
|
+
throw new RangeError("reserveTool requires an estimated cost, got undefined");
|
|
1291
|
+
}
|
|
1292
|
+
if (!Number.isFinite(estimatedCost) || estimatedCost < 0) {
|
|
1293
|
+
throw new RangeError(
|
|
1294
|
+
`estimatedCost must be a finite, non-negative number, got ${estimatedCost}`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
return this.reserve(estimatedCost);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Release a reservation and record a tool call's actual cost.
|
|
1301
|
+
*
|
|
1302
|
+
* `recordTool` is `settleTool` with no reservation. The caller supplies the
|
|
1303
|
+
* cost — tools have no token usage to price. Accrues into the same
|
|
1304
|
+
* `spentUsd` ceiling as tokens, tallies the per-tool total
|
|
1305
|
+
* ({@link BudgetGuard.toolCosts}), updates the tool side of the next-call
|
|
1306
|
+
* estimate (tracked separately from the LLM side; the default prediction is
|
|
1307
|
+
* the max of the two, so a tool-hammering loop's plain `check()` stops
|
|
1308
|
+
* BEFORE the crossing call without a cheap tool shrinking the LLM
|
|
1309
|
+
* prediction), and appends
|
|
1310
|
+
* a `kind: "tool"` {@link SpendEvent} to {@link BudgetGuard.spendLog}.
|
|
1311
|
+
* Returns `costUsd`.
|
|
1312
|
+
*/
|
|
1313
|
+
settleTool(tool, costUsd, options = {}) {
|
|
1136
1314
|
if (!Number.isFinite(costUsd) || costUsd < 0) {
|
|
1137
1315
|
throw new RangeError(`costUsd must be a finite, non-negative number, got ${costUsd}`);
|
|
1138
1316
|
}
|
|
1317
|
+
const reserved = options.reserved ?? 0;
|
|
1318
|
+
if (!Number.isFinite(reserved) || reserved < 0) {
|
|
1319
|
+
throw new RangeError(`reserved must be a finite, non-negative number, got ${reserved}`);
|
|
1320
|
+
}
|
|
1321
|
+
if (reserved) {
|
|
1322
|
+
this.consumeReservation(reserved);
|
|
1323
|
+
}
|
|
1139
1324
|
this.spentUsd += costUsd;
|
|
1140
1325
|
if (this.spentUsd - this.limitUsd > 0 && this.spentUsd - this.limitUsd < EPS) {
|
|
1141
1326
|
this.spentUsd = this.limitUsd;
|
|
1142
1327
|
}
|
|
1328
|
+
this.lastToolCost = costUsd;
|
|
1329
|
+
this.toolCostTotals[tool] = (this.toolCostTotals[tool] ?? 0) + costUsd;
|
|
1143
1330
|
this.appendEvent({
|
|
1144
1331
|
timestamp: Date.now() / 1e3,
|
|
1145
1332
|
kind: "tool",
|
|
@@ -1147,10 +1334,22 @@ var BudgetGuard = class {
|
|
|
1147
1334
|
promptTokens: null,
|
|
1148
1335
|
completionTokens: null,
|
|
1149
1336
|
costUsd,
|
|
1150
|
-
...options.label !== void 0 ? { label: options.label } : {}
|
|
1337
|
+
...options.label !== void 0 ? { label: options.label } : {},
|
|
1338
|
+
...reserved ? { reserved } : {}
|
|
1151
1339
|
});
|
|
1152
1340
|
return costUsd;
|
|
1153
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Accrue a non-LLM cost (a paid tool/API call) against the same ceiling.
|
|
1344
|
+
*
|
|
1345
|
+
* Post-hoc accrual for costs only known after the call (metered APIs); when
|
|
1346
|
+
* the price is known up front, {@link BudgetGuard.reserveTool} /
|
|
1347
|
+
* {@link BudgetGuard.settleTool} give the stronger pre-call hard-stop. See
|
|
1348
|
+
* `settleTool` for the full contract. Returns `costUsd`.
|
|
1349
|
+
*/
|
|
1350
|
+
recordTool(tool, costUsd, options = {}) {
|
|
1351
|
+
return this.settleTool(tool, costUsd, { reserved: 0, label: options.label });
|
|
1352
|
+
}
|
|
1154
1353
|
/**
|
|
1155
1354
|
* Drop an in-flight reservation without recording spend (e.g. the call failed
|
|
1156
1355
|
* before producing usage). Safe to call with `0`.
|
|
@@ -1160,16 +1359,25 @@ var BudgetGuard = class {
|
|
|
1160
1359
|
throw new RangeError(`reserved must be a finite, non-negative number, got ${reserved}`);
|
|
1161
1360
|
}
|
|
1162
1361
|
if (!reserved) return;
|
|
1163
|
-
this.
|
|
1362
|
+
this.consumeReservation(reserved);
|
|
1164
1363
|
}
|
|
1165
1364
|
/** USD left before the ceiling, net of in-flight reservations (never negative). */
|
|
1166
1365
|
get remainingUsd() {
|
|
1167
1366
|
return Math.max(0, this.limitUsd - this.spentUsd - this.reserved);
|
|
1168
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Per-tool running USD totals, keyed by the name given to `settleTool()` /
|
|
1370
|
+
* `recordTool()` — e.g. `{"apollo.people_lookup": 0.42, "exa.search": 0.11}`.
|
|
1371
|
+
* Makes the token/tool split of the one shared ceiling inspectable
|
|
1372
|
+
* (`spentUsd - sum of toolCosts` is the token side). Returns a snapshot copy.
|
|
1373
|
+
*/
|
|
1374
|
+
get toolCosts() {
|
|
1375
|
+
return { ...this.toolCostTotals };
|
|
1376
|
+
}
|
|
1169
1377
|
/**
|
|
1170
1378
|
* The per-call spend ledger, oldest first — one {@link SpendEvent} per priced
|
|
1171
|
-
* `record()` / `settle()` / `recordTool()`. Returns a
|
|
1172
|
-
* it cannot corrupt the ledger.
|
|
1379
|
+
* `record()` / `settle()` / `recordTool()` / `settleTool()`. Returns a
|
|
1380
|
+
* snapshot copy: mutating it cannot corrupt the ledger.
|
|
1173
1381
|
*/
|
|
1174
1382
|
get spendLog() {
|
|
1175
1383
|
return [...this.spendEvents];
|
|
@@ -1200,6 +1408,34 @@ var BudgetGuard = class {
|
|
|
1200
1408
|
`;
|
|
1201
1409
|
}).join("");
|
|
1202
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* The default next-call prediction when the caller supplies no estimate.
|
|
1413
|
+
* Conservative: the costlier of the last LLM call and the last tool call — a
|
|
1414
|
+
* mixed loop predicts the pricier kind, which at worst blocks one call early
|
|
1415
|
+
* (fail-closed) rather than letting a crossing call through because the LAST
|
|
1416
|
+
* event happened to be cheap.
|
|
1417
|
+
*/
|
|
1418
|
+
defaultEstimate() {
|
|
1419
|
+
return Math.max(this.lastLlmCost, this.lastToolCost);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Subtract a settled/released hold from the in-flight tally. A handle larger
|
|
1423
|
+
* than EVERYTHING currently held cannot have come from a matching
|
|
1424
|
+
* `reserve()` — throwing beats silently clamping, which would free OTHER
|
|
1425
|
+
* callers' holds and fail the ceiling open. The epsilon absorbs float dust
|
|
1426
|
+
* from accumulating and draining many holds; per-caller over-release (a
|
|
1427
|
+
* handle within the total but larger than the caller's own hold) is
|
|
1428
|
+
* undetectable without per-handle tracking and remains the caller's
|
|
1429
|
+
* responsibility.
|
|
1430
|
+
*/
|
|
1431
|
+
consumeReservation(reserved) {
|
|
1432
|
+
if (reserved > this.reserved + EPS) {
|
|
1433
|
+
throw new RangeError(
|
|
1434
|
+
`reserved handle (${reserved}) exceeds total in-flight reservations (${this.reserved}) \u2014 a handle must come from a matching reserve()`
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
this.reserved = Math.max(0, this.reserved - reserved);
|
|
1438
|
+
}
|
|
1203
1439
|
appendEvent(event) {
|
|
1204
1440
|
this.spendEvents.push(Object.freeze(event));
|
|
1205
1441
|
if (this.maxLogEvents !== void 0 && this.spendEvents.length > this.maxLogEvents) {
|