@hostwebhook/platform-contracts 0.9.0 → 0.11.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/dist/precios/index.d.ts +15 -3
- package/dist/precios/index.js +15 -3
- package/dist/precios/tabla-de-precios.d.ts +71 -3
- package/dist/precios/tabla-de-precios.js +108 -16
- package/dist/precios/tarifas-de-openrouter.d.ts +99 -0
- package/dist/precios/tarifas-de-openrouter.js +154 -0
- package/dist/servicios/ejecucion-de-ia.d.ts +24 -4
- package/dist/servicios/ejecucion-de-ia.js +0 -34
- package/dist/servicios/trazas-de-llm.d.ts +30 -1
- package/dist/servicios/trazas-de-llm.js +0 -18
- package/package.json +3 -3
package/dist/precios/index.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lo que cuesta un token, por modelo.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Dos módulos, y la diferencia entre ellos es DE DÓNDE SALE EL PRECIO:
|
|
5
|
+
*
|
|
6
|
+
* - `tabla-de-precios`: la tabla que mantenemos a mano, contra la página de
|
|
7
|
+
* cada proveedor. Es la fuente para casi todo, y también donde vive el
|
|
8
|
+
* cálculo (`calculateCost`, `tarifaDelTramo`).
|
|
9
|
+
* - `tarifas-de-openrouter`: el precio que trae el propio proveedor. Sus
|
|
10
|
+
* ~425 modelos publican su tarifa junto a la lista, así que se convierten
|
|
11
|
+
* y se pasan por el parámetro `tarifa` de `calculateCost` en vez de
|
|
12
|
+
* copiarse a la tabla. Un proveedor futuro que también traiga su precio
|
|
13
|
+
* entra por aquí, con su propio conversor y el mismo parámetro.
|
|
14
|
+
*
|
|
15
|
+
* Están en su propia carpeta porque lo que va a crecer aquí son las tarifas
|
|
16
|
+
* —contexto largo, audio, lotes, proveedores que se tarifan solos— y no las
|
|
17
|
+
* formas de contrato, que es de lo que va `servicios/`.
|
|
7
18
|
*
|
|
8
19
|
* ⚠️ Aquí NO van los precios de venta ni los ids de Stripe. Ver la cabecera de
|
|
9
20
|
* `tabla-de-precios.ts`: son cosas distintas que se llaman igual.
|
|
10
21
|
*/
|
|
11
22
|
export * from './tabla-de-precios';
|
|
23
|
+
export * from './tarifas-de-openrouter';
|
package/dist/precios/index.js
CHANGED
|
@@ -17,11 +17,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
/**
|
|
18
18
|
* Lo que cuesta un token, por modelo.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Dos módulos, y la diferencia entre ellos es DE DÓNDE SALE EL PRECIO:
|
|
21
|
+
*
|
|
22
|
+
* - `tabla-de-precios`: la tabla que mantenemos a mano, contra la página de
|
|
23
|
+
* cada proveedor. Es la fuente para casi todo, y también donde vive el
|
|
24
|
+
* cálculo (`calculateCost`, `tarifaDelTramo`).
|
|
25
|
+
* - `tarifas-de-openrouter`: el precio que trae el propio proveedor. Sus
|
|
26
|
+
* ~425 modelos publican su tarifa junto a la lista, así que se convierten
|
|
27
|
+
* y se pasan por el parámetro `tarifa` de `calculateCost` en vez de
|
|
28
|
+
* copiarse a la tabla. Un proveedor futuro que también traiga su precio
|
|
29
|
+
* entra por aquí, con su propio conversor y el mismo parámetro.
|
|
30
|
+
*
|
|
31
|
+
* Están en su propia carpeta porque lo que va a crecer aquí son las tarifas
|
|
32
|
+
* —contexto largo, audio, lotes, proveedores que se tarifan solos— y no las
|
|
33
|
+
* formas de contrato, que es de lo que va `servicios/`.
|
|
23
34
|
*
|
|
24
35
|
* ⚠️ Aquí NO van los precios de venta ni los ids de Stripe. Ver la cabecera de
|
|
25
36
|
* `tabla-de-precios.ts`: son cosas distintas que se llaman igual.
|
|
26
37
|
*/
|
|
27
38
|
__exportStar(require("./tabla-de-precios"), exports);
|
|
39
|
+
__exportStar(require("./tarifas-de-openrouter"), exports);
|
|
@@ -47,6 +47,24 @@
|
|
|
47
47
|
* Se conservan modelos retirados a propósito: las trazas históricas siguen
|
|
48
48
|
* consultándose y tienen que seguir cuadrando.
|
|
49
49
|
*
|
|
50
|
+
* ## Lo que esta tabla deliberadamente NO contiene: OpenRouter
|
|
51
|
+
*
|
|
52
|
+
* Sus ~425 modelos NO están aquí y no van a estarlo. No es un hueco: es que su
|
|
53
|
+
* API publica el precio DE CADA MODELO en la misma respuesta que la lista, así
|
|
54
|
+
* que copiarlo aquí sería mantener a mano lo que ya nos dan, y quedarse viejo
|
|
55
|
+
* en silencio 425 veces —que es el fallo del que va la sección de arriba—.
|
|
56
|
+
*
|
|
57
|
+
* Se cobran por el otro camino, el que abre el parámetro `tarifa` de
|
|
58
|
+
* `calculateCost` e `isModelPriced`: quien tiene la respuesta de OpenRouter la
|
|
59
|
+
* pasa por `tarifaDeOpenRouter` (fichero de al lado) y entrega el
|
|
60
|
+
* `ModelPricing` resultante. Mismo cálculo, mismos tramos, otro origen del
|
|
61
|
+
* dato.
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ Y por eso el id de OpenRouter NUNCA se recorta para buscarlo aquí:
|
|
64
|
+
* `"anthropic/claude-sonnet-5"` no es `"claude-sonnet-5"`. Comprarle un
|
|
65
|
+
* modelo a un revendedor no cuesta lo que comprárselo al laboratorio, así que
|
|
66
|
+
* dejar que caiga en esta tabla cobraría el precio de otro trato.
|
|
67
|
+
*
|
|
50
68
|
* ## Lo que esta tabla NO modela (aceptable para estimar coste)
|
|
51
69
|
*
|
|
52
70
|
* - (RESUELTO, ver `umbralEntrada`) Contexto largo. Ya se modela en los diez
|
|
@@ -160,8 +178,36 @@ export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
|
160
178
|
* llamante un número inventado con pinta de bueno.
|
|
161
179
|
*/
|
|
162
180
|
export declare function findPricingKey(model: string): string | null;
|
|
163
|
-
/**
|
|
164
|
-
|
|
181
|
+
/**
|
|
182
|
+
* True cuando sabemos de verdad cuánto vale este modelo.
|
|
183
|
+
*
|
|
184
|
+
* ## El segundo argumento: cuando el precio NO sale de la tabla
|
|
185
|
+
*
|
|
186
|
+
* Hay proveedores que traen su tarifa con el modelo —OpenRouter publica el
|
|
187
|
+
* precio de cada uno de sus ~425 en la misma respuesta que la lista—, y meter
|
|
188
|
+
* eso en `MODEL_PRICING` sería mantener a mano un dato que la API ya da. Para
|
|
189
|
+
* esos, quien llama trae la tarifa ya convertida y la pasa aquí.
|
|
190
|
+
*
|
|
191
|
+
* Los tres estados son distintos y hay que respetarlos:
|
|
192
|
+
*
|
|
193
|
+
* - `undefined` (no se pasa nada) → decide la tabla, como siempre. Ningún
|
|
194
|
+
* llamador de los de antes cambia de comportamiento.
|
|
195
|
+
* - un `ModelPricing` → **sí lo sabemos**, y lo sabemos mejor que la tabla:
|
|
196
|
+
* viene del proveedor. `true` sin mirar la tabla, aunque el modelo no esté
|
|
197
|
+
* —que es justo el caso: `"anthropic/claude-sonnet-5"` no está ni va a
|
|
198
|
+
* estar—.
|
|
199
|
+
* - `null` → se preguntó y **no se sabe**. `false`.
|
|
200
|
+
*
|
|
201
|
+
* ⚠️ Ese `null` no es adorno. `tarifaDeOpenRouter` devuelve `null` cuando el
|
|
202
|
+
* modelo no publica precio utilizable, siguiendo el mismo criterio que
|
|
203
|
+
* `findPricingKey`: antes decir «no lo sé» que devolver un número inventado.
|
|
204
|
+
* Encadenarlos —`isModelPriced(id, tarifaDeOpenRouter(m))`— conserva esa
|
|
205
|
+
* distinción de punta a punta. Convertir el `null` en `undefined` por el
|
|
206
|
+
* camino (un `?? undefined`, un `||`) la pierde: preguntaría a la tabla, la
|
|
207
|
+
* tabla tampoco lo sabría, y saldría el mismo `false` por casualidad hoy y no
|
|
208
|
+
* mañana.
|
|
209
|
+
*/
|
|
210
|
+
export declare function isModelPriced(model: string, tarifa?: ModelPricing | null): boolean;
|
|
165
211
|
/**
|
|
166
212
|
* Coste en USD, redondeado a 6 decimales (precisión de micro-dólar).
|
|
167
213
|
*
|
|
@@ -190,8 +236,30 @@ export declare function isModelPriced(model: string): boolean;
|
|
|
190
236
|
* Antes se sumaba `reasoningTokens * precio_de_salida` ENCIMA de
|
|
191
237
|
* `outputTokens` para openai y google: eso cobraba dos veces el mismo
|
|
192
238
|
* pensamiento. El parámetro se conserva para no romper a quien ya lo pasa.
|
|
239
|
+
*
|
|
240
|
+
* ## `tarifa`: el precio que trae el proveedor
|
|
241
|
+
*
|
|
242
|
+
* Último y opcional, para que ningún llamador de los que ya existen cambie.
|
|
243
|
+
* Cuando se pasa, ES la tarifa: la tabla no se consulta y `model` sólo sirve
|
|
244
|
+
* ya para detectar el proveedor de los multiplicadores de caché. Es lo que
|
|
245
|
+
* usa OpenRouter, cuyos ~425 modelos publican su propio precio y por eso NO
|
|
246
|
+
* están en `MODEL_PRICING`; sirve igual para cualquier proveedor futuro que
|
|
247
|
+
* traiga el suyo.
|
|
248
|
+
*
|
|
249
|
+
* Los tres estados son los mismos que en `isModelPriced`: `undefined` decide
|
|
250
|
+
* la tabla, un objeto manda, y `null` significa «se preguntó y no se sabe» →
|
|
251
|
+
* coste 0, igual que un modelo ausente de la tabla, y con la misma trampa: 0
|
|
252
|
+
* no es gratis. Pregunta antes con `isModelPriced(model, tarifa)`.
|
|
253
|
+
*
|
|
254
|
+
* ⚠️ La tarifa que llega por parámetro pasa por `tarifaDelTramo` EXACTAMENTE
|
|
255
|
+
* igual que la de la tabla, y es a propósito que sea el mismo camino y no uno
|
|
256
|
+
* paralelo. Hoy nadie la usa con tramo —OpenRouter no publica umbrales, así
|
|
257
|
+
* que sus tarifas salen sin `umbralEntrada` y `tarifaDelTramo` las devuelve
|
|
258
|
+
* tal cual—, pero el día que un proveedor publique uno, funciona sin tocar
|
|
259
|
+
* nada. Un segundo camino sin tramos sería un acantilado que sólo se cobra por
|
|
260
|
+
* un lado.
|
|
193
261
|
*/
|
|
194
|
-
export declare function calculateCost(model: string, inputTokens: number, outputTokens: number, cacheReadInputTokens?: number, cacheCreationInputTokens?: number, _reasoningTokens?: number): number;
|
|
262
|
+
export declare function calculateCost(model: string, inputTokens: number, outputTokens: number, cacheReadInputTokens?: number, cacheCreationInputTokens?: number, _reasoningTokens?: number, tarifa?: ModelPricing | null): number;
|
|
195
263
|
/** Los modelos que la tabla sabe poner en precio, en orden de declaración. */
|
|
196
264
|
export declare function getKnownModels(): string[];
|
|
197
265
|
/**
|
|
@@ -48,6 +48,24 @@
|
|
|
48
48
|
* Se conservan modelos retirados a propósito: las trazas históricas siguen
|
|
49
49
|
* consultándose y tienen que seguir cuadrando.
|
|
50
50
|
*
|
|
51
|
+
* ## Lo que esta tabla deliberadamente NO contiene: OpenRouter
|
|
52
|
+
*
|
|
53
|
+
* Sus ~425 modelos NO están aquí y no van a estarlo. No es un hueco: es que su
|
|
54
|
+
* API publica el precio DE CADA MODELO en la misma respuesta que la lista, así
|
|
55
|
+
* que copiarlo aquí sería mantener a mano lo que ya nos dan, y quedarse viejo
|
|
56
|
+
* en silencio 425 veces —que es el fallo del que va la sección de arriba—.
|
|
57
|
+
*
|
|
58
|
+
* Se cobran por el otro camino, el que abre el parámetro `tarifa` de
|
|
59
|
+
* `calculateCost` e `isModelPriced`: quien tiene la respuesta de OpenRouter la
|
|
60
|
+
* pasa por `tarifaDeOpenRouter` (fichero de al lado) y entrega el
|
|
61
|
+
* `ModelPricing` resultante. Mismo cálculo, mismos tramos, otro origen del
|
|
62
|
+
* dato.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ Y por eso el id de OpenRouter NUNCA se recorta para buscarlo aquí:
|
|
65
|
+
* `"anthropic/claude-sonnet-5"` no es `"claude-sonnet-5"`. Comprarle un
|
|
66
|
+
* modelo a un revendedor no cuesta lo que comprárselo al laboratorio, así que
|
|
67
|
+
* dejar que caiga en esta tabla cobraría el precio de otro trato.
|
|
68
|
+
*
|
|
51
69
|
* ## Lo que esta tabla NO modela (aceptable para estimar coste)
|
|
52
70
|
*
|
|
53
71
|
* - (RESUELTO, ver `umbralEntrada`) Contexto largo. Ya se modela en los diez
|
|
@@ -306,8 +324,38 @@ function findPricingKey(model) {
|
|
|
306
324
|
}
|
|
307
325
|
return mejor;
|
|
308
326
|
}
|
|
309
|
-
/**
|
|
310
|
-
|
|
327
|
+
/**
|
|
328
|
+
* True cuando sabemos de verdad cuánto vale este modelo.
|
|
329
|
+
*
|
|
330
|
+
* ## El segundo argumento: cuando el precio NO sale de la tabla
|
|
331
|
+
*
|
|
332
|
+
* Hay proveedores que traen su tarifa con el modelo —OpenRouter publica el
|
|
333
|
+
* precio de cada uno de sus ~425 en la misma respuesta que la lista—, y meter
|
|
334
|
+
* eso en `MODEL_PRICING` sería mantener a mano un dato que la API ya da. Para
|
|
335
|
+
* esos, quien llama trae la tarifa ya convertida y la pasa aquí.
|
|
336
|
+
*
|
|
337
|
+
* Los tres estados son distintos y hay que respetarlos:
|
|
338
|
+
*
|
|
339
|
+
* - `undefined` (no se pasa nada) → decide la tabla, como siempre. Ningún
|
|
340
|
+
* llamador de los de antes cambia de comportamiento.
|
|
341
|
+
* - un `ModelPricing` → **sí lo sabemos**, y lo sabemos mejor que la tabla:
|
|
342
|
+
* viene del proveedor. `true` sin mirar la tabla, aunque el modelo no esté
|
|
343
|
+
* —que es justo el caso: `"anthropic/claude-sonnet-5"` no está ni va a
|
|
344
|
+
* estar—.
|
|
345
|
+
* - `null` → se preguntó y **no se sabe**. `false`.
|
|
346
|
+
*
|
|
347
|
+
* ⚠️ Ese `null` no es adorno. `tarifaDeOpenRouter` devuelve `null` cuando el
|
|
348
|
+
* modelo no publica precio utilizable, siguiendo el mismo criterio que
|
|
349
|
+
* `findPricingKey`: antes decir «no lo sé» que devolver un número inventado.
|
|
350
|
+
* Encadenarlos —`isModelPriced(id, tarifaDeOpenRouter(m))`— conserva esa
|
|
351
|
+
* distinción de punta a punta. Convertir el `null` en `undefined` por el
|
|
352
|
+
* camino (un `?? undefined`, un `||`) la pierde: preguntaría a la tabla, la
|
|
353
|
+
* tabla tampoco lo sabría, y saldría el mismo `false` por casualidad hoy y no
|
|
354
|
+
* mañana.
|
|
355
|
+
*/
|
|
356
|
+
function isModelPriced(model, tarifa) {
|
|
357
|
+
if (tarifa !== undefined)
|
|
358
|
+
return tarifa !== null;
|
|
311
359
|
return findPricingKey(model) !== null;
|
|
312
360
|
}
|
|
313
361
|
/* ── Coste ───────────────────────────────────────────────────────── */
|
|
@@ -339,13 +387,51 @@ function isModelPriced(model) {
|
|
|
339
387
|
* Antes se sumaba `reasoningTokens * precio_de_salida` ENCIMA de
|
|
340
388
|
* `outputTokens` para openai y google: eso cobraba dos veces el mismo
|
|
341
389
|
* pensamiento. El parámetro se conserva para no romper a quien ya lo pasa.
|
|
390
|
+
*
|
|
391
|
+
* ## `tarifa`: el precio que trae el proveedor
|
|
392
|
+
*
|
|
393
|
+
* Último y opcional, para que ningún llamador de los que ya existen cambie.
|
|
394
|
+
* Cuando se pasa, ES la tarifa: la tabla no se consulta y `model` sólo sirve
|
|
395
|
+
* ya para detectar el proveedor de los multiplicadores de caché. Es lo que
|
|
396
|
+
* usa OpenRouter, cuyos ~425 modelos publican su propio precio y por eso NO
|
|
397
|
+
* están en `MODEL_PRICING`; sirve igual para cualquier proveedor futuro que
|
|
398
|
+
* traiga el suyo.
|
|
399
|
+
*
|
|
400
|
+
* Los tres estados son los mismos que en `isModelPriced`: `undefined` decide
|
|
401
|
+
* la tabla, un objeto manda, y `null` significa «se preguntó y no se sabe» →
|
|
402
|
+
* coste 0, igual que un modelo ausente de la tabla, y con la misma trampa: 0
|
|
403
|
+
* no es gratis. Pregunta antes con `isModelPriced(model, tarifa)`.
|
|
404
|
+
*
|
|
405
|
+
* ⚠️ La tarifa que llega por parámetro pasa por `tarifaDelTramo` EXACTAMENTE
|
|
406
|
+
* igual que la de la tabla, y es a propósito que sea el mismo camino y no uno
|
|
407
|
+
* paralelo. Hoy nadie la usa con tramo —OpenRouter no publica umbrales, así
|
|
408
|
+
* que sus tarifas salen sin `umbralEntrada` y `tarifaDelTramo` las devuelve
|
|
409
|
+
* tal cual—, pero el día que un proveedor publique uno, funciona sin tocar
|
|
410
|
+
* nada. Un segundo camino sin tramos sería un acantilado que sólo se cobra por
|
|
411
|
+
* un lado.
|
|
342
412
|
*/
|
|
343
|
-
function calculateCost(model, inputTokens, outputTokens, cacheReadInputTokens = 0, cacheCreationInputTokens = 0, _reasoningTokens = 0) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
413
|
+
function calculateCost(model, inputTokens, outputTokens, cacheReadInputTokens = 0, cacheCreationInputTokens = 0, _reasoningTokens = 0, tarifa) {
|
|
414
|
+
let pricing;
|
|
415
|
+
/* Sobre qué cadena se detecta el proveedor de los multiplicadores de caché:
|
|
416
|
+
con tabla, sobre la CLAVE que ganó (`claude-sonnet-4-6-20260101` resuelve
|
|
417
|
+
a `claude-sonnet-4-6`); con tarifa por parámetro no hay clave, así que
|
|
418
|
+
sobre el modelo pedido — que en OpenRouter lleva el laboratorio delante
|
|
419
|
+
(`anthropic/claude-sonnet-5`) y `detectProvider` reconoce igual. */
|
|
420
|
+
let paraDetectarProveedor;
|
|
421
|
+
if (tarifa !== undefined) {
|
|
422
|
+
if (tarifa === null)
|
|
423
|
+
return 0; // se preguntó y no se sabe
|
|
424
|
+
pricing = tarifa;
|
|
425
|
+
paraDetectarProveedor = model;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const key = findPricingKey(model);
|
|
429
|
+
if (!key)
|
|
430
|
+
return 0;
|
|
431
|
+
pricing = exports.MODEL_PRICING[key];
|
|
432
|
+
paraDetectarProveedor = key;
|
|
433
|
+
}
|
|
434
|
+
const provider = detectProvider(paraDetectarProveedor);
|
|
349
435
|
/**
|
|
350
436
|
* Los tokens cacheados CUENTAN para decidir el tramo: fisicamente son el
|
|
351
437
|
* prompt, y en un tope de gasto errar del lado caro es errar del lado seguro.
|
|
@@ -356,17 +442,23 @@ function calculateCost(model, inputTokens, outputTokens, cacheReadInputTokens =
|
|
|
356
442
|
* hay que confirmarla antes de que alguien empiece a rellenarlos.
|
|
357
443
|
*/
|
|
358
444
|
const entradaParaElTramo = inputTokens + cacheReadInputTokens + cacheCreationInputTokens;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
445
|
+
/* `tarifaAplicada` y no `tarifa` a secas porque `tarifa` es ahora el
|
|
446
|
+
PARÁMETRO, y son dos cosas distintas a propósito: `tarifa` es lo que trajo
|
|
447
|
+
quien llama —o nada—, y esto es lo que se cobra de verdad tras decidir el
|
|
448
|
+
tramo. Las dos pasan por aquí, venga el precio de la tabla o de fuera. */
|
|
449
|
+
const tarifaAplicada = tarifaDelTramo(pricing, entradaParaElTramo);
|
|
450
|
+
const inputCost = (inputTokens / 1000000) * tarifaAplicada.input;
|
|
451
|
+
const outputCost = (outputTokens / 1000000) * tarifaAplicada.output;
|
|
362
452
|
// Caché: multiplicadores por proveedor
|
|
363
453
|
const cacheReadMul = CACHE_READ_MULTIPLIERS[provider] ?? 0.1;
|
|
364
454
|
const cacheCreationMul = CACHE_CREATION_MULTIPLIERS[provider] ?? 0;
|
|
365
|
-
// Sobre `
|
|
366
|
-
// tambien se encarece, que es lo que dice la tabla de Google —su fila
|
|
367
|
-
// cache esta partida por tamano de prompt igual que las otras dos.
|
|
368
|
-
const cacheReadCost = (cacheReadInputTokens / 1000000) *
|
|
369
|
-
const cacheCreationCost = (cacheCreationInputTokens / 1000000) *
|
|
455
|
+
// Sobre `tarifaAplicada.input`, no sobre `pricing.input`: en tramo alto la
|
|
456
|
+
// cache tambien se encarece, que es lo que dice la tabla de Google —su fila
|
|
457
|
+
// de cache esta partida por tamano de prompt igual que las otras dos.
|
|
458
|
+
const cacheReadCost = (cacheReadInputTokens / 1000000) * tarifaAplicada.input * cacheReadMul;
|
|
459
|
+
const cacheCreationCost = (cacheCreationInputTokens / 1000000) *
|
|
460
|
+
tarifaAplicada.input *
|
|
461
|
+
cacheCreationMul;
|
|
370
462
|
return (Math.round((inputCost + outputCost + cacheReadCost + cacheCreationCost) * 1000000) / 1000000);
|
|
371
463
|
}
|
|
372
464
|
/** Los modelos que la tabla sabe poner en precio, en orden de declaración. */
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De lo que publica OpenRouter a un `ModelPricing` nuestro.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué hace falta convertir nada
|
|
5
|
+
*
|
|
6
|
+
* Porque las dos puntas miden distinto, y en las dos dimensiones a la vez:
|
|
7
|
+
*
|
|
8
|
+
* | | OpenRouter | `ModelPricing` |
|
|
9
|
+
* |-|-|-|
|
|
10
|
+
* | unidad | **por token** | **por millón de tokens** |
|
|
11
|
+
* | tipo | **cadena** | **número** |
|
|
12
|
+
*
|
|
13
|
+
* `{"prompt": "0.0000001"}` son $0,10 el millón. Un `parseFloat` sin el
|
|
14
|
+
* `* 1_000_000` cobra un millón de veces de menos, y como el resultado
|
|
15
|
+
* redondea a cero micro-dólares, un tope de gasto puesto encima no salta
|
|
16
|
+
* jamás. Es el mismo modo de fallo que un modelo ausente de la tabla: sale 0 y
|
|
17
|
+
* nadie se entera.
|
|
18
|
+
*
|
|
19
|
+
* ## La decisión que importa: `"0"` NO es lo mismo que ausente
|
|
20
|
+
*
|
|
21
|
+
* Los dos casos existen HOY, contados sobre la respuesta real (2026-08-31, 425
|
|
22
|
+
* modelos):
|
|
23
|
+
*
|
|
24
|
+
* - **21 modelos gratis de verdad**, los `:free`, con `pricing` a `"0"`. Eso
|
|
25
|
+
* es un precio CONOCIDO que resulta ser cero, y hay que poder cobrarlo como
|
|
26
|
+
* tal: `{ input: 0, output: 0 }`.
|
|
27
|
+
* - **5 modelos con `"-1"`**: `openrouter/auto`, `auto-beta`, `fusion`,
|
|
28
|
+
* `pareto-code` y `bodybuilder`. Son routers dentro del router — lo que
|
|
29
|
+
* cuesten depende de a dónde acaben enrutando, y OpenRouter lo dice con ese
|
|
30
|
+
* `-1`. Eso es **no lo sabemos**, y ahí se devuelve `null`.
|
|
31
|
+
*
|
|
32
|
+
* Un modelo sin `pricing`, con el campo a `null` o con basura no numérica cae
|
|
33
|
+
* también en `null`. Hoy no hay ninguno —los 425 traen `prompt` y `completion`—
|
|
34
|
+
* pero es una respuesta de red y nada nuestro lo garantiza mañana.
|
|
35
|
+
*
|
|
36
|
+
* El criterio es el que ya tiene `findPricingKey`: antes decir «no lo sé» que
|
|
37
|
+
* devolver un número inventado. Los dos casos acaban en un coste de 0 dólares
|
|
38
|
+
* si nadie mira, y significan lo contrario: «esta llamada no cuesta nada»
|
|
39
|
+
* frente a «esta llamada puede costar cualquier cosa». Quien ponga un tope
|
|
40
|
+
* tiene que preguntar con `isModelPriced(id, tarifa)`, que distingue el objeto
|
|
41
|
+
* del `null`, y no mirar el número.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ Un `-1` que pasara el filtro se convertiría en −$1.000.000 el millón: el
|
|
44
|
+
* gasto acumulado BAJARÍA al usarlo, y el tope se alejaría cuanto más se
|
|
45
|
+
* gastara. Cualquier cosa que no sea un número finito y ≥ 0 es desconocido.
|
|
46
|
+
*
|
|
47
|
+
* ## Lo que esta conversión NO trae, y se dice en vez de fingir
|
|
48
|
+
*
|
|
49
|
+
* - **La caché.** `input_cache_read`, `input_cache_write` y hasta
|
|
50
|
+
* `input_cache_write_1h` vienen en la respuesta y aquí se ignoran, porque
|
|
51
|
+
* `ModelPricing` no tiene dónde meterlos: el modelo de caché de
|
|
52
|
+
* `calculateCost` son multiplicadores por proveedor, no precios absolutos.
|
|
53
|
+
* Consecuencia concreta: a un modelo de OpenRouter la caché se le estima
|
|
54
|
+
* con el multiplicador que salga de `detectProvider` sobre su id, no con su
|
|
55
|
+
* precio real. Hoy es inerte —el emisor de trazas no rellena los campos de
|
|
56
|
+
* caché— pero deja de serlo el día que alguien los rellene. Cerrarlo es
|
|
57
|
+
* dar campos absolutos de caché a `ModelPricing`, no parchear esto.
|
|
58
|
+
* - **Tramos de contexto largo.** OpenRouter no publica umbrales, así que
|
|
59
|
+
* las tarifas salen SIN `umbralEntrada` y `tarifaDelTramo` las devuelve
|
|
60
|
+
* planas. No es que se ignore un tramo: es que no hay ninguno que leer.
|
|
61
|
+
* - **Lo que no se cobra por token**: `request` (precio por petición),
|
|
62
|
+
* `image`, `web_search`, `internal_reasoning`. Un modelo con `request` > 0
|
|
63
|
+
* sale INFRAVALORADO por lo que cueste la petición en sí.
|
|
64
|
+
*/
|
|
65
|
+
import type { ModeloDeOpenRouter, PrecioDeOpenRouter } from '@hostwebhook/node-types';
|
|
66
|
+
import type { ModelPricing } from './tabla-de-precios';
|
|
67
|
+
/**
|
|
68
|
+
* La tarifa de un modelo de OpenRouter, o `null` cuando no se sabe.
|
|
69
|
+
*
|
|
70
|
+
* Acepta el modelo entero —que es lo que se tiene tras el fetch— o su bloque
|
|
71
|
+
* `pricing` suelto.
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ Hacen falta LAS DOS mitades. Con `prompt` pero sin `completion` no se
|
|
74
|
+
* devuelve media tarifa poniendo la salida a 0: la salida es la cara —un 5x
|
|
75
|
+
* sobre la entrada es lo normal—, así que una tarifa a medias no infravalora
|
|
76
|
+
* un poco, infravalora justo por donde duele. O se sabe entero o no se sabe.
|
|
77
|
+
*/
|
|
78
|
+
export declare function tarifaDeOpenRouter(modelo: ModeloDeOpenRouter | PrecioDeOpenRouter | null | undefined): ModelPricing | null;
|
|
79
|
+
/**
|
|
80
|
+
* Un índice `id → tarifa` a partir del catálogo entero.
|
|
81
|
+
*
|
|
82
|
+
* Para quien va a tarifar muchas llamadas contra una sola respuesta —el
|
|
83
|
+
* selector del dashboard, o un caché de proceso en la api— en vez de recorrer
|
|
84
|
+
* los 425 por cada llamada.
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ Los modelos cuya tarifa no se conoce **NO entran en el mapa**, y esa
|
|
87
|
+
* ausencia es la información: `mapa.get(id)` devuelve `undefined`, que es
|
|
88
|
+
* precisamente lo que `calculateCost` interpreta como «mira la tabla» y NO
|
|
89
|
+
* como «no se sabe». Quien consulte este mapa tiene que convertir ese hueco en
|
|
90
|
+
* un `null` explícito antes de pasarlo:
|
|
91
|
+
*
|
|
92
|
+
* const tarifa = mapa.get(id) ?? null; // ← el `?? null` es obligatorio
|
|
93
|
+
* if (!isModelPriced(id, tarifa)) { … }
|
|
94
|
+
*
|
|
95
|
+
* Se deja así, y no metiendo `null`s en el mapa, porque un `Map` con valores
|
|
96
|
+
* nulos invita a `mapa.has(id)` como si fuera «lo sé», que es la pregunta
|
|
97
|
+
* equivocada.
|
|
98
|
+
*/
|
|
99
|
+
export declare function tarifasDeOpenRouter(modelos: ModeloDeOpenRouter[] | null | undefined): Map<string, ModelPricing>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* De lo que publica OpenRouter a un `ModelPricing` nuestro.
|
|
4
|
+
*
|
|
5
|
+
* ## Por qué hace falta convertir nada
|
|
6
|
+
*
|
|
7
|
+
* Porque las dos puntas miden distinto, y en las dos dimensiones a la vez:
|
|
8
|
+
*
|
|
9
|
+
* | | OpenRouter | `ModelPricing` |
|
|
10
|
+
* |-|-|-|
|
|
11
|
+
* | unidad | **por token** | **por millón de tokens** |
|
|
12
|
+
* | tipo | **cadena** | **número** |
|
|
13
|
+
*
|
|
14
|
+
* `{"prompt": "0.0000001"}` son $0,10 el millón. Un `parseFloat` sin el
|
|
15
|
+
* `* 1_000_000` cobra un millón de veces de menos, y como el resultado
|
|
16
|
+
* redondea a cero micro-dólares, un tope de gasto puesto encima no salta
|
|
17
|
+
* jamás. Es el mismo modo de fallo que un modelo ausente de la tabla: sale 0 y
|
|
18
|
+
* nadie se entera.
|
|
19
|
+
*
|
|
20
|
+
* ## La decisión que importa: `"0"` NO es lo mismo que ausente
|
|
21
|
+
*
|
|
22
|
+
* Los dos casos existen HOY, contados sobre la respuesta real (2026-08-31, 425
|
|
23
|
+
* modelos):
|
|
24
|
+
*
|
|
25
|
+
* - **21 modelos gratis de verdad**, los `:free`, con `pricing` a `"0"`. Eso
|
|
26
|
+
* es un precio CONOCIDO que resulta ser cero, y hay que poder cobrarlo como
|
|
27
|
+
* tal: `{ input: 0, output: 0 }`.
|
|
28
|
+
* - **5 modelos con `"-1"`**: `openrouter/auto`, `auto-beta`, `fusion`,
|
|
29
|
+
* `pareto-code` y `bodybuilder`. Son routers dentro del router — lo que
|
|
30
|
+
* cuesten depende de a dónde acaben enrutando, y OpenRouter lo dice con ese
|
|
31
|
+
* `-1`. Eso es **no lo sabemos**, y ahí se devuelve `null`.
|
|
32
|
+
*
|
|
33
|
+
* Un modelo sin `pricing`, con el campo a `null` o con basura no numérica cae
|
|
34
|
+
* también en `null`. Hoy no hay ninguno —los 425 traen `prompt` y `completion`—
|
|
35
|
+
* pero es una respuesta de red y nada nuestro lo garantiza mañana.
|
|
36
|
+
*
|
|
37
|
+
* El criterio es el que ya tiene `findPricingKey`: antes decir «no lo sé» que
|
|
38
|
+
* devolver un número inventado. Los dos casos acaban en un coste de 0 dólares
|
|
39
|
+
* si nadie mira, y significan lo contrario: «esta llamada no cuesta nada»
|
|
40
|
+
* frente a «esta llamada puede costar cualquier cosa». Quien ponga un tope
|
|
41
|
+
* tiene que preguntar con `isModelPriced(id, tarifa)`, que distingue el objeto
|
|
42
|
+
* del `null`, y no mirar el número.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ Un `-1` que pasara el filtro se convertiría en −$1.000.000 el millón: el
|
|
45
|
+
* gasto acumulado BAJARÍA al usarlo, y el tope se alejaría cuanto más se
|
|
46
|
+
* gastara. Cualquier cosa que no sea un número finito y ≥ 0 es desconocido.
|
|
47
|
+
*
|
|
48
|
+
* ## Lo que esta conversión NO trae, y se dice en vez de fingir
|
|
49
|
+
*
|
|
50
|
+
* - **La caché.** `input_cache_read`, `input_cache_write` y hasta
|
|
51
|
+
* `input_cache_write_1h` vienen en la respuesta y aquí se ignoran, porque
|
|
52
|
+
* `ModelPricing` no tiene dónde meterlos: el modelo de caché de
|
|
53
|
+
* `calculateCost` son multiplicadores por proveedor, no precios absolutos.
|
|
54
|
+
* Consecuencia concreta: a un modelo de OpenRouter la caché se le estima
|
|
55
|
+
* con el multiplicador que salga de `detectProvider` sobre su id, no con su
|
|
56
|
+
* precio real. Hoy es inerte —el emisor de trazas no rellena los campos de
|
|
57
|
+
* caché— pero deja de serlo el día que alguien los rellene. Cerrarlo es
|
|
58
|
+
* dar campos absolutos de caché a `ModelPricing`, no parchear esto.
|
|
59
|
+
* - **Tramos de contexto largo.** OpenRouter no publica umbrales, así que
|
|
60
|
+
* las tarifas salen SIN `umbralEntrada` y `tarifaDelTramo` las devuelve
|
|
61
|
+
* planas. No es que se ignore un tramo: es que no hay ninguno que leer.
|
|
62
|
+
* - **Lo que no se cobra por token**: `request` (precio por petición),
|
|
63
|
+
* `image`, `web_search`, `internal_reasoning`. Un modelo con `request` > 0
|
|
64
|
+
* sale INFRAVALORADO por lo que cueste la petición en sí.
|
|
65
|
+
*/
|
|
66
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
+
exports.tarifaDeOpenRouter = tarifaDeOpenRouter;
|
|
68
|
+
exports.tarifasDeOpenRouter = tarifasDeOpenRouter;
|
|
69
|
+
/** Tokens por unidad de `ModelPricing`. El nombre está para que el `* 1e6` de
|
|
70
|
+
* abajo no parezca una constante mágica: es exactamente este cambio de
|
|
71
|
+
* unidad, de por-token a por-millón. */
|
|
72
|
+
const TOKENS_POR_UNIDAD_DE_TARIFA = 1000000;
|
|
73
|
+
/**
|
|
74
|
+
* USD por millón a partir de un campo por token, o `null` si no se sabe.
|
|
75
|
+
*
|
|
76
|
+
* El redondeo a 9 decimales no es cosmético: `parseFloat('0.0000001') * 1e6`
|
|
77
|
+
* da `0.09999999999999999` en coma flotante, no `0.1`. Sin recortar, cada
|
|
78
|
+
* tarifa llegaría con una cola de basura que luego aparece en cualquier
|
|
79
|
+
* comparación y en cualquier importe mostrado. Nueve decimales borra la cola
|
|
80
|
+
* —que vive quince órdenes de magnitud más abajo— sin tocar el precio más
|
|
81
|
+
* ridículo que OpenRouter llegue a publicar.
|
|
82
|
+
*/
|
|
83
|
+
function porMillon(valor) {
|
|
84
|
+
/* Se aceptan cadena y número. La API manda cadenas, pero un consumidor que
|
|
85
|
+
haya normalizado el JSON antes de llegar aquí no tiene por qué perder el
|
|
86
|
+
dato por eso. */
|
|
87
|
+
const crudo = typeof valor === 'string' ? Number.parseFloat(valor)
|
|
88
|
+
: typeof valor === 'number' ? valor
|
|
89
|
+
: NaN;
|
|
90
|
+
// NaN, Infinity y negativos: desconocido, no cero. Ver la cabecera.
|
|
91
|
+
if (!Number.isFinite(crudo) || crudo < 0)
|
|
92
|
+
return null;
|
|
93
|
+
const porMillonDeTokens = crudo * TOKENS_POR_UNIDAD_DE_TARIFA;
|
|
94
|
+
return Math.round(porMillonDeTokens * 1e9) / 1e9;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* La tarifa de un modelo de OpenRouter, o `null` cuando no se sabe.
|
|
98
|
+
*
|
|
99
|
+
* Acepta el modelo entero —que es lo que se tiene tras el fetch— o su bloque
|
|
100
|
+
* `pricing` suelto.
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ Hacen falta LAS DOS mitades. Con `prompt` pero sin `completion` no se
|
|
103
|
+
* devuelve media tarifa poniendo la salida a 0: la salida es la cara —un 5x
|
|
104
|
+
* sobre la entrada es lo normal—, así que una tarifa a medias no infravalora
|
|
105
|
+
* un poco, infravalora justo por donde duele. O se sabe entero o no se sabe.
|
|
106
|
+
*/
|
|
107
|
+
function tarifaDeOpenRouter(modelo) {
|
|
108
|
+
if (!modelo || typeof modelo !== 'object')
|
|
109
|
+
return null;
|
|
110
|
+
const pricing = 'pricing' in modelo
|
|
111
|
+
? modelo.pricing
|
|
112
|
+
: modelo;
|
|
113
|
+
if (!pricing || typeof pricing !== 'object')
|
|
114
|
+
return null;
|
|
115
|
+
const input = porMillon(pricing.prompt);
|
|
116
|
+
const output = porMillon(pricing.completion);
|
|
117
|
+
if (input === null || output === null)
|
|
118
|
+
return null;
|
|
119
|
+
/* Sin `umbralEntrada`: OpenRouter no publica tramos. Ver la cabecera. */
|
|
120
|
+
return { input, output };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Un índice `id → tarifa` a partir del catálogo entero.
|
|
124
|
+
*
|
|
125
|
+
* Para quien va a tarifar muchas llamadas contra una sola respuesta —el
|
|
126
|
+
* selector del dashboard, o un caché de proceso en la api— en vez de recorrer
|
|
127
|
+
* los 425 por cada llamada.
|
|
128
|
+
*
|
|
129
|
+
* ⚠️ Los modelos cuya tarifa no se conoce **NO entran en el mapa**, y esa
|
|
130
|
+
* ausencia es la información: `mapa.get(id)` devuelve `undefined`, que es
|
|
131
|
+
* precisamente lo que `calculateCost` interpreta como «mira la tabla» y NO
|
|
132
|
+
* como «no se sabe». Quien consulte este mapa tiene que convertir ese hueco en
|
|
133
|
+
* un `null` explícito antes de pasarlo:
|
|
134
|
+
*
|
|
135
|
+
* const tarifa = mapa.get(id) ?? null; // ← el `?? null` es obligatorio
|
|
136
|
+
* if (!isModelPriced(id, tarifa)) { … }
|
|
137
|
+
*
|
|
138
|
+
* Se deja así, y no metiendo `null`s en el mapa, porque un `Map` con valores
|
|
139
|
+
* nulos invita a `mapa.has(id)` como si fuera «lo sé», que es la pregunta
|
|
140
|
+
* equivocada.
|
|
141
|
+
*/
|
|
142
|
+
function tarifasDeOpenRouter(modelos) {
|
|
143
|
+
const mapa = new Map();
|
|
144
|
+
if (!Array.isArray(modelos))
|
|
145
|
+
return mapa;
|
|
146
|
+
for (const modelo of modelos) {
|
|
147
|
+
if (!modelo || typeof modelo.id !== 'string' || modelo.id === '')
|
|
148
|
+
continue;
|
|
149
|
+
const tarifa = tarifaDeOpenRouter(modelo);
|
|
150
|
+
if (tarifa)
|
|
151
|
+
mapa.set(modelo.id, tarifa);
|
|
152
|
+
}
|
|
153
|
+
return mapa;
|
|
154
|
+
}
|
|
@@ -32,14 +32,16 @@
|
|
|
32
32
|
* Antes la puerta leía `memoryEnabled` del documento para decidir; esa decisión
|
|
33
33
|
* se va con el dato, que es donde puede comprobarse.
|
|
34
34
|
*/
|
|
35
|
+
import type { LlmProvider } from '@hostwebhook/node-types';
|
|
35
36
|
/**
|
|
36
|
-
* El nodo conectado, en lo que la puerta lee de él.
|
|
37
|
+
* El nodo conectado, en lo que la puerta lee de él. Ocho campos, medidos.
|
|
37
38
|
*
|
|
38
|
-
* Los cuatro primeros son lo que la puerta necesitaba para EJECUTAR. Los
|
|
39
|
+
* Los cuatro primeros son lo que la puerta necesitaba para EJECUTAR. Los cuatro
|
|
39
40
|
* últimos son lo que necesita para PONERLE PRECIO al turno antes de ejecutarlo:
|
|
40
41
|
* el Chat Trigger tiene topes en dólares y reserva por adelantado, y esa reserva
|
|
41
|
-
* no se puede calcular sin saber a qué modelo se va a llamar ni
|
|
42
|
-
* devolver. Sin ellos la estimación caía a sus valores por defecto
|
|
42
|
+
* no se puede calcular sin saber de quién ni a qué modelo se va a llamar, ni
|
|
43
|
+
* cuánto puede devolver. Sin ellos la estimación caía a sus valores por defecto
|
|
44
|
+
* en silencio.
|
|
43
45
|
*/
|
|
44
46
|
export interface NodoDeIaConectado {
|
|
45
47
|
id: string;
|
|
@@ -48,6 +50,24 @@ export interface NodoDeIaConectado {
|
|
|
48
50
|
workspaceId: string | null;
|
|
49
51
|
/** Si el widget enseña las llamadas a herramientas mientras ocurren. */
|
|
50
52
|
muestraLasHerramientas: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* El proveedor configurado en el nodo. Viaja porque el nombre del modelo NO
|
|
55
|
+
* lo determina, y adivinarlo se rompe de la peor manera: en silencio y con
|
|
56
|
+
* cara de acierto.
|
|
57
|
+
*
|
|
58
|
+
* 🔥 El caso que lo cierra: `openai/gpt-oss-20b` es un id REAL de OpenRouter.
|
|
59
|
+
* Empieza por `openai/`, así que cualquier heurística sobre el nombre lo da
|
|
60
|
+
* por OpenAI, le pone la tarifa de OpenAI y reserva el tope equivocado. Y no
|
|
61
|
+
* es un caso de laboratorio: TODO el catálogo de OpenRouter va prefijado con
|
|
62
|
+
* el nombre del laboratorio de origen (`anthropic/…`, `google/…`), o sea que
|
|
63
|
+
* el choque es la norma ahí, no la excepción.
|
|
64
|
+
*
|
|
65
|
+
* ⚠️ Es la LlmProvider de `node-types`, la misma que elige el selector, y va
|
|
66
|
+
* obligatorio: opcional dejaría intacto el camino de adivinar, que es justo
|
|
67
|
+
* lo que sobra. Quien sirva este contrato lee el campo del nodo; no lo
|
|
68
|
+
* deduzca del modelo.
|
|
69
|
+
*/
|
|
70
|
+
provider: LlmProvider;
|
|
51
71
|
/** El modelo configurado en el nodo. Sin el nombre no hay tarifa, y sin
|
|
52
72
|
* tarifa la reserva del tope EN DÓLARES no se puede calcular: los tokens
|
|
53
73
|
* estimados no son un precio hasta que alguien dice de qué modelo son. */
|
|
@@ -1,38 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Lo que el Chat Trigger necesita del nodo de IA.
|
|
4
|
-
*
|
|
5
|
-
* ## Por qué existe
|
|
6
|
-
*
|
|
7
|
-
* `chat-triggers` vive en el gateway —es uno de los cinco nodos de ORIGEN— y
|
|
8
|
-
* `ai-nodes` se muda a hw-nodes. Era el último cruce de la puerta hacia el
|
|
9
|
-
* ejecutor, y el más difícil de los tres: los otros dos eran filas de base de
|
|
10
|
-
* datos; éste lleva STREAMING.
|
|
11
|
-
*
|
|
12
|
-
* ⚠️ Y no se resolvía con el traspaso del motor. El chat NO pasa por el motor,
|
|
13
|
-
* y está escrito en tres sitios de su propio fichero: «chatTrigger is an
|
|
14
|
-
* ingress, not dispatched in-pipeline».
|
|
15
|
-
*
|
|
16
|
-
* ## 🔥 Lo que NO puede cruzar: el documento mutado
|
|
17
|
-
*
|
|
18
|
-
* El código de antes hacía esto:
|
|
19
|
-
*
|
|
20
|
-
* const aiNode = await this.aiNodeModel.findOne({…});
|
|
21
|
-
* if (trigger.systemPromptOverride) an.systemPrompt = trigger.systemPromptOverride;
|
|
22
|
-
* if (an.memoryEnabled) an.memoryUserId = sessionId;
|
|
23
|
-
* await this.aiNodesService.executeForEvent(aiNode, payload, {…});
|
|
24
|
-
*
|
|
25
|
-
* O sea: la puerta cargaba el documento del nodo, le PARCHEABA dos campos en
|
|
26
|
-
* memoria y se lo pasaba al ejecutor. Un documento de Mongoose parcheado no
|
|
27
|
-
* cruza una red.
|
|
28
|
-
*
|
|
29
|
-
* Así que la puerta deja de cargar el nodo. Manda el id y los dos ajustes, y
|
|
30
|
-
* quien tiene el nodo los aplica.
|
|
31
|
-
*
|
|
32
|
-
* ⚠️ El `sessionId` viaja SIEMPRE y se aplica sólo si el nodo tiene memoria.
|
|
33
|
-
* Antes la puerta leía `memoryEnabled` del documento para decidir; esa decisión
|
|
34
|
-
* se va con el dato, que es donde puede comprobarse.
|
|
35
|
-
*/
|
|
36
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
3
|
exports.EJECUCION_DE_IA = void 0;
|
|
38
4
|
/** ⚠️ Cadena y no `Symbol`. */
|
|
@@ -16,13 +16,34 @@
|
|
|
16
16
|
* cliente real, que reenvía lo que conteste el servicio de trazas sin darle
|
|
17
17
|
* forma. Tipar aquí una forma que allí no se garantiza sería inventarla.
|
|
18
18
|
*/
|
|
19
|
+
import type { LlmProvider } from '@hostwebhook/node-types';
|
|
20
|
+
/**
|
|
21
|
+
* El proveedor que atendió la llamada, tal como se apunta en la traza.
|
|
22
|
+
*
|
|
23
|
+
* 🔥 Se DERIVA de `LlmProvider` en vez de repetirse, y ése es el arreglo. La
|
|
24
|
+
* lista escrita a mano que había aquí se quedó en los tres primeros: no tenía
|
|
25
|
+
* ni `groq` —que es proveedor del catálogo desde hace tiempo— ni `openrouter`,
|
|
26
|
+
* que se añadió en la 1.68.0 de `node-types`. El síntoma no fue un fallo de
|
|
27
|
+
* compilación sino su contrario: los llamantes escribían
|
|
28
|
+
* `provider: entity.provider as any` para poder emitir la traza, y ese `as any`
|
|
29
|
+
* apagaba de paso cualquier otra comprobación del objeto. Derivando, un
|
|
30
|
+
* proveedor nuevo en el catálogo entra aquí solo.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ Los cuatro de abajo NO están en `LlmProvider` y se quedan a propósito:
|
|
33
|
+
* esto describe lo que hay APUNTADO, no lo que se puede elegir hoy. `ollama`
|
|
34
|
+
* tiene su `llm_ollama` en el registro de credenciales aunque no sea un
|
|
35
|
+
* `LlmProvider`; `mistral`, `cohere` y `custom` son lo que devuelve
|
|
36
|
+
* `detectProvider` en `precios/tabla-de-precios.ts`. Quitarlos no arreglaría
|
|
37
|
+
* nada: dejaría de compilar la lectura de trazas que ya existen.
|
|
38
|
+
*/
|
|
39
|
+
export type ProveedorDeTraza = LlmProvider | 'ollama' | 'mistral' | 'cohere' | 'custom';
|
|
19
40
|
/** Lo que se apunta de una llamada al modelo. */
|
|
20
41
|
export interface TrazaDeLlm {
|
|
21
42
|
traceId?: string;
|
|
22
43
|
spanId?: string;
|
|
23
44
|
startTime: string | Date;
|
|
24
45
|
endTime?: string | Date;
|
|
25
|
-
provider:
|
|
46
|
+
provider: ProveedorDeTraza;
|
|
26
47
|
model: string;
|
|
27
48
|
operation?: 'chat' | 'completion' | 'embedding' | 'image' | 'audio';
|
|
28
49
|
inputTokens?: number;
|
|
@@ -62,6 +83,14 @@ export interface FiltrosDeTrazas {
|
|
|
62
83
|
aiNodeId?: string;
|
|
63
84
|
status?: 'success' | 'error' | 'timeout';
|
|
64
85
|
model?: string;
|
|
86
|
+
/**
|
|
87
|
+
* ⚠️ `string` y NO `ProveedorDeTraza`, a diferencia del campo de la traza.
|
|
88
|
+
* No es un descuido: esto es un filtro y llega de la query de una pantalla,
|
|
89
|
+
* o sea de `req.query.provider`, que es una cadena cualquiera. Estrecharlo
|
|
90
|
+
* obligaría a castear en el borde exactamente igual que hacía el `as any`
|
|
91
|
+
* que este cambio viene a quitar, y sin ganar nada: un proveedor que no
|
|
92
|
+
* exista aquí no rompe nada, simplemente no casa con ninguna traza.
|
|
93
|
+
*/
|
|
65
94
|
provider?: string;
|
|
66
95
|
startAfter?: string;
|
|
67
96
|
startBefore?: string;
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Lo que el AI Node apunta y consulta sobre sus llamadas al modelo.
|
|
4
|
-
*
|
|
5
|
-
* Costura de la Fase 2, del lote de las pequeñas. `LlmTracesClientService` es
|
|
6
|
-
* un CLIENTE HTTP contra `hw-llm-traces`, que ya es un servicio aparte — el
|
|
7
|
-
* coste de los logs no vive en esta api. Aun así el nodo no debe conocer al
|
|
8
|
-
* cliente: arrastra su configuración, su reintento y su autenticación.
|
|
9
|
-
*
|
|
10
|
-
* ## Cómo viaja
|
|
11
|
-
*
|
|
12
|
-
* ⚠️ `emitTrace` devuelve `Promise<void>` y su llamante NO la espera: apuntar
|
|
13
|
-
* una traza no puede retrasar una respuesta del modelo ni tumbarla. Eso hay
|
|
14
|
-
* que conservarlo.
|
|
15
|
-
*
|
|
16
|
-
* Las dos consultas devuelven `unknown` a propósito — es lo que devuelve el
|
|
17
|
-
* cliente real, que reenvía lo que conteste el servicio de trazas sin darle
|
|
18
|
-
* forma. Tipar aquí una forma que allí no se garantiza sería inventarla.
|
|
19
|
-
*/
|
|
20
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
3
|
exports.TRAZAS_DE_LLM = void 0;
|
|
22
4
|
/** ⚠️ 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.11.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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "npm --prefix ../node-types run build && tsc",
|
|
12
|
-
"test": "vitest run",
|
|
12
|
+
"test": "npm --prefix ../node-types run build && tsc -p tsconfig.tests.json && vitest run",
|
|
13
13
|
"prepublishOnly": "npm run build"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@nestjs/common": ">=11.0.0 <12",
|
|
29
|
-
"@hostwebhook/node-types": ">=1.
|
|
29
|
+
"@hostwebhook/node-types": ">=1.69.0 <2"
|
|
30
30
|
}
|
|
31
31
|
}
|