floe-guard 0.15.6 → 0.16.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 CHANGED
@@ -34,6 +34,79 @@ The middleware sits in the call path: it `check()`s before `doGenerate` /
34
34
  `doStream` (throwing `BudgetExceeded` to halt the run) and `record()`s priced
35
35
  token usage after — for streaming it reads usage from the `finish` part.
36
36
 
37
+ ## Mid-stream budget enforcement
38
+
39
+ `StreamGuard` and `guardStream` port Python's streaming USD guard. They price
40
+ each text delta before it reaches the consumer. When a chunk crosses the
41
+ shared ceiling, its partial spend is recorded in `guard.spendLog` **before**
42
+ `BudgetExceeded` is thrown. Active streams on the same guard share the ceiling.
43
+
44
+ ```ts
45
+ import { BudgetGuard, guardStream } from "floe-guard";
46
+
47
+ const guard = new BudgetGuard(0.01);
48
+ // textDeltas is your provider's Iterable<string> or AsyncIterable<string>.
49
+ for await (const text of guardStream(guard, "gpt-4o", textDeltas)) {
50
+ consume(text);
51
+ }
52
+ ```
53
+
54
+ For structured chunks, supply `{ getText: chunk => chunk.delta.text ?? "" }`
55
+ using your provider's actual shape. Without an extractor, non-string chunks
56
+ throw instead of silently recording zero. Synchronous inputs return a
57
+ synchronous iterator; asynchronous inputs return an asynchronous iterator.
58
+
59
+ Use `StreamGuard` directly when the provider reports final usage:
60
+
61
+ ```ts
62
+ import { StreamGuard } from "floe-guard";
63
+
64
+ const reserved = guard.reserve(guard.estimateCall("gpt-4o", 100, 200));
65
+ const stream = new StreamGuard(guard, "gpt-4o", { promptTokens: 100, reserved });
66
+ try {
67
+ for await (const text of textDeltas) {
68
+ stream.feedText(text); // or feedTokens(n) with known per-chunk counts
69
+ consume(text);
70
+ }
71
+ stream.finish({ promptTokens: reportedPromptTokens, completionTokens: reportedCompletionTokens });
72
+ } finally {
73
+ stream.close(); // idempotent; settles estimates if finish() was not reached
74
+ }
75
+ ```
76
+
77
+ - Options: `promptTokens`, `reserved`, `price`, `label`, and `countTokens(delta)`.
78
+ `guardStream` additionally accepts `getText(chunk)`.
79
+ Configuration is captured at construction; changing the caller's options or
80
+ manual price object later does not change an existing stream.
81
+ - `approxTokens` defaults to roughly four Unicode characters per token, with
82
+ a minimum of one for a non-empty delta. A custom tokenizer can replace it.
83
+ `finish()` reconciles to reported usage; `completionTokens` exposes the running
84
+ estimate. Mid-stream checks cover the aggregate USD ceiling, matching Python;
85
+ token reservations are reconciled at settlement.
86
+ - An unpriceable model fails at construction and releases its reservation.
87
+ With `failClosed: false`, unpriceable streams pass through and settlement
88
+ warns and skips accounting, matching the existing guard policy.
89
+ - The wrapper settles on exhaustion, source/consumer errors, and early `break`,
90
+ and closes the source iterator when iteration ends early. Direct users must
91
+ call `close()` in `finally`. If a wrapper is **never iterated**, its reservation
92
+ remains the caller's responsibility: call `guard.release(reserved)`.
93
+ - The crossing chunk has already been generated. With accurate counts, the
94
+ local cutoff can overshoot by that chunk; heuristic error, parallel streams,
95
+ provider buffering and delayed cancellation can cause additional actual
96
+ spend. Stopping iteration requests iterator cleanup, not guaranteed remote
97
+ cancellation. Connect cleanup to your provider's abort mechanism where needed.
98
+ - Existing Vapi, LiveKit and middleware streaming behavior is unchanged; this
99
+ is the standalone primitive requested in issue #124.
100
+
101
+ Run the no-key example from a repository checkout:
102
+
103
+ ```bash
104
+ cd js
105
+ npm ci
106
+ npm run build
107
+ node ../examples/streaming_guard.mjs
108
+ ```
109
+
37
110
  ## Pricing
38
111
 
39
112
  Tokens are priced **offline** from a bundled
@@ -76,7 +76,7 @@ function preCall(guard, options = {}) {
76
76
  // src/cost_map.json
77
77
  var cost_map_default = {
78
78
  __meta__: {
79
- generated_at: "2026-09-07",
79
+ generated_at: "2026-09-14",
80
80
  source: "LiteLLM public model prices (bundled snapshot); voice rates verified from vendor list pages"
81
81
  },
82
82
  "chat-latest": {
@@ -334,6 +334,12 @@ var cost_map_default = {
334
334
  cache_read_input_token_cost: 2e-7,
335
335
  cache_creation_input_token_cost: 25e-7
336
336
  },
337
+ "computer-use-preview": {
338
+ input_cost_per_token: 3e-6,
339
+ output_cost_per_token: 12e-6,
340
+ litellm_provider: "openai",
341
+ mode: "chat"
342
+ },
337
343
  "daybreak-blue-latest": {
338
344
  input_cost_per_token: 4e-6,
339
345
  output_cost_per_token: 2e-5,