@hostwebhook/platform-contracts 0.5.0 → 0.7.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 +47 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +10 -2
- package/dist/precios/index.d.ts +11 -0
- package/dist/precios/index.js +27 -0
- package/dist/precios/tabla-de-precios.d.ts +162 -0
- package/dist/precios/tabla-de-precios.js +363 -0
- package/dist/servicios/historial-de-corridas.d.ts +32 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,10 +72,55 @@ nadie que la ate al otro lado es una lista que vuelve a divergir — que es
|
|
|
72
72
|
exactamente de donde se venía: había seis copias de los operadores de filtro y
|
|
73
73
|
ninguna igual a otra.
|
|
74
74
|
|
|
75
|
+
## Precios de los modelos
|
|
76
|
+
|
|
77
|
+
Lo que los proveedores de LLM cobran por millón de tokens. Entrada y salida se
|
|
78
|
+
tarifan **por separado**, con precios que suelen llevar un 5x entre ellos.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
calculateCost,
|
|
83
|
+
findPricingKey,
|
|
84
|
+
isModelPriced,
|
|
85
|
+
MODEL_PRICING,
|
|
86
|
+
} from '@hostwebhook/platform-contracts';
|
|
87
|
+
|
|
88
|
+
calculateCost('claude-sonnet-4-6', 1_000_000, 1_000_000); // → 18 USD
|
|
89
|
+
findPricingKey('claude-sonnet-4-6-20260101'); // → 'claude-sonnet-4-6'
|
|
90
|
+
findPricingKey('modelo-que-no-existe'); // → null
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
⚠️ **`null` no es cero.** `findPricingKey` devuelve `null` para un modelo que la
|
|
94
|
+
tabla no conoce, y entonces `calculateCost` da `0`. Ese 0 significa «no sé
|
|
95
|
+
cuánto vale», no «es gratis». Quien vaya a decidir algo con el número —un tope
|
|
96
|
+
de gasto, una factura, una pantalla— tiene que preguntar antes con
|
|
97
|
+
`isModelPriced()`. Tratar el 0 como gratis deja pasar sin límite justo los
|
|
98
|
+
modelos recién salidos, que son los caros.
|
|
99
|
+
|
|
100
|
+
La resolución **no adivina**: sólo vale una clave de la tabla que sea prefijo
|
|
101
|
+
del modelo pedido, y hasta un separador. Así `claude-sonnet-4-6-20260101`
|
|
102
|
+
encuentra su base, y `claude-opus-9` no hereda la tarifa de un vecino.
|
|
103
|
+
|
|
104
|
+
### 🔴 Hay dos copias de esta tabla
|
|
105
|
+
|
|
106
|
+
Ésta es la fuente de verdad desde el 2026-08-31.
|
|
107
|
+
`hw-llm-traces/src/common/utils/pricing.ts` es la copia que hay que retirar; el
|
|
108
|
+
PR que la hace consumir este paquete va en ese repo y todavía no está hecho.
|
|
109
|
+
Mientras tanto, un cambio de tarifa se hace aquí **y** se copia allí.
|
|
110
|
+
|
|
111
|
+
### Por qué aquí y no en la api
|
|
112
|
+
|
|
113
|
+
Los topes de gasto del Chat Trigger se comprueban en el camino caliente del
|
|
114
|
+
chat, una vez por turno. Preguntar la tarifa por red a `hw-llm-traces` metería
|
|
115
|
+
latencia en cada turno y, si ese servicio está caído o sin configurar, el tope
|
|
116
|
+
se caería **abierto** — que es lo mismo que no tenerlo.
|
|
117
|
+
|
|
75
118
|
## Qué no va aquí
|
|
76
119
|
|
|
77
|
-
- Precios e ids de Stripe: viven en la configuración de la api, que
|
|
78
|
-
que habla con Stripe. Cambiarlos no puede obligar a publicar un
|
|
120
|
+
- Precios **de venta** e ids de Stripe: viven en la configuración de la api, que
|
|
121
|
+
es la única que habla con Stripe. Cambiarlos no puede obligar a publicar un
|
|
122
|
+
paquete. (No confundir con `precios/`, que es lo que los proveedores de LLM
|
|
123
|
+
nos cobran a nosotros — ver arriba.)
|
|
79
124
|
- Límites por plan: `plan.constants.ts` en la api.
|
|
80
125
|
- Etiquetas de pantalla: cómo se llama un operador para el usuario es cosa del
|
|
81
126
|
dashboard (`lib/operadores.ts`). Aquí van las claves, no los textos.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
* escribe dos veces, un día se escribe distinto.
|
|
7
7
|
*
|
|
8
8
|
* Lo que NO entra: nada específico de un servicio, nada con secretos, y nada
|
|
9
|
-
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios
|
|
10
|
-
* Stripe, límites por plan).
|
|
9
|
+
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios de
|
|
10
|
+
* VENTA, ids de Stripe, límites por plan).
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ «Precios» ahí arriba significa lo que HostWebhook le COBRA a un cliente.
|
|
13
|
+
* `precios/` es la otra cosa que se llama igual: lo que los proveedores de LLM
|
|
14
|
+
* NOS cobran a nosotros por token. Entra porque no es una palanca comercial
|
|
15
|
+
* sino un dato de entrada para un cálculo, y porque la api lo necesita en el
|
|
16
|
+
* camino caliente del chat, donde no puede ir a preguntárselo a otro servicio
|
|
17
|
+
* por red. La cabecera de `precios/tabla-de-precios.ts` lo cuenta entero.
|
|
11
18
|
*/
|
|
12
19
|
export * from './addons';
|
|
13
20
|
export * from './operadores';
|
|
21
|
+
export * from './precios';
|
|
14
22
|
export * from './servicios';
|
package/dist/index.js
CHANGED
|
@@ -22,9 +22,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
* escribe dos veces, un día se escribe distinto.
|
|
23
23
|
*
|
|
24
24
|
* Lo que NO entra: nada específico de un servicio, nada con secretos, y nada
|
|
25
|
-
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios
|
|
26
|
-
* Stripe, límites por plan).
|
|
25
|
+
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios de
|
|
26
|
+
* VENTA, ids de Stripe, límites por plan).
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ «Precios» ahí arriba significa lo que HostWebhook le COBRA a un cliente.
|
|
29
|
+
* `precios/` es la otra cosa que se llama igual: lo que los proveedores de LLM
|
|
30
|
+
* NOS cobran a nosotros por token. Entra porque no es una palanca comercial
|
|
31
|
+
* sino un dato de entrada para un cálculo, y porque la api lo necesita en el
|
|
32
|
+
* camino caliente del chat, donde no puede ir a preguntárselo a otro servicio
|
|
33
|
+
* por red. La cabecera de `precios/tabla-de-precios.ts` lo cuenta entero.
|
|
27
34
|
*/
|
|
28
35
|
__exportStar(require("./addons"), exports);
|
|
29
36
|
__exportStar(require("./operadores"), exports);
|
|
37
|
+
__exportStar(require("./precios"), exports);
|
|
30
38
|
__exportStar(require("./servicios"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que cuesta un token, por modelo.
|
|
3
|
+
*
|
|
4
|
+
* Un solo módulo por ahora: `tabla-de-precios`. Está en su propia carpeta
|
|
5
|
+
* porque lo que va a crecer aquí son las tarifas —contexto largo, audio,
|
|
6
|
+
* lotes— y no las formas de contrato, que es de lo que va `servicios/`.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ Aquí NO van los precios de venta ni los ids de Stripe. Ver la cabecera de
|
|
9
|
+
* `tabla-de-precios.ts`: son cosas distintas que se llaman igual.
|
|
10
|
+
*/
|
|
11
|
+
export * from './tabla-de-precios';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Lo que cuesta un token, por modelo.
|
|
19
|
+
*
|
|
20
|
+
* Un solo módulo por ahora: `tabla-de-precios`. Está en su propia carpeta
|
|
21
|
+
* porque lo que va a crecer aquí son las tarifas —contexto largo, audio,
|
|
22
|
+
* lotes— y no las formas de contrato, que es de lo que va `servicios/`.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ Aquí NO van los precios de venta ni los ids de Stripe. Ver la cabecera de
|
|
25
|
+
* `tabla-de-precios.ts`: son cosas distintas que se llaman igual.
|
|
26
|
+
*/
|
|
27
|
+
__exportStar(require("./tabla-de-precios"), exports);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cuánto cuesta un millón de tokens, por modelo y en USD.
|
|
3
|
+
*
|
|
4
|
+
* ## 🔴 Ésta es la FUENTE DE VERDAD desde el 2026-08-31
|
|
5
|
+
*
|
|
6
|
+
* La tabla nació en `hw-llm-traces/src/common/utils/pricing.ts`. Esa copia
|
|
7
|
+
* **hay que retirarla**: mientras siga ahí existen dos tablas, y dos tablas de
|
|
8
|
+
* lo mismo divergen —es literalmente el fallo que este paquete existe para
|
|
9
|
+
* evitar—. El PR que hace que `hw-llm-traces` importe de aquí y borre su
|
|
10
|
+
* fichero va en ESE repo y todavía no está hecho.
|
|
11
|
+
*
|
|
12
|
+
* Hasta que lo esté: **cualquier cambio de tarifa se hace aquí y se copia
|
|
13
|
+
* allí**, no al revés.
|
|
14
|
+
*
|
|
15
|
+
* ## Por qué esto vive en un paquete y no en la api
|
|
16
|
+
*
|
|
17
|
+
* Porque la api va a poner topes de gasto en dólares al Chat Trigger, y eso
|
|
18
|
+
* ocurre en el camino caliente del chat, una vez por turno. Preguntárselo por
|
|
19
|
+
* red a `hw-llm-traces` metería latencia en cada turno, y peor: si el servicio
|
|
20
|
+
* de trazas está caído o sin configurar, el tope se caería ABIERTO. Un tope de
|
|
21
|
+
* gasto que falla abierto no es un tope.
|
|
22
|
+
*
|
|
23
|
+
* Es el mismo motivo por el que las listas de operadores están en este
|
|
24
|
+
* paquete y no pegadas a su evaluador: lo necesitan los dos lados y no puede
|
|
25
|
+
* escribirse dos veces.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ Ojo con la regla de al lado, que se parece y NO es ésta: los precios de
|
|
28
|
+
* VENTA y los ids de Stripe —lo que HostWebhook le cobra a un cliente— siguen
|
|
29
|
+
* fuera, en la configuración de la api. Esto de aquí es lo que los proveedores
|
|
30
|
+
* de LLM nos cobran a nosotros: es un dato de entrada para un cálculo, no una
|
|
31
|
+
* palanca comercial que haya que poder mover sin publicar.
|
|
32
|
+
*
|
|
33
|
+
* ## (!) ESTA TABLA CADUCA
|
|
34
|
+
*
|
|
35
|
+
* Última verificación: 2026-08-26, contra las páginas oficiales:
|
|
36
|
+
* OpenAI https://developers.openai.com/api/docs/pricing
|
|
37
|
+
* Anthropic https://platform.claude.com/docs/en/about-claude/pricing
|
|
38
|
+
* Google https://ai.google.dev/gemini-api/docs/pricing
|
|
39
|
+
*
|
|
40
|
+
* La versión anterior llevaba parada desde el 2026-04-18 —y el servicio sin
|
|
41
|
+
* redesplegar desde entonces—, así que para agosto le faltaban familias
|
|
42
|
+
* enteras: gpt-5.5, gpt-5.6, Opus 5, Sonnet 5, Gemini 3.5/3.6/3.7. Un modelo
|
|
43
|
+
* ausente ya no se cobra con la tarifa de un vecino —`findPricingKey` se niega
|
|
44
|
+
* a adivinar— pero sale a coste CERO y con clave `null`: revisa esta tabla
|
|
45
|
+
* cuando veas trazas sin precio.
|
|
46
|
+
*
|
|
47
|
+
* Se conservan modelos retirados a propósito: las trazas históricas siguen
|
|
48
|
+
* consultándose y tienen que seguir cuadrando.
|
|
49
|
+
*
|
|
50
|
+
* ## Lo que esta tabla NO modela (aceptable para estimar coste)
|
|
51
|
+
*
|
|
52
|
+
* - Contexto largo: `gemini-3.1-pro` y `gemini-2.5-pro` casi doblan tarifa
|
|
53
|
+
* por encima de 200k tokens de entrada. Se usa siempre la base, así que
|
|
54
|
+
* INFRAVALORA los prompts muy largos.
|
|
55
|
+
* - Audio: `gemini-2.5-flash` y `3.1-flash-lite` cobran más el audio que el
|
|
56
|
+
* texto. Aquí todo va a tarifa de texto.
|
|
57
|
+
* - Anthropic: `inference_geo` "us" multiplica por 1,1, y el modo rápido de
|
|
58
|
+
* Opus 5 / 4.8 cuesta $10/$50. No se modelan: HostWebhook no los usa.
|
|
59
|
+
* - Descuento del 50% de las APIs por lotes. Nadie lo usa todavía.
|
|
60
|
+
* - (!) Claude 4.7 en adelante usa un tokenizador nuevo que produce ~30% MÁS
|
|
61
|
+
* tokens para el mismo texto. El precio por token no cambia, pero la
|
|
62
|
+
* factura por la misma conversación sí.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ Quien ponga un tope de gasto sobre esto tiene que saber que INFRAVALORA
|
|
65
|
+
* en los casos de arriba. El tope real queda por encima del nominal.
|
|
66
|
+
*/
|
|
67
|
+
export interface ModelPricing {
|
|
68
|
+
input: number;
|
|
69
|
+
output: number;
|
|
70
|
+
}
|
|
71
|
+
export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
72
|
+
/**
|
|
73
|
+
* Qué entrada de la tabla pone precio a este modelo, o `null` cuando de verdad
|
|
74
|
+
* no lo sabemos.
|
|
75
|
+
*
|
|
76
|
+
* ## Por qué la regla es «la clave conocida es PREFIJO del modelo pedido»
|
|
77
|
+
*
|
|
78
|
+
* La versión anterior iba al revés: recortaba el modelo pedido y buscaba la
|
|
79
|
+
* primera clave que empezara por ese trozo. Dos fallos, los dos MEDIDOS
|
|
80
|
+
* ejecutando la función con 1M de entrada y 1M de salida:
|
|
81
|
+
*
|
|
82
|
+
* - `gpt-5.4-turbo-2026` recortaba a `gpt-5.4` y `find()` devolvía la PRIMERA
|
|
83
|
+
* clave declarada que empieza así —`gpt-5.4-pro`, a $30/$180—. Cobraba $210
|
|
84
|
+
* donde la base `gpt-5.4` cuesta $17.50. Doce veces de más, y el ORDEN DE
|
|
85
|
+
* DECLARACIÓN del objeto decidía la factura.
|
|
86
|
+
* - `claude-opus-5` recortaba a `claude-opus` y cobraba $30 con la tarifa de
|
|
87
|
+
* `claude-opus-4-7`. Un modelo ausente salía con un precio inventado y de
|
|
88
|
+
* aspecto plausible.
|
|
89
|
+
*
|
|
90
|
+
* La dirección correcta es la contraria: sólo vale una clave que sea prefijo
|
|
91
|
+
* DEL MODELO PEDIDO, y gana la más larga. Eso cubre el caso real —un snapshot
|
|
92
|
+
* con fecha, `claude-sonnet-4-6-20260101`— y se niega a adivinar cuando de
|
|
93
|
+
* verdad es un modelo nuevo. Mejor decir «no lo sé» que cobrar la tarifa de
|
|
94
|
+
* otro.
|
|
95
|
+
*
|
|
96
|
+
* La frontera importa: `gpt-4` NO puede prestar su precio a `gpt-45-turbo`,
|
|
97
|
+
* así que el carácter siguiente tiene que ser un separador.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ Ese `null` es INFORMACIÓN, no un hueco que rellenar. Hay código aguas
|
|
100
|
+
* abajo que necesita distinguir «gratis» de «no sé cuánto vale» —un tope de
|
|
101
|
+
* gasto, sin ir más lejos—. Quien lo «mejore» adivinando le devuelve al
|
|
102
|
+
* llamante un número inventado con pinta de bueno.
|
|
103
|
+
*/
|
|
104
|
+
export declare function findPricingKey(model: string): string | null;
|
|
105
|
+
/** True cuando la tabla sabe de verdad cuánto vale este modelo. */
|
|
106
|
+
export declare function isModelPriced(model: string): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Coste en USD, redondeado a 6 decimales (precisión de micro-dólar).
|
|
109
|
+
*
|
|
110
|
+
* ⚠️ Devuelve 0 para modelos que no sabemos poner en precio: usa
|
|
111
|
+
* `isModelPriced()` o `findPricingKey()` para distinguir «gratis» de «no lo
|
|
112
|
+
* sé» ANTES de enseñarle un 0 a una persona —o de dejar pasar un turno de chat
|
|
113
|
+
* porque su coste «no llega al tope»—.
|
|
114
|
+
*
|
|
115
|
+
* ⚠️ Entrada y salida se tarifan POR SEPARADO, con precios distintos y a
|
|
116
|
+
* menudo con un factor de 5x entre ellos. Sumar tokens y multiplicar por un
|
|
117
|
+
* precio único da un número que no es el de nadie.
|
|
118
|
+
*
|
|
119
|
+
* ## (!) `outputTokens` YA incluye el razonamiento
|
|
120
|
+
*
|
|
121
|
+
* `reasoningTokens` es INFORMATIVO y no se cobra aparte, porque los dos
|
|
122
|
+
* proveedores que lo reportan ya lo tienen dentro de la salida:
|
|
123
|
+
*
|
|
124
|
+
* - OpenAI: "reasoning tokens ... are billed as output tokens";
|
|
125
|
+
* `completion_tokens_details.reasoning_tokens` es un desglose de
|
|
126
|
+
* `completion_tokens`, no un extra.
|
|
127
|
+
* - Google: `thoughtsTokenCount` va aparte de `candidatesTokenCount` en el
|
|
128
|
+
* JSON, así que quien emite tiene que sumarlos ANTES de mandar
|
|
129
|
+
* `outputTokens` — es lo que hace HostWebhook desde el arreglo del conteo
|
|
130
|
+
* de Gemini.
|
|
131
|
+
*
|
|
132
|
+
* Antes se sumaba `reasoningTokens * precio_de_salida` ENCIMA de
|
|
133
|
+
* `outputTokens` para openai y google: eso cobraba dos veces el mismo
|
|
134
|
+
* pensamiento. El parámetro se conserva para no romper a quien ya lo pasa.
|
|
135
|
+
*/
|
|
136
|
+
export declare function calculateCost(model: string, inputTokens: number, outputTokens: number, cacheReadInputTokens?: number, cacheCreationInputTokens?: number, _reasoningTokens?: number): number;
|
|
137
|
+
/** Los modelos que la tabla sabe poner en precio, en orden de declaración. */
|
|
138
|
+
export declare function getKnownModels(): string[];
|
|
139
|
+
/**
|
|
140
|
+
* De un modelo caro a otro más barato de capacidad parecida.
|
|
141
|
+
*
|
|
142
|
+
* ⚠️ Es un mapa a mano y NO cubre toda la tabla: un modelo que no está aquí
|
|
143
|
+
* simplemente no tiene sugerencia. No es la lista de modelos válidos —esa es
|
|
144
|
+
* `MODEL_PRICING`—.
|
|
145
|
+
*/
|
|
146
|
+
export declare const MODEL_ALTERNATIVES: Record<string, string>;
|
|
147
|
+
/**
|
|
148
|
+
* La alternativa más barata sugerida para este modelo y lo que se ahorraría,
|
|
149
|
+
* o `null` cuando no hay sugerencia.
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ Busca por el nombre EXACTO en `MODEL_ALTERNATIVES` —no usa
|
|
152
|
+
* `findPricingKey`—, así que un snapshot con fecha no encuentra sugerencia
|
|
153
|
+
* aunque su modelo base sí la tenga. Se conserva tal cual: cambiarlo alteraría
|
|
154
|
+
* qué se le sugiere hoy a la gente.
|
|
155
|
+
*/
|
|
156
|
+
export declare function getSuggestedAlternative(model: string, inputTokens: number, outputTokens: number): {
|
|
157
|
+
alternativeModel: string;
|
|
158
|
+
currentCost: number;
|
|
159
|
+
alternativeCost: number;
|
|
160
|
+
savingsUsd: number;
|
|
161
|
+
savingsPercent: number;
|
|
162
|
+
} | null;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cuánto cuesta un millón de tokens, por modelo y en USD.
|
|
4
|
+
*
|
|
5
|
+
* ## 🔴 Ésta es la FUENTE DE VERDAD desde el 2026-08-31
|
|
6
|
+
*
|
|
7
|
+
* La tabla nació en `hw-llm-traces/src/common/utils/pricing.ts`. Esa copia
|
|
8
|
+
* **hay que retirarla**: mientras siga ahí existen dos tablas, y dos tablas de
|
|
9
|
+
* lo mismo divergen —es literalmente el fallo que este paquete existe para
|
|
10
|
+
* evitar—. El PR que hace que `hw-llm-traces` importe de aquí y borre su
|
|
11
|
+
* fichero va en ESE repo y todavía no está hecho.
|
|
12
|
+
*
|
|
13
|
+
* Hasta que lo esté: **cualquier cambio de tarifa se hace aquí y se copia
|
|
14
|
+
* allí**, no al revés.
|
|
15
|
+
*
|
|
16
|
+
* ## Por qué esto vive en un paquete y no en la api
|
|
17
|
+
*
|
|
18
|
+
* Porque la api va a poner topes de gasto en dólares al Chat Trigger, y eso
|
|
19
|
+
* ocurre en el camino caliente del chat, una vez por turno. Preguntárselo por
|
|
20
|
+
* red a `hw-llm-traces` metería latencia en cada turno, y peor: si el servicio
|
|
21
|
+
* de trazas está caído o sin configurar, el tope se caería ABIERTO. Un tope de
|
|
22
|
+
* gasto que falla abierto no es un tope.
|
|
23
|
+
*
|
|
24
|
+
* Es el mismo motivo por el que las listas de operadores están en este
|
|
25
|
+
* paquete y no pegadas a su evaluador: lo necesitan los dos lados y no puede
|
|
26
|
+
* escribirse dos veces.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ Ojo con la regla de al lado, que se parece y NO es ésta: los precios de
|
|
29
|
+
* VENTA y los ids de Stripe —lo que HostWebhook le cobra a un cliente— siguen
|
|
30
|
+
* fuera, en la configuración de la api. Esto de aquí es lo que los proveedores
|
|
31
|
+
* de LLM nos cobran a nosotros: es un dato de entrada para un cálculo, no una
|
|
32
|
+
* palanca comercial que haya que poder mover sin publicar.
|
|
33
|
+
*
|
|
34
|
+
* ## (!) ESTA TABLA CADUCA
|
|
35
|
+
*
|
|
36
|
+
* Última verificación: 2026-08-26, contra las páginas oficiales:
|
|
37
|
+
* OpenAI https://developers.openai.com/api/docs/pricing
|
|
38
|
+
* Anthropic https://platform.claude.com/docs/en/about-claude/pricing
|
|
39
|
+
* Google https://ai.google.dev/gemini-api/docs/pricing
|
|
40
|
+
*
|
|
41
|
+
* La versión anterior llevaba parada desde el 2026-04-18 —y el servicio sin
|
|
42
|
+
* redesplegar desde entonces—, así que para agosto le faltaban familias
|
|
43
|
+
* enteras: gpt-5.5, gpt-5.6, Opus 5, Sonnet 5, Gemini 3.5/3.6/3.7. Un modelo
|
|
44
|
+
* ausente ya no se cobra con la tarifa de un vecino —`findPricingKey` se niega
|
|
45
|
+
* a adivinar— pero sale a coste CERO y con clave `null`: revisa esta tabla
|
|
46
|
+
* cuando veas trazas sin precio.
|
|
47
|
+
*
|
|
48
|
+
* Se conservan modelos retirados a propósito: las trazas históricas siguen
|
|
49
|
+
* consultándose y tienen que seguir cuadrando.
|
|
50
|
+
*
|
|
51
|
+
* ## Lo que esta tabla NO modela (aceptable para estimar coste)
|
|
52
|
+
*
|
|
53
|
+
* - Contexto largo: `gemini-3.1-pro` y `gemini-2.5-pro` casi doblan tarifa
|
|
54
|
+
* por encima de 200k tokens de entrada. Se usa siempre la base, así que
|
|
55
|
+
* INFRAVALORA los prompts muy largos.
|
|
56
|
+
* - Audio: `gemini-2.5-flash` y `3.1-flash-lite` cobran más el audio que el
|
|
57
|
+
* texto. Aquí todo va a tarifa de texto.
|
|
58
|
+
* - Anthropic: `inference_geo` "us" multiplica por 1,1, y el modo rápido de
|
|
59
|
+
* Opus 5 / 4.8 cuesta $10/$50. No se modelan: HostWebhook no los usa.
|
|
60
|
+
* - Descuento del 50% de las APIs por lotes. Nadie lo usa todavía.
|
|
61
|
+
* - (!) Claude 4.7 en adelante usa un tokenizador nuevo que produce ~30% MÁS
|
|
62
|
+
* tokens para el mismo texto. El precio por token no cambia, pero la
|
|
63
|
+
* factura por la misma conversación sí.
|
|
64
|
+
*
|
|
65
|
+
* ⚠️ Quien ponga un tope de gasto sobre esto tiene que saber que INFRAVALORA
|
|
66
|
+
* en los casos de arriba. El tope real queda por encima del nominal.
|
|
67
|
+
*/
|
|
68
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
69
|
+
exports.MODEL_ALTERNATIVES = exports.MODEL_PRICING = void 0;
|
|
70
|
+
exports.findPricingKey = findPricingKey;
|
|
71
|
+
exports.isModelPriced = isModelPriced;
|
|
72
|
+
exports.calculateCost = calculateCost;
|
|
73
|
+
exports.getKnownModels = getKnownModels;
|
|
74
|
+
exports.getSuggestedAlternative = getSuggestedAlternative;
|
|
75
|
+
exports.MODEL_PRICING = {
|
|
76
|
+
// OpenAI — GPT series
|
|
77
|
+
'gpt-5.6-sol': { input: 4.00, output: 20.00 },
|
|
78
|
+
'gpt-5.6-terra': { input: 2.00, output: 12.00 },
|
|
79
|
+
'gpt-5.6-luna': { input: 0.20, output: 1.20 },
|
|
80
|
+
'gpt-5.5-pro': { input: 30.00, output: 180.00 },
|
|
81
|
+
'gpt-5.5': { input: 5.00, output: 30.00 },
|
|
82
|
+
'gpt-5.4-pro': { input: 30.00, output: 180.00 },
|
|
83
|
+
'gpt-5.4': { input: 2.50, output: 15.00 },
|
|
84
|
+
'gpt-5.4-mini': { input: 0.75, output: 4.50 },
|
|
85
|
+
'gpt-5.4-nano': { input: 0.20, output: 1.25 },
|
|
86
|
+
'gpt-5.2-pro': { input: 21.00, output: 168.00 },
|
|
87
|
+
'gpt-5.2': { input: 1.75, output: 14.00 },
|
|
88
|
+
'gpt-5.1': { input: 1.25, output: 10.00 },
|
|
89
|
+
'gpt-5-pro': { input: 15.00, output: 120.00 },
|
|
90
|
+
'gpt-5': { input: 1.25, output: 10.00 },
|
|
91
|
+
'gpt-5-mini': { input: 0.25, output: 2.00 },
|
|
92
|
+
'gpt-5-nano': { input: 0.05, output: 0.40 },
|
|
93
|
+
'gpt-4.1': { input: 2.00, output: 8.00 },
|
|
94
|
+
'gpt-4.1-mini': { input: 0.40, output: 1.60 },
|
|
95
|
+
'gpt-4.1-nano': { input: 0.10, output: 0.40 },
|
|
96
|
+
'gpt-4o': { input: 2.50, output: 10.00 },
|
|
97
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60 },
|
|
98
|
+
'gpt-4-turbo': { input: 10.00, output: 30.00 },
|
|
99
|
+
'gpt-4': { input: 30.00, output: 60.00 },
|
|
100
|
+
'gpt-3.5-turbo': { input: 0.50, output: 1.50 },
|
|
101
|
+
'gpt-4o-2024-05-13': { input: 5.00, output: 15.00 },
|
|
102
|
+
'chat-latest': { input: 5.00, output: 30.00 },
|
|
103
|
+
// OpenAI — reasoning series
|
|
104
|
+
'o1-pro': { input: 150.00, output: 600.00 },
|
|
105
|
+
'o1': { input: 15.00, output: 60.00 },
|
|
106
|
+
'o1-mini': { input: 1.10, output: 4.40 },
|
|
107
|
+
'o3-pro': { input: 20.00, output: 80.00 },
|
|
108
|
+
'o3': { input: 2.00, output: 8.00 },
|
|
109
|
+
'o3-mini': { input: 1.10, output: 4.40 },
|
|
110
|
+
'o4-mini': { input: 1.10, output: 4.40 },
|
|
111
|
+
// Anthropic
|
|
112
|
+
'claude-fable-5': { input: 10.00, output: 50.00 },
|
|
113
|
+
'claude-mythos-5': { input: 10.00, output: 50.00 },
|
|
114
|
+
'claude-opus-5': { input: 5.00, output: 25.00 },
|
|
115
|
+
'claude-opus-4-8': { input: 5.00, output: 25.00 },
|
|
116
|
+
'claude-opus-4-7': { input: 5.00, output: 25.00 },
|
|
117
|
+
'claude-opus-4-6': { input: 5.00, output: 25.00 },
|
|
118
|
+
'claude-opus-4-5': { input: 5.00, output: 25.00 },
|
|
119
|
+
/* (!) `claude-opus-4-20250514` ES Claude Opus 4, y Opus 4 cuesta $15/$75.
|
|
120
|
+
Estaba a $5/$25 —la tarifa de la generación 4.5 en adelante—, o sea
|
|
121
|
+
cobrando un TERCIO de lo real. Se quitó además `claude-opus-4-5-20250514`,
|
|
122
|
+
un id que mezclaba la versión 4.5 con la fecha de lanzamiento de la 4; si
|
|
123
|
+
alguna vez llega, `findPricingKey` lo resuelve solo contra
|
|
124
|
+
`claude-opus-4-5`. */
|
|
125
|
+
'claude-opus-4-20250514': { input: 15.00, output: 75.00 },
|
|
126
|
+
'claude-opus-4': { input: 15.00, output: 75.00 },
|
|
127
|
+
'claude-opus-4-1': { input: 15.00, output: 75.00 },
|
|
128
|
+
'claude-sonnet-5': { input: 2.00, output: 10.00 },
|
|
129
|
+
'claude-sonnet-4-6': { input: 3.00, output: 15.00 },
|
|
130
|
+
'claude-sonnet-4-5': { input: 3.00, output: 15.00 },
|
|
131
|
+
'claude-sonnet-4-5-20250514': { input: 3.00, output: 15.00 },
|
|
132
|
+
'claude-sonnet-4': { input: 3.00, output: 15.00 },
|
|
133
|
+
'claude-sonnet-4-20250514': { input: 3.00, output: 15.00 },
|
|
134
|
+
'claude-haiku-4-5-20251001': { input: 1.00, output: 5.00 },
|
|
135
|
+
'claude-3-7-sonnet-20250219': { input: 3.00, output: 15.00 },
|
|
136
|
+
'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },
|
|
137
|
+
'claude-3-5-haiku-20241022': { input: 0.80, output: 4.00 },
|
|
138
|
+
'claude-3-opus-20240229': { input: 15.00, output: 75.00 },
|
|
139
|
+
'claude-haiku-3': { input: 0.25, output: 1.25 },
|
|
140
|
+
// Google
|
|
141
|
+
'gemini-3.7-flash': { input: 0.75, output: 3.75 },
|
|
142
|
+
'gemini-3.6-flash': { input: 0.75, output: 3.75 },
|
|
143
|
+
'gemini-3.5-flash-lite': { input: 0.30, output: 2.50 },
|
|
144
|
+
'gemini-3.5-flash': { input: 1.50, output: 9.00 },
|
|
145
|
+
'gemini-3.1-pro': { input: 2.00, output: 12.00 },
|
|
146
|
+
'gemini-3.1-flash-lite': { input: 0.25, output: 1.50 },
|
|
147
|
+
'gemini-3-flash': { input: 0.50, output: 3.00 },
|
|
148
|
+
'gemini-2.5-pro': { input: 1.25, output: 10.00 },
|
|
149
|
+
'gemini-2.5-flash': { input: 0.30, output: 2.50 },
|
|
150
|
+
'gemini-2.5-flash-lite': { input: 0.10, output: 0.40 },
|
|
151
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40 },
|
|
152
|
+
'gemini-1.5-pro': { input: 1.25, output: 5.00 },
|
|
153
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30 },
|
|
154
|
+
// Mistral
|
|
155
|
+
'mistral-large-latest': { input: 2.00, output: 6.00 },
|
|
156
|
+
'mistral-small-latest': { input: 0.20, output: 0.60 },
|
|
157
|
+
'codestral-latest': { input: 0.30, output: 0.90 },
|
|
158
|
+
// Cohere
|
|
159
|
+
'command-r-plus': { input: 2.50, output: 10.00 },
|
|
160
|
+
'command-r': { input: 0.15, output: 0.60 },
|
|
161
|
+
};
|
|
162
|
+
/* ── Caché ───────────────────────────────────────────────────────── */
|
|
163
|
+
/** Multiplicadores de lectura de caché, sobre el precio de ENTRADA. */
|
|
164
|
+
const CACHE_READ_MULTIPLIERS = {
|
|
165
|
+
anthropic: 0.1, // 10% del precio de entrada
|
|
166
|
+
openai: 0.5, // 50% del precio de entrada
|
|
167
|
+
google: 0.1, // 10% del precio de entrada ($0.125/$1.25)
|
|
168
|
+
};
|
|
169
|
+
/** Multiplicadores de creación de caché, sobre el precio de ENTRADA. */
|
|
170
|
+
const CACHE_CREATION_MULTIPLIERS = {
|
|
171
|
+
anthropic: 1.25, // 125% del precio de entrada
|
|
172
|
+
// OpenAI y Google: la creación no se cobra (cacheado automático)
|
|
173
|
+
};
|
|
174
|
+
function detectProvider(model) {
|
|
175
|
+
if (model.startsWith('gpt-') ||
|
|
176
|
+
model.startsWith('o1') ||
|
|
177
|
+
model.startsWith('o3') ||
|
|
178
|
+
model.startsWith('o4'))
|
|
179
|
+
return 'openai';
|
|
180
|
+
if (model.includes('claude'))
|
|
181
|
+
return 'anthropic';
|
|
182
|
+
if (model.includes('gemini'))
|
|
183
|
+
return 'google';
|
|
184
|
+
if (model.includes('mistral') || model.includes('mixtral'))
|
|
185
|
+
return 'mistral';
|
|
186
|
+
if (model.includes('command'))
|
|
187
|
+
return 'cohere';
|
|
188
|
+
return 'custom';
|
|
189
|
+
}
|
|
190
|
+
/* ── Resolución del modelo ───────────────────────────────────────── */
|
|
191
|
+
/**
|
|
192
|
+
* Qué entrada de la tabla pone precio a este modelo, o `null` cuando de verdad
|
|
193
|
+
* no lo sabemos.
|
|
194
|
+
*
|
|
195
|
+
* ## Por qué la regla es «la clave conocida es PREFIJO del modelo pedido»
|
|
196
|
+
*
|
|
197
|
+
* La versión anterior iba al revés: recortaba el modelo pedido y buscaba la
|
|
198
|
+
* primera clave que empezara por ese trozo. Dos fallos, los dos MEDIDOS
|
|
199
|
+
* ejecutando la función con 1M de entrada y 1M de salida:
|
|
200
|
+
*
|
|
201
|
+
* - `gpt-5.4-turbo-2026` recortaba a `gpt-5.4` y `find()` devolvía la PRIMERA
|
|
202
|
+
* clave declarada que empieza así —`gpt-5.4-pro`, a $30/$180—. Cobraba $210
|
|
203
|
+
* donde la base `gpt-5.4` cuesta $17.50. Doce veces de más, y el ORDEN DE
|
|
204
|
+
* DECLARACIÓN del objeto decidía la factura.
|
|
205
|
+
* - `claude-opus-5` recortaba a `claude-opus` y cobraba $30 con la tarifa de
|
|
206
|
+
* `claude-opus-4-7`. Un modelo ausente salía con un precio inventado y de
|
|
207
|
+
* aspecto plausible.
|
|
208
|
+
*
|
|
209
|
+
* La dirección correcta es la contraria: sólo vale una clave que sea prefijo
|
|
210
|
+
* DEL MODELO PEDIDO, y gana la más larga. Eso cubre el caso real —un snapshot
|
|
211
|
+
* con fecha, `claude-sonnet-4-6-20260101`— y se niega a adivinar cuando de
|
|
212
|
+
* verdad es un modelo nuevo. Mejor decir «no lo sé» que cobrar la tarifa de
|
|
213
|
+
* otro.
|
|
214
|
+
*
|
|
215
|
+
* La frontera importa: `gpt-4` NO puede prestar su precio a `gpt-45-turbo`,
|
|
216
|
+
* así que el carácter siguiente tiene que ser un separador.
|
|
217
|
+
*
|
|
218
|
+
* ⚠️ Ese `null` es INFORMACIÓN, no un hueco que rellenar. Hay código aguas
|
|
219
|
+
* abajo que necesita distinguir «gratis» de «no sé cuánto vale» —un tope de
|
|
220
|
+
* gasto, sin ir más lejos—. Quien lo «mejore» adivinando le devuelve al
|
|
221
|
+
* llamante un número inventado con pinta de bueno.
|
|
222
|
+
*/
|
|
223
|
+
function findPricingKey(model) {
|
|
224
|
+
if (!model)
|
|
225
|
+
return null;
|
|
226
|
+
if (exports.MODEL_PRICING[model])
|
|
227
|
+
return model;
|
|
228
|
+
const SEPARADORES = new Set(['-', '_', '.', ':', '@', '/']);
|
|
229
|
+
let mejor = null;
|
|
230
|
+
for (const key of Object.keys(exports.MODEL_PRICING)) {
|
|
231
|
+
if (!model.startsWith(key))
|
|
232
|
+
continue;
|
|
233
|
+
if (!SEPARADORES.has(model.charAt(key.length)))
|
|
234
|
+
continue;
|
|
235
|
+
if (!mejor || key.length > mejor.length)
|
|
236
|
+
mejor = key;
|
|
237
|
+
}
|
|
238
|
+
return mejor;
|
|
239
|
+
}
|
|
240
|
+
/** True cuando la tabla sabe de verdad cuánto vale este modelo. */
|
|
241
|
+
function isModelPriced(model) {
|
|
242
|
+
return findPricingKey(model) !== null;
|
|
243
|
+
}
|
|
244
|
+
/* ── Coste ───────────────────────────────────────────────────────── */
|
|
245
|
+
/**
|
|
246
|
+
* Coste en USD, redondeado a 6 decimales (precisión de micro-dólar).
|
|
247
|
+
*
|
|
248
|
+
* ⚠️ Devuelve 0 para modelos que no sabemos poner en precio: usa
|
|
249
|
+
* `isModelPriced()` o `findPricingKey()` para distinguir «gratis» de «no lo
|
|
250
|
+
* sé» ANTES de enseñarle un 0 a una persona —o de dejar pasar un turno de chat
|
|
251
|
+
* porque su coste «no llega al tope»—.
|
|
252
|
+
*
|
|
253
|
+
* ⚠️ Entrada y salida se tarifan POR SEPARADO, con precios distintos y a
|
|
254
|
+
* menudo con un factor de 5x entre ellos. Sumar tokens y multiplicar por un
|
|
255
|
+
* precio único da un número que no es el de nadie.
|
|
256
|
+
*
|
|
257
|
+
* ## (!) `outputTokens` YA incluye el razonamiento
|
|
258
|
+
*
|
|
259
|
+
* `reasoningTokens` es INFORMATIVO y no se cobra aparte, porque los dos
|
|
260
|
+
* proveedores que lo reportan ya lo tienen dentro de la salida:
|
|
261
|
+
*
|
|
262
|
+
* - OpenAI: "reasoning tokens ... are billed as output tokens";
|
|
263
|
+
* `completion_tokens_details.reasoning_tokens` es un desglose de
|
|
264
|
+
* `completion_tokens`, no un extra.
|
|
265
|
+
* - Google: `thoughtsTokenCount` va aparte de `candidatesTokenCount` en el
|
|
266
|
+
* JSON, así que quien emite tiene que sumarlos ANTES de mandar
|
|
267
|
+
* `outputTokens` — es lo que hace HostWebhook desde el arreglo del conteo
|
|
268
|
+
* de Gemini.
|
|
269
|
+
*
|
|
270
|
+
* Antes se sumaba `reasoningTokens * precio_de_salida` ENCIMA de
|
|
271
|
+
* `outputTokens` para openai y google: eso cobraba dos veces el mismo
|
|
272
|
+
* pensamiento. El parámetro se conserva para no romper a quien ya lo pasa.
|
|
273
|
+
*/
|
|
274
|
+
function calculateCost(model, inputTokens, outputTokens, cacheReadInputTokens = 0, cacheCreationInputTokens = 0, _reasoningTokens = 0) {
|
|
275
|
+
const key = findPricingKey(model);
|
|
276
|
+
if (!key)
|
|
277
|
+
return 0;
|
|
278
|
+
const pricing = exports.MODEL_PRICING[key];
|
|
279
|
+
const provider = detectProvider(key);
|
|
280
|
+
const inputCost = (inputTokens / 1000000) * pricing.input;
|
|
281
|
+
const outputCost = (outputTokens / 1000000) * pricing.output;
|
|
282
|
+
// Caché: multiplicadores por proveedor
|
|
283
|
+
const cacheReadMul = CACHE_READ_MULTIPLIERS[provider] ?? 0.1;
|
|
284
|
+
const cacheCreationMul = CACHE_CREATION_MULTIPLIERS[provider] ?? 0;
|
|
285
|
+
const cacheReadCost = (cacheReadInputTokens / 1000000) * pricing.input * cacheReadMul;
|
|
286
|
+
const cacheCreationCost = (cacheCreationInputTokens / 1000000) * pricing.input * cacheCreationMul;
|
|
287
|
+
return (Math.round((inputCost + outputCost + cacheReadCost + cacheCreationCost) * 1000000) / 1000000);
|
|
288
|
+
}
|
|
289
|
+
/** Los modelos que la tabla sabe poner en precio, en orden de declaración. */
|
|
290
|
+
function getKnownModels() {
|
|
291
|
+
return Object.keys(exports.MODEL_PRICING);
|
|
292
|
+
}
|
|
293
|
+
/* ── Alternativas más baratas ────────────────────────────────────── */
|
|
294
|
+
/**
|
|
295
|
+
* De un modelo caro a otro más barato de capacidad parecida.
|
|
296
|
+
*
|
|
297
|
+
* ⚠️ Es un mapa a mano y NO cubre toda la tabla: un modelo que no está aquí
|
|
298
|
+
* simplemente no tiene sugerencia. No es la lista de modelos válidos —esa es
|
|
299
|
+
* `MODEL_PRICING`—.
|
|
300
|
+
*/
|
|
301
|
+
exports.MODEL_ALTERNATIVES = {
|
|
302
|
+
// OpenAI — GPT series
|
|
303
|
+
'gpt-5.4': 'gpt-5.2',
|
|
304
|
+
'gpt-5.2': 'gpt-5',
|
|
305
|
+
'gpt-5.1': 'gpt-5-mini',
|
|
306
|
+
'gpt-5': 'gpt-5-mini',
|
|
307
|
+
'gpt-5-mini': 'gpt-5-nano',
|
|
308
|
+
'gpt-4.1': 'gpt-4.1-mini',
|
|
309
|
+
'gpt-4.1-mini': 'gpt-4.1-nano',
|
|
310
|
+
'gpt-4o': 'gpt-4o-mini',
|
|
311
|
+
'gpt-4-turbo': 'gpt-4.1',
|
|
312
|
+
'gpt-4': 'gpt-4.1',
|
|
313
|
+
// OpenAI — reasoning series
|
|
314
|
+
'o1': 'o3',
|
|
315
|
+
'o3': 'o4-mini',
|
|
316
|
+
'o1-mini': 'o4-mini',
|
|
317
|
+
'o3-mini': 'o4-mini',
|
|
318
|
+
// Anthropic
|
|
319
|
+
'claude-opus-4-6': 'claude-sonnet-4-6',
|
|
320
|
+
'claude-opus-4-20250514': 'claude-sonnet-4-20250514',
|
|
321
|
+
'claude-sonnet-4-6': 'claude-haiku-4-5-20251001',
|
|
322
|
+
'claude-sonnet-4-20250514': 'claude-haiku-4-5-20251001',
|
|
323
|
+
'claude-3-7-sonnet-20250219': 'claude-haiku-4-5-20251001',
|
|
324
|
+
'claude-3-5-sonnet-20241022': 'claude-3-5-haiku-20241022',
|
|
325
|
+
'claude-3-opus-20240229': 'claude-sonnet-4-6',
|
|
326
|
+
// Google
|
|
327
|
+
'gemini-3.1-pro': 'gemini-3-flash',
|
|
328
|
+
'gemini-3-flash': 'gemini-2.5-flash',
|
|
329
|
+
'gemini-2.5-pro': 'gemini-2.5-flash',
|
|
330
|
+
'gemini-2.5-flash': 'gemini-2.5-flash-lite',
|
|
331
|
+
'gemini-1.5-pro': 'gemini-2.5-flash',
|
|
332
|
+
// Mistral
|
|
333
|
+
'mistral-large-latest': 'mistral-small-latest',
|
|
334
|
+
// Cohere
|
|
335
|
+
'command-r-plus': 'command-r',
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* La alternativa más barata sugerida para este modelo y lo que se ahorraría,
|
|
339
|
+
* o `null` cuando no hay sugerencia.
|
|
340
|
+
*
|
|
341
|
+
* ⚠️ Busca por el nombre EXACTO en `MODEL_ALTERNATIVES` —no usa
|
|
342
|
+
* `findPricingKey`—, así que un snapshot con fecha no encuentra sugerencia
|
|
343
|
+
* aunque su modelo base sí la tenga. Se conserva tal cual: cambiarlo alteraría
|
|
344
|
+
* qué se le sugiere hoy a la gente.
|
|
345
|
+
*/
|
|
346
|
+
function getSuggestedAlternative(model, inputTokens, outputTokens) {
|
|
347
|
+
const alt = exports.MODEL_ALTERNATIVES[model];
|
|
348
|
+
if (!alt)
|
|
349
|
+
return null;
|
|
350
|
+
const currentCost = calculateCost(model, inputTokens, outputTokens);
|
|
351
|
+
const alternativeCost = calculateCost(alt, inputTokens, outputTokens);
|
|
352
|
+
if (currentCost <= 0)
|
|
353
|
+
return null;
|
|
354
|
+
const savingsUsd = currentCost - alternativeCost;
|
|
355
|
+
const savingsPercent = Math.round((savingsUsd / currentCost) * 10000) / 100;
|
|
356
|
+
return {
|
|
357
|
+
alternativeModel: alt,
|
|
358
|
+
currentCost: Math.round(currentCost * 1000000) / 1000000,
|
|
359
|
+
alternativeCost: Math.round(alternativeCost * 1000000) / 1000000,
|
|
360
|
+
savingsUsd: Math.round(savingsUsd * 1000000) / 1000000,
|
|
361
|
+
savingsPercent,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
@@ -68,6 +68,38 @@ export interface GrabadorDeHistorial {
|
|
|
68
68
|
* tiene que garantizarlo también.
|
|
69
69
|
*/
|
|
70
70
|
cerrar(correlationId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Sella la versión del flujo y devuelve su huella, o `null` si no hay flujo
|
|
73
|
+
* que sellar.
|
|
74
|
+
*
|
|
75
|
+
* ## 🔥 Por qué está en el contrato y no se calcula donde se graba
|
|
76
|
+
*
|
|
77
|
+
* La huella ES la forma del flujo, y la forma se lee del LIENZO, que hoy
|
|
78
|
+
* vive en el gateway. Sin lienzo no hay huella, y no se puede inventar.
|
|
79
|
+
*
|
|
80
|
+
* Cuando hw-nodes empezó a grabar el historial por su cuenta, esta llamada
|
|
81
|
+
* se quedó fuera — y el efecto no era «esta corrida pierde su foto»:
|
|
82
|
+
* `asegurarVersion` no CONSULTA la versión, la **CREA**. El que graba el paso
|
|
83
|
+
* es el único que la llama, así que sin ella **deja de haber fotos** y el
|
|
84
|
+
* historial de versiones del flujo se congela en la última que selló el
|
|
85
|
+
* gateway.
|
|
86
|
+
*
|
|
87
|
+
* ## ⚠️ Es `Promise`, a diferencia de los otros dos
|
|
88
|
+
*
|
|
89
|
+
* `registrarPaso` y `cerrar` son `void` porque corren en el camino caliente
|
|
90
|
+
* de cada paso. Éste no: se llama **una vez por corrida** —quien lo
|
|
91
|
+
* implemente debe recordar el resultado por `correlationId`— y el que graba
|
|
92
|
+
* necesita la huella para escribirla en la fila. Un `void` aquí obligaría a
|
|
93
|
+
* escribir la corrida sin huella y volver a por ella después.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ Y devuelve `null` de verdad cuando no hay nada que sellar (sin
|
|
96
|
+
* `workspaceId`, o si el lienzo falla). `null` NO es «reusa la última»:
|
|
97
|
+
* escribir una huella que no es la de esa corrida se lee peor que no tener
|
|
98
|
+
* ninguna, porque nadie sospecha de ella.
|
|
99
|
+
*/
|
|
100
|
+
sellarVersion(orgId: string, workspaceId?: string,
|
|
101
|
+
/** De qué nodo salió la corrida. Decide QUÉ flujo se sella. */
|
|
102
|
+
sourceId?: string): Promise<string | null>;
|
|
71
103
|
}
|
|
72
104
|
/**
|
|
73
105
|
* ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostwebhook/platform-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Contratos compartidos entre los servicios de HostWebhook: addons del plan, identidad interna, y las formas que cruzan una frontera de red",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|