/** * El catálogo VIVO de OpenRouter: la forma de lo que devuelve su API y cómo se * convierte en las mismas opciones que ya pintan los desplegables. * * ## Por qué este proveedor no tiene lista en `llm-models.ts` * * Porque no es un laboratorio, es un router: revende ~425 modelos de otros * detrás de una sola clave, y el conjunto se mueve solo. La lista entera está * en `GET https://openrouter.ai/api/v1/models`, que es **abierto y sin clave** * —se puede pedir antes de que el usuario haya pegado ninguna— y cada entrada * trae su `context_length` y **su propio precio**. Copiar eso a mano a * `LLM_MODELS` y a `MODEL_PRICING` sería mantener a mano lo que la API ya da, * con 425 oportunidades de que se quede viejo en silencio. * * ## El reparto, que es lo que hay que entender antes de tocar esto * * De la MISMA respuesta salen dos cosas, y viven en paquetes distintos porque * son de dueños distintos: * * - **el catálogo** (id, nombre, ventana de contexto) → aquí, porque el * catálogo de modelos es de este fichero desde siempre; * - **la tarifa** (`pricing`) → `tarifaDeOpenRouter` en * `@hostwebhook/platform-contracts`, porque el precio de un token es de * ese paquete y no de éste. * * Y no al revés: este paquete NO puede importar `ModelPricing` * —`platform-contracts` depende de éste, no al contrario—, así que el tipo que * cruza es el crudo `ModeloDeOpenRouter`. Quien pinte el selector hace UN solo * fetch y pasa cada entrada por las dos funciones. * * ## ⚠️ Esto es una respuesta de red, no un fichero nuestro * * Todo lo que no sea `id` va opcional a propósito. No porque OpenRouter lo * omita hoy, sino porque nada nuestro impide que lo omita mañana, y un campo * declarado obligatorio que llega `undefined` no falla al parsear —TypeScript * no valida en runtime—: falla más tarde y más lejos, leyendo una propiedad de * algo que no está. `id` es la única excepción porque sin él la entrada no * identifica a ningún modelo y no hay nada que ofrecer. * * Verificado contra la respuesta real el 2026-08-31: 425 modelos, los 425 con * `pricing.prompt` y `pricing.completion`, y algunos con un * `top_provider.context_length` MENOR que su `context_length` anunciado. */ import type { LlmModelOption } from './llm-models.js'; /** El endpoint del catálogo. Abierto: no lleva `Authorization`. */ export declare const URL_DE_MODELOS_DE_OPENROUTER = "https://openrouter.ai/api/v1/models"; /** * El bloque `pricing` de un modelo. * * ⚠️ Dos trampas, las dos verificadas contra la respuesta real: * * 1. Los valores son **cadenas**, no números: `"0.0000001"`. * 2. Son **por token**, no por millón. `"0.0000001"` = $0,10 por millón. * * O sea que ninguno de estos números se puede meter en un `ModelPricing` sin * pasar por `tarifaDeOpenRouter`, que hace las dos conversiones. * * Se tipa como índice abierto porque OpenRouter publica más claves de las que * usamos (`request`, `image`, `web_search`, `internal_reasoning`, y las de * caché) y añade más sin avisar. */ export interface PrecioDeOpenRouter { /** USD por token de ENTRADA, como cadena. */ prompt?: string | null; /** USD por token de SALIDA, como cadena. */ completion?: string | null; /** USD por token leído de caché. Hoy no se usa: ver `tarifaDeOpenRouter`. */ input_cache_read?: string | null; /** USD por token escrito en caché. Hoy no se usa. */ input_cache_write?: string | null; /** Caché de una hora, cuando el modelo la ofrece. Hoy no se usa. */ input_cache_write_1h?: string | null; [otras: string]: unknown; } /** Una entrada del catálogo de OpenRouter. */ export interface ModeloDeOpenRouter { /** * Lo que se le manda a la API como `model`, con el laboratorio delante: * `"anthropic/claude-sonnet-5"`, `"openai/gpt-5.6-luna"`. * * ⚠️ Ese prefijo es parte del id y NO se quita. Un `"anthropic/..."` * recortado a `"claude-sonnet-5"` cobraría la tarifa de la tabla estática * —la de comprarle a Anthropic directamente— y OpenRouter no cobra eso. */ id: string; /** El id sin el sufijo de variante (`:free`, `:nitro`, `:floor`). */ canonical_slug?: string; /** Nombre para humanos: `"Anthropic: Claude Sonnet 5"`. */ name?: string; /** Ventana efectiva en TOKENS, tal y como la publica el proveedor. */ context_length?: number | null; pricing?: PrecioDeOpenRouter | null; architecture?: { input_modalities?: string[]; output_modalities?: string[]; tokenizer?: string; [otras: string]: unknown; } | null; top_provider?: { context_length?: number | null; max_completion_tokens?: number | null; is_moderated?: boolean; [otras: string]: unknown; } | null; /** Qué parámetros acepta: `"tools"`, `"reasoning"`, `"structured_outputs"`… */ supported_parameters?: string[] | null; [otras: string]: unknown; } /** El sobre: `{ data: [...] }`. */ export interface RespuestaDeModelosDeOpenRouter { data?: ModeloDeOpenRouter[] | null; } /** * Las opciones del desplegable a partir de la respuesta cruda. * * Devuelve `LlmModelOption`, el MISMO tipo que `LLM_MODELS`, para que el * selector no tenga dos formas de modelo según el proveedor: la única * diferencia entre un catálogo y el otro es de dónde salió la lista. * * ⚠️ Ninguna opción sale `recommended`. En los catálogos estáticos esa marca * la pone una persona que ha mirado los precios; aquí no hay nadie mirando, y * una recomendación automática —«el primero», «el más barato»— sería una * decisión de producto tomada por accidente. Sin `recommended`, * `getDefaultModel('openrouter')` sigue siendo `''` y elegir es del usuario. * * Acepta el sobre entero o el array suelto porque quien llama a veces ya lo ha * desenvuelto, y tolera basura (`null`, entradas sin `id`) devolviendo lo que * sí sirve: un catálogo a medias es utilizable, una excepción a media pantalla * no. */ export declare function opcionesDeModelosDeOpenRouter(respuesta: RespuestaDeModelosDeOpenRouter | ModeloDeOpenRouter[] | null | undefined): LlmModelOption[]; /** * La ventana de contexto de un modelo de OpenRouter, en tokens, o `undefined`. * * Aparte y con nombre propio porque hay DOS sitios donde viene y no siempre * coinciden: `context_length` es lo que anuncia el modelo, y * `top_provider.context_length` lo que sirve de verdad el proveedor que * OpenRouter va a usar. Gana el más PEQUEÑO de los dos: quien trocea para * resumir prefiere quedarse corto —más llamadas— a que el proveedor le * rechace la petición por pasarse. */ export declare function ventanaDeContextoDeOpenRouter(modelo: ModeloDeOpenRouter | null | undefined): number | undefined; /** * Lo más que un modelo de OpenRouter puede escribir en una respuesta, en * tokens, o `undefined` —y entonces no se topa—. * * Es la otra mitad de `salidaMaximaDelModelo` (`llm-models.ts`): la tabla * estática no lleva ids de OpenRouter por la misma regla que su catálogo, y el * número viaja con cada entrada, en `top_provider.max_completion_tokens`, que * es lo que sirve el proveedor que OpenRouter va a usar. * * ⚠️ No vale copiarlo de la tabla del laboratorio: el 2026-10-06 la respuesta * real traía `openai/gpt-oss-20b` con 32.768 (Groq lo sirve con 65.536) y 8 de * 465 modelos con el campo a `null` —los routers, como `openrouter/auto`—. * * Sólo un entero positivo es un tope. `null`, ausente, cero, negativo o con * decimales es «no lo sabemos»: mandar un tope que no es un número de tokens * es peor que no mandar ninguno. */ export declare function salidaMaximaDeOpenRouter(modelo: ModeloDeOpenRouter | null | undefined): number | undefined;