@bivelio/savings-layer 0.4.21 → 0.4.24
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 +173 -24
- package/dist/gateway/bin.js +746 -360
- package/dist/index.d.ts +463 -16
- package/dist/index.js +928 -731
- package/docs/terminales/README.md +83 -12
- package/docs/terminales/claude-code.md +4 -2
- package/docs/terminales/cline.md +1 -1
- package/docs/terminales/codex.md +12 -11
- package/docs/terminales/crush.md +1 -1
- package/docs/terminales/droid.md +1 -1
- package/docs/terminales/goose.md +1 -1
- package/docs/terminales/kilo.md +1 -1
- package/docs/terminales/opencode.md +1 -1
- package/docs/terminales/qwen-code.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
Every request your app sends to an AI is paid for by the text going in and out.
|
|
20
20
|
**Savings** sits on that path and lets through only what's needed — skipping calls
|
|
21
21
|
that don't have to happen, and trimming what does — so you pay for less. It is not a
|
|
22
|
-
promise you take on faith: **every saving is measured with the provider's
|
|
23
|
-
meter, the same one on your bill**, and
|
|
22
|
+
promise you take on faith: **every saving we charge on is measured with the provider's
|
|
23
|
+
official meter, the same one on your bill**, and every saving — measured or estimated,
|
|
24
|
+
and labelled as such — is handed back to you request by request.
|
|
24
25
|
|
|
25
26
|
**We only earn when you save.** Your first month carries no commission at all: we
|
|
26
27
|
measure everything and show you the statement of what it would have cost. After
|
|
@@ -40,8 +41,10 @@ goes straight from your app to your provider; the SDK is never a middleman serve
|
|
|
40
41
|
|
|
41
42
|
> **Commercial software — © 2026 BiVelio Inc.** Free to install, **paid to run**:
|
|
42
43
|
> BV‑SALA is fail‑closed and executes only with an active BiVelio license key.
|
|
43
|
-
>
|
|
44
|
-
>
|
|
44
|
+
> The license signature is verified **offline**; with `licenseServerUrl` set, the SDK
|
|
45
|
+
> also calls `savings.bivelio.com` for the revocation list, license renewal and usage
|
|
46
|
+
> metering. **Your prompts never leave your infrastructure** — what travels is token
|
|
47
|
+
> counts, cost, the model id and opaque seat/session ids, never content. Use is governed by the [LICENSE](./LICENSE)
|
|
45
48
|
> and the BiVelio Terms. Not a wrapper around any third‑party library.
|
|
46
49
|
|
|
47
50
|
## Where it installs
|
|
@@ -64,14 +67,55 @@ story, in plain words:
|
|
|
64
67
|
|
|
65
68
|
**Claude Code, Codex CLI, Goose or Qwen Code with an API key?** The package also
|
|
66
69
|
ships a local measuring gateway: `bvsala claude` (or `bvsala codex`, `bvsala
|
|
67
|
-
goose`, `bvsala qwen`) launches the terminal through it and your real spend
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
goose`, `bvsala qwen`) launches the terminal through it and your real spend shows
|
|
71
|
+
up in your dashboard, token by token — nothing to configure, nothing persisted.
|
|
72
|
+
For opencode, Crush, Droid, Cline or Kilo Code, `bvsala doctor` prints the exact
|
|
73
|
+
config block for each one you have installed. Like the SDK, the gateway is
|
|
74
|
+
fail‑closed: it starts only with your service key (`BIVELIO_SERVICE_KEY`) and an
|
|
75
|
+
active BiVelio license, which it collects with that key and verifies against the
|
|
76
|
+
key embedded in the package. Once running it never interrupts a request in flight:
|
|
77
|
+
if the license lapses, only new requests are refused (HTTP 402) until it is
|
|
78
|
+
renewed. It measures every request. With a PRO license it also applies one saving
|
|
79
|
+
by default: it shortens the cache-write TTL while your session stays hot, which
|
|
80
|
+
changes the price, never the content the model reads. The other levers (sibling
|
|
81
|
+
model, per-turn effort) stay off until you switch them on. On a Claude or ChatGPT
|
|
82
|
+
subscription there is no per-token invoice, so that traffic is metered at
|
|
83
|
+
list-price equivalent and never carries commission; the per-seat license applies
|
|
84
|
+
as for any other use.
|
|
85
|
+
|
|
86
|
+
### Python and any other language: the local gateway
|
|
87
|
+
|
|
88
|
+
The same gateway sits in front of any SDK that lets you set a base URL, whatever
|
|
89
|
+
the language:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bvsala gateway --upstream openai --port 8403 # one gateway per upstream
|
|
93
|
+
bvsala gateway # Anthropic, the default, on 8402
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from openai import OpenAI
|
|
98
|
+
client = OpenAI(base_url="http://127.0.0.1:8403/v1") # WITH /v1
|
|
99
|
+
stream = client.chat.completions.create(
|
|
100
|
+
model="gpt-4o-mini", messages=[{"role": "user", "content": "Hi"}], stream=True,
|
|
101
|
+
stream_options={"include_usage": True}, # without it a stream carries no usage and is not metered
|
|
102
|
+
)
|
|
103
|
+
for chunk in stream:
|
|
104
|
+
if chunk.choices: # the last chunk carries only the usage
|
|
105
|
+
print(chunk.choices[0].delta.content or "", end="")
|
|
106
|
+
|
|
107
|
+
from anthropic import Anthropic
|
|
108
|
+
claude = Anthropic(base_url="http://127.0.0.1:8402") # NO /v1
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
With `BIVELIO_SERVICE_KEY` set, the gateway measures every request whose usage the
|
|
112
|
+
provider reports — Anthropic Messages, OpenAI Chat Completions and Responses, streamed
|
|
113
|
+
or not — for models with a list price in its catalog, priced at that list price. A
|
|
114
|
+
request it cannot meter (a model without a list price, a body it cannot read, a stream
|
|
115
|
+
that never asked for usage) is still forwarded, and its log line says so and why. That
|
|
116
|
+
is measurement, not savings: the saving levers of the SDK are TypeScript, and the
|
|
117
|
+
gateway's own levers apply to Anthropic `/v1/messages` traffic with a PRO license.
|
|
118
|
+
`bvsala doctor` prints the recipe for each SDK.
|
|
75
119
|
|
|
76
120
|
## Check it before you install
|
|
77
121
|
|
|
@@ -87,25 +131,34 @@ lives in your dashboard once you sign in.
|
|
|
87
131
|
**1 · The package**
|
|
88
132
|
|
|
89
133
|
```bash
|
|
90
|
-
pnpm add @bivelio/savings-layer
|
|
91
|
-
# or: npm i @bivelio/savings-layer · yarn add @bivelio/savings-layer
|
|
134
|
+
pnpm add @bivelio/savings-layer gpt-tokenizer
|
|
135
|
+
# or: npm i @bivelio/savings-layer gpt-tokenizer · yarn add @bivelio/savings-layer gpt-tokenizer
|
|
92
136
|
```
|
|
93
137
|
|
|
138
|
+
`gpt-tokenizer` is optional, but install it if you call OpenAI models: it is the
|
|
139
|
+
provider's own tokenizer, and the SDK loads it by itself. Without it, counts fall back
|
|
140
|
+
to a built‑in estimate and the reconstructed savings are less accurate. Either way, a
|
|
141
|
+
saving where the layer removed something is reported as `estimated`, and estimated
|
|
142
|
+
savings are never billed — the SDK warns once when the tokenizer is missing.
|
|
143
|
+
|
|
94
144
|
**2 · The license** — from your [dashboard](https://savings.bivelio.com/dashboard/install)
|
|
95
145
|
(**€4 / seat / month** or **€39 / seat / year**, EUR)
|
|
96
146
|
|
|
97
147
|
```bash
|
|
98
148
|
export BIVELIO_LICENSE_KEY=… # your license key
|
|
99
|
-
export BIVELIO_SERVICE_KEY=… # service-account key: auto-renewal + seat metering
|
|
149
|
+
export BIVELIO_SERVICE_KEY=… # service-account key: auto-renewal + seat metering (required by the gateway)
|
|
100
150
|
```
|
|
101
151
|
|
|
102
|
-
**3 · Wrap the call** — this is the entire code change:
|
|
152
|
+
**3 · Wrap the call** — this is the entire code change. The package is ESM only:
|
|
153
|
+
put this in a `"type": "module"` project or a `.mts` file.
|
|
103
154
|
|
|
104
155
|
```ts
|
|
105
156
|
import { createSavingsLayer, OpenAICompatibleProvider } from "@bivelio/savings-layer";
|
|
106
157
|
|
|
107
158
|
// Your provider. LLM traffic goes straight to it — the SDK never proxies it.
|
|
108
159
|
// Any OpenAI-compatible endpoint works: OpenAI, Azure, vLLM, SGLang, a gateway.
|
|
160
|
+
// Model ids are qualified (`openai/gpt-4o-mini`); the prefix is stripped on the
|
|
161
|
+
// wire for OpenAI and Azure and kept for gateways — see `stripProviderPrefix`.
|
|
109
162
|
const provider = new OpenAICompatibleProvider({
|
|
110
163
|
baseUrl: "https://api.openai.com/v1",
|
|
111
164
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -114,7 +167,7 @@ const provider = new OpenAICompatibleProvider({
|
|
|
114
167
|
const layer = createSavingsLayer({
|
|
115
168
|
provider,
|
|
116
169
|
licenseKey: process.env.BIVELIO_LICENSE_KEY, // required — from your savings.bivelio.com dashboard
|
|
117
|
-
licenseServerUrl: "https://savings.bivelio.com", //
|
|
170
|
+
licenseServerUrl: "https://savings.bivelio.com", // revocation list, renewal + metering — omit it and the two lines below do nothing
|
|
118
171
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY, // seat metering + AUTO-RENEWAL pickup
|
|
119
172
|
});
|
|
120
173
|
|
|
@@ -124,12 +177,56 @@ const { text, report } = await layer.generate({
|
|
|
124
177
|
});
|
|
125
178
|
|
|
126
179
|
console.log(text); // "8" (computed locally — no LLM call)
|
|
127
|
-
console.log(report.calls.avoidedLlm); // → whole
|
|
128
|
-
console.log(report.cost.netSavings); // →
|
|
180
|
+
console.log(report.calls.avoidedLlm); // → 1: a whole call you never paid for
|
|
181
|
+
console.log(report.cost.netSavings); // → 0..1 ratio of the baseline you did not pay (1 here)
|
|
182
|
+
console.log(report.cost.baselineEstimate - report.cost.actual); // → the same saving in USD
|
|
183
|
+
console.log(report.cost.basis); // → "estimated": nothing ran, so there is no provider counter
|
|
129
184
|
```
|
|
130
185
|
|
|
131
|
-
That's it. From the first request, `report` carries the saving
|
|
132
|
-
|
|
186
|
+
That's it. From the first request, `report` carries the saving and says how it
|
|
187
|
+
was obtained: `actual` is what the provider metered; `basis` is `"measured"` when
|
|
188
|
+
the request went through untouched, `"reference"` when a repeat of a byte‑identical
|
|
189
|
+
request was served without calling the provider — its baseline is the provider's own
|
|
190
|
+
counter for the identical request that already ran and that you already paid
|
|
191
|
+
(`report.cache.reference` points to it; with an output schema, only when your schema
|
|
192
|
+
traveled exactly as sent, in strict mode, in the provider's native response‑format
|
|
193
|
+
parameter, with nothing added to the prompt), valued at the provider's cached‑input
|
|
194
|
+
rate, the lowest it bills for repeating a prompt; the output counts only under deterministic
|
|
195
|
+
sampling (`temperature: 0`), on a model with a list price in the catalog that does not
|
|
196
|
+
reason and honors that setting, and when the call that ran carried no hidden reasoning (the
|
|
197
|
+
provider reported zero reasoning tokens or, where it does not break them out, its answer
|
|
198
|
+
had no reasoning block), and then at 95 % of its counter, a margin for the small drift
|
|
199
|
+
`temperature: 0` still has —
|
|
200
|
+
`"estimated"` when the layer removed something (the baseline is then reconstructed
|
|
201
|
+
with the model's tokenizer, or with a calibrated estimate where no exact tokenizer
|
|
202
|
+
exists, as for Anthropic models), and `"unmeasured"` when the provider returned no
|
|
203
|
+
usage. Only `measured` and `reference` figures reach your invoice; estimated ones
|
|
204
|
+
never do.
|
|
205
|
+
|
|
206
|
+
**Streaming.** `generateStream()` takes the same request and hands you the answer as
|
|
207
|
+
the provider writes it, then the same `SavingsReport`:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const stream = layer.generateStream({
|
|
211
|
+
model: "openai/gpt-4o-mini",
|
|
212
|
+
messages: [{ role: "user", content: "Summarize the release notes in one line." }],
|
|
213
|
+
});
|
|
214
|
+
for await (const event of stream) {
|
|
215
|
+
if (event.type === "text") process.stdout.write(event.delta);
|
|
216
|
+
}
|
|
217
|
+
const { report: streamed } = await stream.result;
|
|
218
|
+
console.log(streamed.cost.basis); // same rules as generate(), read from the provider's final usage
|
|
219
|
+
console.log(streamed.streaming?.delivery); // "live", or "whole" when the answer arrived in one piece
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The SDK asks the provider for its final usage at the end of every stream (on
|
|
223
|
+
OpenAI‑compatible endpoints, `stream_options.include_usage`); a stream that ends
|
|
224
|
+
without it is reported as `unmeasured` and never billed. With an output contract, or
|
|
225
|
+
a request declared `deferrable`, the answer is produced exactly as `generate()` would
|
|
226
|
+
and arrives as a single `text` event. Breaking out of the loop while the provider is
|
|
227
|
+
still writing cancels its request, and nothing is cached or recorded; once its answer
|
|
228
|
+
is complete, or when it arrives in one piece, nothing is cancelled.
|
|
229
|
+
`await stream.result` tells you which happened.
|
|
133
230
|
|
|
134
231
|
## What you get on every request
|
|
135
232
|
|
|
@@ -148,6 +245,56 @@ are chosen by counting real tokens with the destination model's own tokenizer, a
|
|
|
148
245
|
BV‑SALA only moves away from plain JSON when the saving clearly clears the bar —
|
|
149
246
|
`report.serialization` shows what was chosen and what each candidate would have cost.
|
|
150
247
|
|
|
248
|
+
<details>
|
|
249
|
+
<summary><b>Your own A/B — verification sampling (opt-in, off by default)</b></summary>
|
|
250
|
+
|
|
251
|
+
The per-request figures above are what the layer can show without running your
|
|
252
|
+
request twice. To see what it does on your own traffic, with both sides metered by
|
|
253
|
+
your provider, turn on verification sampling:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const checkedLayer = createSavingsLayer({
|
|
257
|
+
provider,
|
|
258
|
+
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
259
|
+
licenseServerUrl: "https://savings.bivelio.com",
|
|
260
|
+
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
261
|
+
verification: { sampleRate: 0.02 }, // about 2 in every 100 eligible requests
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
For that share of eligible requests, after your answer has been returned, the SDK
|
|
266
|
+
sends the same request once more to your provider exactly as it would go without the
|
|
267
|
+
layer — the model you asked for, all your tools, your data inline, no cache marks —
|
|
268
|
+
and discards the second answer. Each pair (what the unoptimized copy cost and what the
|
|
269
|
+
optimized request cost, both from your provider's usage counters) appears under
|
|
270
|
+
**Proof › Your bench** in your dashboard, with the mean difference, its 95 % interval
|
|
271
|
+
and every pair as a CSV download.
|
|
272
|
+
|
|
273
|
+
- **It costs you money.** You pay your provider for every copy: roughly the sampling
|
|
274
|
+
rate times what that traffic would cost without the layer. The SDK says so once
|
|
275
|
+
per process when it starts (`silenceWarnings: true` mutes that notice too).
|
|
276
|
+
- **It is never billed.** Pairs travel on their own channel into their own table.
|
|
277
|
+
They never enter measured savings, your statement or any commission.
|
|
278
|
+
- **Only requests served intact are checked.** Requests that took the fail-open path,
|
|
279
|
+
were cut off, missed their output contract or came back without a provider counter
|
|
280
|
+
are counted as excluded and never sent twice. `savings: { verification: false }`
|
|
281
|
+
excludes a single request.
|
|
282
|
+
- **Both sides follow the same rule.** A pair is compared only when the copy also
|
|
283
|
+
finished normally and met your contract; a copy cut off by the output limit is
|
|
284
|
+
counted and exported, never scored as a saving.
|
|
285
|
+
- **The headline is the floor, and it waits.** It is the lower bound of the 95 %
|
|
286
|
+
interval, shown once there are enough pairs overall and groups with too few pairs
|
|
287
|
+
of their own are a small share of your traffic, so a group where the layer costs
|
|
288
|
+
you money cannot be left out of it.
|
|
289
|
+
- **Only counts leave your infrastructure.** The copy goes to your own provider, like
|
|
290
|
+
the original; BiVelio receives token counts, costs, model ids and outcomes — never
|
|
291
|
+
prompts or answers.
|
|
292
|
+
- **`close()` settles every copy still running:** it is recorded as abandoned, never
|
|
293
|
+
as a pair. On serverless platforms, pass your runtime's `waitUntil` as
|
|
294
|
+
`verification.waitUntil` so checks can finish.
|
|
295
|
+
|
|
296
|
+
</details>
|
|
297
|
+
|
|
151
298
|
<details>
|
|
152
299
|
<summary><b>Structured output — validated <code>data</code>, not just text</b></summary>
|
|
153
300
|
|
|
@@ -243,8 +390,10 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
|
|
|
243
390
|
|
|
244
391
|
- **Node ≥ 20.** The package is **ESM‑only** (`"type": "module"`); from CommonJS,
|
|
245
392
|
`require()` works on Node ≥ 22.
|
|
246
|
-
- Optional peer `gpt-tokenizer` for exact OpenAI token counts
|
|
247
|
-
built‑in estimator is used
|
|
393
|
+
- Optional peer `gpt-tokenizer` for exact OpenAI token counts, loaded automatically
|
|
394
|
+
when installed — without it a built‑in estimator is used. It improves the accuracy
|
|
395
|
+
of estimated figures; what reaches your invoice is `measured` and `reference`, which
|
|
396
|
+
come from the provider's own counter either way.
|
|
248
397
|
- Every component (cache, vector store, tokenizer, compressor, constraint backend)
|
|
249
398
|
ships with an in‑memory default and is independently swappable behind the same
|
|
250
399
|
interfaces.
|
|
@@ -255,7 +404,7 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
|
|
|
255
404
|
|
|
256
405
|
BV‑SALA is commercial, per‑seat software (**€4 / seat / month** or **€39 / seat /
|
|
257
406
|
year**, EUR; VAT not included). No free tier and no trial: what you can check before
|
|
258
|
-
paying is the published measurement. The license is a signed token your SDK
|
|
407
|
+
paying is the published measurement. The license is a signed token your SDK and the gateway verify **offline**; the
|
|
259
408
|
managed control plane and the revocation kill‑switch are served from BiVelio. Your
|
|
260
409
|
LLM traffic goes **straight to your provider** — BiVelio sees counts and cost,
|
|
261
410
|
never prompts or completions.
|