@bivelio/savings-layer 0.4.22 → 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.
@@ -11,8 +11,12 @@ terminal). Cada afirmación va marcada **[V]** (verificada en fuente) o **[I]**
11
11
  cada petición **byte a byte** al proveedor (`https://api.anthropic.com` por
12
12
  defecto) y, una vez servida la respuesta, lee el `usage` del stream y lo
13
13
  convierte en coste. Tus prompts jamás salen de tu máquina hacia BiVelio — solo
14
- cuentas y coste. Nunca corta el tráfico: sin clave o con la ingesta caída,
15
- reenvía y avisa.
14
+ cuentas y coste. Es fail-closed como el SDK: arranca solo con tu clave de
15
+ servicio (`BIVELIO_SERVICE_KEY`) y una licencia BiVelio activa, que recoge con
16
+ esa clave y cuya firma verifica en tu máquina. Una vez arrancado, jamás corta
17
+ una petición en curso: si la licencia caduca o se revoca, solo las peticiones
18
+ NUEVAS reciben un HTTP 402 hasta renovarla. La medición es best-effort: con la
19
+ ingesta caída, reenvía y avisa.
16
20
 
17
21
  Mide cada petición. Con licencia PRO aplica por defecto **una sola palanca**:
18
22
  rebaja el TTL de escritura de caché mientras la sesión sigue caliente — cambia la
@@ -21,6 +25,11 @@ por turno) van apagadas hasta que las enciendes. Y mide **todo el tráfico de in
21
25
  que el terminal enruta por el base URL — no «todo el tráfico»: cada guía dice
22
26
  lo que su terminal manda por fuera.
23
27
 
28
+ En suscripción (login de claude.ai en Claude Code, cuenta ChatGPT en Codex) no
29
+ hay factura por tokens: ese tráfico se mide a precio de lista equivalente y
30
+ nunca se le acredita ahorro ni comisión; la licencia por asiento hace falta
31
+ igual.
32
+
24
33
  ## Índice
25
34
 
26
35
  | Guía | Base URL exacto | `/v1` | Enrutado [V] | Envoltorio | Estado |
@@ -34,6 +43,7 @@ lo que su terminal manda por fuera.
34
43
  | [Droid](droid.md) | `http://127.0.0.1:8402` | sin **[I]** | `~/.factory/settings.json` | — (config de usuario) | validar en mano |
35
44
  | [Cline](cline.md) | `http://127.0.0.1:8402/v1` | **con** | UI | — | [V] |
36
45
  | [Kilo Code](kilo.md) | `http://127.0.0.1:8402/v1` | con **[I]** | UI | — | validar en mano |
46
+ | [Python y cualquier SDK](../../README.md#python-and-any-other-language-the-local-gateway) (OpenAI · Anthropic) | OpenAI: `http://127.0.0.1:8403/v1` · Anthropic: `http://127.0.0.1:8402` | OpenAI **con**, Anthropic **sin** | `base_url` del SDK | `bvsala gateway --upstream openai` | [V] en test (upstream falso) |
37
47
 
38
48
  Reglas de la columna «Base URL»: **nunca barra final**, **nunca redirecciones**
39
49
  (un 3xx en el base URL es fallo por diseño del cliente, y el gateway lo reenvía
@@ -52,25 +62,43 @@ receta manual de cada guía hace exactamente lo mismo. Para el resto,
52
62
 
53
63
  ```bash
54
64
  npm i -g @bivelio/savings-layer # trae el binario `bvsala`
55
- export BIVELIO_SERVICE_KEY=bvk_… # clave de servicio, desde tu panel. Sin ella: reenvía, NO mide
65
+ export BIVELIO_SERVICE_KEY=bvk_… # clave de servicio, desde tu panel. Obligatoria: sin ella el gateway no arranca
56
66
  bvsala gateway # escucha en http://127.0.0.1:8402
57
- bvsala gateway --puerto 8500 # otro puerto si el 8402 está ocupado
67
+ bvsala gateway --port 8500 # otro puerto si el 8402 está ocupado
58
68
  ```
59
69
 
60
- Al arrancar imprime dos líneas; la segunda te dice si estás midiendo:
70
+ Al arrancar imprime, entre otras, estas líneas:
61
71
 
62
72
  ```
63
- [bvsala gateway] v0.4.4 escuchando en http://127.0.0.1:8402
64
- [bvsala gateway] medición: ACTIVA (clave de servicio presente)
73
+ [bvsala gateway] v0.4.x listening on http://127.0.0.1:8402 → https://api.anthropic.com
74
+ [bvsala gateway] license: PRO, active until 2026-10-24 (+14 days grace), collected with your service key and verified on this machine
75
+ [bvsala gateway] metering: ON (service key present)
65
76
  ```
66
77
 
67
- `medición: APAGADA (falta BIVELIO_SERVICE_KEY)` = el terminal funciona igual,
68
- pero nada llega a tu panel.
78
+ Si no arranca (sale con código 1), el mensaje dice exactamente qué falta:
79
+
80
+ | Mensaje | Qué pasa | Arreglo |
81
+ |---|---|---|
82
+ | `BIVELIO_SERVICE_KEY is not set` | falta la clave de servicio | créala en el panel (Instalar, paso 2), expórtala y relanza |
83
+ | `BIVELIO_SERVICE_KEY is not recognised` | clave borrada, rotada o copiada a medias | crea otra |
84
+ | `your organization has no active BiVelio license` | no hay suscripción | suscríbete en Cuenta y relanza |
85
+ | `your BiVelio license expired on …` | licencia caducada y sin gracia | renueva en Cuenta |
86
+ | `could not collect your license from savings.bivelio.com` | sin acceso a savings.bivelio.com | revisa la conexión; como respaldo, `BIVELIO_LICENSE_KEY` (se verifica sin red y se sustituye a mano cada período) |
87
+ | un HTTP 402 en el terminal a mitad de sesión | la licencia caducó o se revocó con la sesión abierta | renueva; las peticiones que ya estaban en curso no se cortaron |
88
+
89
+ Si lanzas `bvsala gateway` como servicio (launchd/systemd), un fallo de licencia
90
+ queda en el log del servicio con código 1 y el terminal solo verá «connection
91
+ refused»: mira ese log.
69
92
 
70
93
  Un gateway habla con **un** upstream (`BVSALA_GATEWAY_UPSTREAM`, por defecto
71
94
  `https://api.anthropic.com`). Los terminales del dialecto Anthropic comparten
72
95
  instancia; Codex necesita la suya contra OpenAI — `bvsala codex` la arranca
73
- sola con el upstream correcto según tu modo de auth (ver su guía).
96
+ sola con el upstream correcto según tu modo de auth (ver su guía). Desde la
97
+ próxima versión, `bvsala gateway --upstream openai|anthropic|<origen>` lo elige
98
+ sin variable de entorno (el flag manda sobre `BVSALA_GATEWAY_UPSTREAM`). Para un
99
+ SDK de OpenAI (Python, Node…) arranca uno con `--upstream openai` en otro puerto;
100
+ en streaming, la petición tiene que llevar `stream_options.include_usage` o el
101
+ gateway la reenvía sin medirla y lo dice en el log.
74
102
 
75
103
  ### 2 · Comprobar que mide: el log del gateway
76
104
 
@@ -88,13 +116,56 @@ las otras líneas:
88
116
  |---|---|---|
89
117
  | `[gateway] POST /v1/v1/messages → 404` | el base URL lleva `/v1` **de más** para ese terminal | quita el `/v1` |
90
118
  | `[gateway] POST /messages → 404` | al base URL le **falta** `/v1` (el SDK solo añade `/messages`) | añade el `/v1` |
91
- | `[bvsala gateway] el modelo "…" no está en el catálogo de precios; su uso se reenvía pero no se mide.` | modelo fuera de catálogo (a propósito: presente-y-mal es peor que ausente) | nada; se reenvía sin medir |
119
+ | `[gateway] POST /v1/messages → 402 (license expired)` | la licencia caducó, se revocó o la org está bloqueada: las peticiones NUEVAS se rechazan (las que estaban en curso terminaron) | renueva en Cuenta; el terminal enseña el motivo |
92
120
  | nada de nada | el terminal no está apuntando al gateway (config no leída, env pisado por config, otro puerto) | revisa la trampa de su guía |
93
121
 
122
+ Cada petición que el gateway reenvía **sin medir** deja su propia línea con
123
+ `· not metered: <motivo>`, y en su lugar no se estima nada. Los motivos, tal y
124
+ como salen hoy:
125
+
126
+ | `· not metered: …` | Qué pasa | Arreglo |
127
+ |---|---|---|
128
+ | `no BIVELIO_SERVICE_KEY` | un gateway arrancado desde código sin clave de servicio: reenvía, no mide (el CLI `bvsala` ni arranca sin ella) | exporta `BIVELIO_SERVICE_KEY` |
129
+ | `model "…" has no list price in the catalog` | modelo fuera del catálogo de precios, a propósito: un precio presente y equivocado es peor que ninguno (la consola lo avisa una vez por modelo) | nada; se reenvía sin medir |
130
+ | `could not read the model from the request body (content-encoding: …)` | el cuerpo no se pudo leer: JSON ilegible o una compresión que este Node no abre (zstd, el de Codex, necesita Node ≥ 22.15) | actualiza Node |
131
+ | `the request did not ask for usage (stream_options.include_usage)` | Chat Completions en stream sin `include_usage`: el proveedor no manda contador | añade `stream_options={"include_usage": True}` a la llamada, o arranca con `BVSALA_GATEWAY_USAGE_INJECT=1` |
132
+ | `…; usage injection skips compressed request bodies` | la inyección está puesta, pero solo añade la opción a cuerpos sin comprimir | pídelo en la llamada |
133
+ | `…, and the upstream rejects that option` | el upstream rechazó `stream_options` y la inyección quedó apagada para el resto del proceso | en stream no hay arreglo desde el gateway; con `stream: false` sí se mide |
134
+ | `the stream ended without the provider's final usage event` | el stream se cortó antes de su evento final (`message_delta`, `response.completed`/`incomplete`/`failed`, el chunk de uso o `[DONE]`): sin él no hay contador | nada |
135
+ | `the response ended as response.failed without usage` | la Responses API cerró con `response.failed` sin `usage`. Si lo trae, sí se mide | nada |
136
+ | `the response ended as response.incomplete without usage` | la Responses API cerró con `response.incomplete` sin `usage`. Si lo trae, sí se mide: un `response.incomplete` por `max_output_tokens` o por el filtro de contenido lo factura el proveedor | nada |
137
+ | `the response carried no usage` | respuesta JSON (`stream: false`) sin `usage` | nada |
138
+ | `the response was too large to read its usage` | respuesta JSON de más de 5 MB: el gateway no la guarda entera para leerla | nada |
139
+ | `the response is neither JSON nor an event stream (content-type: …)` | el upstream contestó con otro tipo de contenido en una ruta que sí sabe medir | revisa el upstream |
140
+ | `no usage dialect for this route` | ruta sin contador que el gateway sepa leer (embeddings, imágenes, audio…): solo mide `/v1/messages`, `…/chat/completions` y `…/responses` | nada |
141
+
142
+ Con `BVSALA_GATEWAY_COALESCE=1` (apagada por defecto), una petición idéntica a
143
+ otra que sigue en vuelo no viaja: recibe la réplica y su línea lleva
144
+ `· COALESCED (call avoided)`. Detrás va `· by reference: floor $… (cache read
145
+ price, …)` cuando se valora con el contador de la petición que sí viajó —a
146
+ precio de lectura de caché, sin primas y con la salida solo si el cuerpo trae
147
+ `temperature: 0` y el modelo no razona (la misma guarda de modelo que el SDK)—,
148
+ o `· estimated, not by reference: <motivo>` cuando no. Esa línea es la
149
+ valoración local: el panel todavía recibe estas peticiones como `estimated`, y
150
+ no entran en la base de la comisión, porque el gateway aún no lee los tokens de
151
+ razonamiento que declara el proveedor y la regla `reference-v2` los exige.
152
+
153
+ | `· estimated, not by reference: …` | Por qué no |
154
+ |---|---|
155
+ | `no-primary-record` | la petición que viajó no dejó su contador final (ni evento final del stream ni `usage` en el JSON) |
156
+ | `subscription` | tráfico de suscripción (Claude o ChatGPT): no hay factura por token |
157
+ | `unofficial-upstream` | el gateway no habla con `api.anthropic.com` ni `api.openai.com` (un mock, un proxy, Azure, un revendedor): el precio de lista no está en ninguna factura |
158
+ | `layer-changed-request` | una palanca cambió lo que viajó (modelo, cuerpo, cabeceras; también la rebaja de TTL de la caché): ya no es la petición del cliente byte a byte |
159
+ | `primary-failed` | la petición que viajó cerró con `response.failed` |
160
+ | `client-disconnected` | el cliente de esta petición se fue antes de recibir la réplica entera |
161
+ | `service-tier` | el `service_tier` pedido o el servido no tiene precio por token publicado (`scale`, uno nuevo) |
162
+ | `no-cached-price` | el modelo no tiene precio de lectura de caché en su propia fila del catálogo |
163
+ | `no-credit` | el contador no deja nada que acreditar |
164
+
94
165
  Claude Code manda además un `HEAD /api/hello` antes de la primera llamada: es
95
166
  la sonda de vida, no una inferencia.
96
167
 
97
- `bvsala doctor [--puerto N]`: sondea el PATH y la existencia de las configs de
168
+ `bvsala doctor [--port N]`: sondea el PATH y la existencia de las configs de
98
169
  usuario (nunca lee su contenido) e imprime el bloque exacto por terminal con el
99
170
  puerto indicado. Solo imprime: no escribe ninguna configuración.
100
171
 
@@ -12,8 +12,10 @@ construcción, con test.
12
12
  `apiKeyHelper`): es el segmento que BV-SALA mide y factura. Con login de
13
13
  claude.ai el enrutado también funciona — la doc oficial lo sanciona: el login
14
14
  sigue siendo la credencial activa y «its usage limits and billing apply» [V] —
15
- pero ahí no hay factura por token que recortar.
16
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
15
+ pero ahí no hay factura por token: el gateway lo mide a precio de lista
16
+ equivalente y nunca le acredita ahorro ni comisión (la licencia por asiento
17
+ hace falta igual).
18
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
17
19
 
18
20
  ## Envoltorio: `bvsala claude` (recomendado)
19
21
 
@@ -14,7 +14,7 @@ que se usa.
14
14
 
15
15
  - Cline instalado en VS Code.
16
16
  - Tu **API key** de Anthropic.
17
- - `bvsala gateway` corriendo en `http://127.0.0.1:8402` (ver [README](README.md)).
17
+ - `bvsala gateway` corriendo en `http://127.0.0.1:8402` (necesita `BIVELIO_SERVICE_KEY` y una licencia BiVelio activa; ver [README](README.md)).
18
18
 
19
19
  ## Pasos en la UI
20
20
 
@@ -11,9 +11,10 @@ cifras.
11
11
 
12
12
  - `codex` en el PATH.
13
13
  - Login en Codex: **API key de OpenAI** (el caso que se monetiza) o
14
- suscripción ChatGPT (se enruta y se mide, pero **no se monetiza**: no hay
15
- factura por tokens; ver más abajo).
16
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
14
+ suscripción ChatGPT (se enruta y se mide a precio de lista equivalente, **sin
15
+ comisión**: no hay factura por tokens; la licencia por asiento hace falta
16
+ igual; ver más abajo).
17
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
17
18
 
18
19
  ## Envoltorio: `bvsala codex [args…]`
19
20
 
@@ -92,26 +93,26 @@ que el tráfico real enseñó y el gateway ya contempla:
92
93
  de Codex frente a su backend: aparece igual **sin** gateway (comprobado).
93
94
  4. **Barra final.** `…/v1/` no.
94
95
 
95
- ## Modo suscripción ChatGPT — validado, medido, NO monetizado
96
+ ## Modo suscripción ChatGPT — validado, medido, sin comisión
96
97
 
97
98
  Validado en mano el 30-08 (prefijo `/backend-api/codex`, upstream
98
99
  `https://chatgpt.com`): 200, stream íntegro, `usage` presente en
99
100
  `response.completed` y registro `measured` en la ingesta. **Pero un suscriptor
100
101
  no paga por tokens**: la cifra que ve es el equivalente a precio de lista por
101
102
  API key, no una factura; y como la comisión de BV-SALA solo toca ahorro medido
102
- sobre gasto real, este tráfico **no se monetiza** — igual que el OAuth de
103
+ sobre gasto real, este tráfico **no genera comisión** — igual que el OAuth de
103
104
  suscripción en Claude Code (`docs/PASARELA-F0.md`: «el caso que NO
104
105
  facturamos»). OpenAI ni bendice ni prohíbe el proxy de suscripción (silencio
105
106
  del staff, a diferencia de Anthropic, que lo documenta). Postura idéntica a
106
107
  Claude Code: cero persistencia de `Authorization` / `ChatGPT-Account-ID`,
107
108
  binario sin modificar, y no se promociona como modo de ahorro.
108
109
 
109
- **Laguna conocida:** el registro que emite el gateway para un suscriptor es hoy
110
- **idéntico** al de una API key (`basis: measured`, coste a precio de lista) —
111
- el panel no puede distinguirlos. Mientras la comisión sea 0 no hay efecto; antes
112
- de que S4 calcule comisión sobre ahorro medido, S2 etiquetará la auth de
113
- suscripción en el wire para que ese tráfico jamás entre en la base. Está en el
114
- plan (`docs/PLAN-PASARELA-API-KEY.md`, S3).
110
+ **Comisión 0 por diseño (desde la versión en que el gateway exige licencia):**
111
+ el gateway clasifica como suscripción el tráfico de `/backend-api/…` o con
112
+ upstream chatgpt.com (y el de Claude Code con `anthropic-beta: oauth-*`) y no le
113
+ acredita ahorro: `baseline == actual`. Laguna que queda: el registro no lleva
114
+ etiqueta propia en el wire, así que el panel no distingue suscripción de API key
115
+ (exige migración; fuera de este frente).
115
116
 
116
117
  ## Validaciones en mano
117
118
 
@@ -10,7 +10,7 @@ relativa, y eso dicta la forma del URL: **con `/v1`, sin barra final**.
10
10
  - `crush` instalado.
11
11
  - `ANTHROPIC_API_KEY` exportada (Crush retiró su OAuth de Claude en ene-2026:
12
12
  solo API key).
13
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
13
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
14
14
 
15
15
  ## Config de usuario: `~/.config/crush/crushrc`
16
16
 
@@ -9,7 +9,7 @@ lo dice tal cual; no hay validación hecha.
9
9
  ## Qué necesitas
10
10
 
11
11
  - `droid` instalado, en modo BYOK con tu **API key** de Anthropic.
12
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
12
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
13
13
 
14
14
  ## Config de usuario: `~/.factory/settings.json`
15
15
 
@@ -9,7 +9,7 @@ forma del URL.
9
9
 
10
10
  - `goose` en el PATH, con el proveedor Anthropic elegido.
11
11
  - `ANTHROPIC_API_KEY` exportada (Goose es solo BYOK).
12
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
12
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
13
13
 
14
14
  ## Envoltorio: `bvsala goose`
15
15
 
@@ -10,7 +10,7 @@ gateway.** No hay validación hecha; esta guía lo dice tal cual.
10
10
 
11
11
  - Kilo Code instalado en VS Code.
12
12
  - Tu **API key** de Anthropic.
13
- - `bvsala gateway` corriendo en `http://127.0.0.1:8402` (ver [README](README.md)).
13
+ - `bvsala gateway` corriendo en `http://127.0.0.1:8402` (necesita `BIVELIO_SERVICE_KEY` y una licencia BiVelio activa; ver [README](README.md)).
14
14
 
15
15
  ## Pasos en la UI
16
16
 
@@ -7,7 +7,7 @@
7
7
  ## Qué necesitas
8
8
 
9
9
  - `opencode` instalado, proveedor `anthropic` con tu **API key** de Anthropic.
10
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
10
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
11
11
 
12
12
  ## Config de usuario: `~/.config/opencode/opencode.json`
13
13
 
@@ -10,7 +10,7 @@ catálogo a propósito (más abajo).
10
10
  - `qwen` en el PATH.
11
11
  - **Una API key de Anthropic** (`ANTHROPIC_API_KEY`), con Qwen Code configurado
12
12
  para usar el proveedor Anthropic.
13
- - `bvsala` instalado y `BIVELIO_SERVICE_KEY` exportada (ver [README](README.md)).
13
+ - `bvsala` instalado, `BIVELIO_SERVICE_KEY` exportada y una licencia BiVelio activa (la recoge el gateway con la clave; ver [README](README.md)).
14
14
 
15
15
  ## Config de usuario: `~/.qwen/settings.json`
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivelio/savings-layer",
3
- "version": "0.4.22",
3
+ "version": "0.4.24",
4
4
  "description": "The savings layer for your LLM bill: a library inside your code that lets through only what has to be paid for and returns a SavingsReport per call, measured with your provider's own meter and kept apart from estimates. Commercial license, paid per seat, commission only on measured savings. Your prompts never leave your infrastructure.",
5
5
  "keywords": [
6
6
  "llm",