@hostwebhook/node-types 1.67.0 → 1.69.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.
@@ -36,7 +36,89 @@ export interface CredentialTypeRegistration {
36
36
  * Registry — order matters for fallthrough behavior in pickers (most
37
37
  * common types first), but not for correctness.
38
38
  */
39
- export declare const CREDENTIAL_TYPES: readonly CredentialTypeRegistration[];
39
+ export declare const CREDENTIAL_TYPES: readonly [{
40
+ readonly type: "google_service_account";
41
+ }, {
42
+ readonly type: "oauth2_google";
43
+ }, {
44
+ readonly type: "oauth2_atlassian";
45
+ }, {
46
+ readonly type: "mongodb";
47
+ readonly ssrfValidated: true;
48
+ readonly tunnelable: true;
49
+ }, {
50
+ readonly type: "postgres";
51
+ readonly ssrfValidated: true;
52
+ readonly tunnelable: true;
53
+ }, {
54
+ readonly type: "slack_webhook";
55
+ }, {
56
+ readonly type: "slack_oauth";
57
+ }, {
58
+ readonly type: "discord_webhook";
59
+ }, {
60
+ readonly type: "llm_anthropic";
61
+ }, {
62
+ readonly type: "llm_openai";
63
+ }, {
64
+ readonly type: "llm_google";
65
+ }, {
66
+ readonly type: "llm_groq";
67
+ }, {
68
+ readonly type: "llm_openrouter";
69
+ }, {
70
+ readonly type: "llm_ollama";
71
+ }, {
72
+ readonly type: "firecrawl";
73
+ }, {
74
+ readonly type: "http_auth";
75
+ }, {
76
+ readonly type: "aws_s3";
77
+ }, {
78
+ readonly type: "memory_contextwindow";
79
+ }, {
80
+ readonly type: "memory_mongodb";
81
+ readonly ssrfValidated: true;
82
+ readonly tunnelable: true;
83
+ }, {
84
+ readonly type: "mcp_server";
85
+ }, {
86
+ readonly type: "voice_agent";
87
+ }, {
88
+ readonly type: "telegram_bot";
89
+ }, {
90
+ readonly type: "whatsapp_business";
91
+ }, {
92
+ readonly type: "discord_bot";
93
+ }, {
94
+ readonly type: "discord_oauth";
95
+ }, {
96
+ readonly type: "bluesky_app_password";
97
+ }, {
98
+ readonly type: "twitter_oauth2";
99
+ }, {
100
+ readonly type: "mastodon_oauth2";
101
+ }, {
102
+ readonly type: "linkedin_oauth2";
103
+ }, {
104
+ readonly type: "threads_oauth2";
105
+ }, {
106
+ readonly type: "instagram_oauth2";
107
+ }, {
108
+ readonly type: "facebook_oauth2";
109
+ }, {
110
+ readonly type: "notion_internal";
111
+ }, {
112
+ readonly type: "notion_oauth";
113
+ }, {
114
+ readonly type: "mailchimp_oauth2";
115
+ }, {
116
+ readonly type: "shopify_oauth2";
117
+ }, {
118
+ readonly type: "github_app";
119
+ }, {
120
+ readonly type: "github_pat";
121
+ }];
40
122
  /**
41
123
  * Plain string array — what the Mongoose `enum` field and the
42
124
  * class-validator `@IsIn(...)` decorator both consume. Keep it as a
@@ -51,7 +133,12 @@ export declare const credentialTypeValues: () => string[];
51
133
  * Discriminated union of every supported type. Use as the field type
52
134
  * on entities + DTOs so a typo at the call site fails at compile
53
135
  * time, not at runtime via a Mongoose validation error.
136
+ *
137
+ * ⚠️ Y ahora ES una unión. Hasta la 1.69.0 esto valía `string` —la anotación
138
+ * de `CREDENTIAL_TYPES` ensanchaba los literales— así que la frase de arriba
139
+ * describía una protección que no existía. Ver el comentario del `satisfies`
140
+ * al final del registro.
54
141
  */
55
- export type CredentialType = typeof CREDENTIAL_TYPES[number]['type'];
142
+ export type CredentialType = (typeof CREDENTIAL_TYPES)[number]['type'];
56
143
  export declare function getCredentialType(type: string): CredentialTypeRegistration | undefined;
57
144
  export declare function isCredentialType(type: string): type is CredentialType;
@@ -19,6 +19,40 @@ exports.CREDENTIAL_TYPES = [
19
19
  { type: 'llm_anthropic' },
20
20
  { type: 'llm_openai' },
21
21
  { type: 'llm_google' },
22
+ // La clave de API de Groq (`gsk_…`), que el usuario saca de
23
+ // https://console.groq.com/keys y pega. Un secreto suyo, sin OAuth de por
24
+ // medio, igual que los tres de arriba y que `llm_openrouter` de abajo.
25
+ //
26
+ // El sufijo es `groq` por la misma regla que explica el bloque de
27
+ // OpenRouter: de aquí salen `getModelsFor`/`getDefaultModel` quitando el
28
+ // prefijo `llm_`, así que lo que quede detrás tiene que ser LITERALMENTE el
29
+ // `LlmProvider`, y ése es `'groq'`.
30
+ //
31
+ // 🔥 Llega TARDE, y el agujero que tapa no era teórico: `groq` es un
32
+ // `LlmProvider` de pleno derecho desde hace tiempo —está en `LLM_PROVIDERS`,
33
+ // con sus cinco modelos en `LLM_MODELS.groq`— pero no tenía tipo de
34
+ // credencial. O sea que el proveedor se podía ELEGIR en el selector y no
35
+ // había dónde guardar su clave: una rama del catálogo inalcanzable, sin un
36
+ // solo error que lo dijera. El resto de la lista se anota antes de que la
37
+ // api exista precisamente para no llegar a esto.
38
+ { type: 'llm_groq' },
39
+ // La clave de API de OpenRouter (`sk-or-v1-…`). Un secreto pegado por el
40
+ // usuario, igual que los tres de arriba: no hay OAuth de por medio.
41
+ //
42
+ // El sufijo es `openrouter` y no `open_router` porque de aquí sale
43
+ // `getModelsFor`/`getDefaultModel` quitando el prefijo `llm_` — el resto
44
+ // tiene que ser LITERALMENTE el `LlmProvider`, y ése es `'openrouter'`.
45
+ //
46
+ // ⚠️ Se anota aquí ANTES de que exista nada en la api, por lo que cuenta el
47
+ // bloque de Mailchimp más abajo: de esta lista salen el `enum` de Mongoose y
48
+ // el validador del DTO, y un tipo que falte ahí no rompe al compilar — mata
49
+ // el `create` de la credencial en runtime, con el usuario delante.
50
+ //
51
+ // ⚠️ El vecino que faltaba y que este bloque dejó escrito —`groq` sin su
52
+ // `llm_groq`— ya está arriba. Lo que queda de aquel aviso es la regla que lo
53
+ // hizo posible, y por eso la cubre ahora un test: cada `LlmProvider` de
54
+ // `LLM_PROVIDERS` tiene que tener su `llm_*` en esta lista.
55
+ { type: 'llm_openrouter' },
22
56
  { type: 'llm_ollama' },
23
57
  { type: 'firecrawl' },
24
58
  { type: 'http_auth' },
@@ -141,6 +175,16 @@ exports.CREDENTIAL_TYPES = [
141
175
  // que cambia es de dónde sale la lista de repositorios del formulario: los
142
176
  // del usuario, en vez de los que abarca una instalación.
143
177
  { type: 'github_pat' },
178
+ /* ⚠️ `as const satisfies …` y no `: readonly CredentialTypeRegistration[]`.
179
+ Los dos comprueban lo mismo —que cada entrada tenga la forma del registro—,
180
+ pero la ANOTACIÓN además ENSANCHA lo que se guarda: con ella cada `type` se
181
+ recuerda como `string`, y entonces `CredentialType` de más abajo es
182
+ literalmente `string`. O sea que la promesa de su comentario («un typo al
183
+ asignar falla al compilar») era falsa desde el primer día: `const t:
184
+ CredentialType = 'llm_openroutr'` compilaba sin rechistar.
185
+
186
+ `satisfies` comprueba y NO ensancha, así que los literales sobreviven y la
187
+ unión es de verdad. El control está en `tipos-de-credencial.test.ts`. */
144
188
  ];
145
189
  /**
146
190
  * Plain string array — what the Mongoose `enum` field and the
package/dist/index.d.ts CHANGED
@@ -60,5 +60,7 @@ export { DOCS_TOOLKIT_SPECS, DOCS_TOOLKIT_BY_TOOL_NAME, DOCS_TOOLKIT_DEFAULTABLE
60
60
  export type { DocsToolkitSpec, DocsToolkitParameter, } from './docs-toolkit';
61
61
  export type { LlmProvider, LlmProviderOption, LlmModelOption, } from './llm-models';
62
62
  export { LLM_PROVIDERS, LLM_MODELS, MODEL_CONTEXT_WINDOWS, getModelsFor, getDefaultModel, getModelLabel, } from './llm-models';
63
+ export type { ModeloDeOpenRouter, PrecioDeOpenRouter, RespuestaDeModelosDeOpenRouter, } from './openrouter';
64
+ export { URL_DE_MODELOS_DE_OPENROUTER, opcionesDeModelosDeOpenRouter, ventanaDeContextoDeOpenRouter, } from './openrouter';
63
65
  export type { CredentialTypeRegistration, CredentialType } from './credentials';
64
66
  export { CREDENTIAL_TYPES, CREDENTIAL_TYPE_VALUES, credentialTypeValues, getCredentialType, isCredentialType, } from './credentials';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DRIVE_OPERATION_SPECS = exports.DRIVE_OPERATIONS = exports.GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = exports.isGoogleCalendarOperation = exports.GOOGLE_CALENDAR_OPERATION_SPECS = exports.GOOGLE_CALENDAR_OPERATIONS = exports.isGmailOperation = exports.resolveGmailSendFields = exports.GMAIL_SEND_LEGACY_FIELDS = exports.NATIVE_EMAIL_TOOLKIT_BY_TOOL_NAME = exports.GMAIL_TOOLKIT_BY_TOOL_NAME = exports.NATIVE_EMAIL_TOOLKIT_SPECS = exports.GMAIL_SEND_AND_WAIT_TOOL_SPEC = exports.GMAIL_ALL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_OPERATIONS = exports.GMAIL_DROPDOWN_OPERATIONS = exports.GMAIL_OPERATION_GROUPS = exports.GMAIL_OPERATION_SPECS = exports.GMAIL_OPERATIONS = exports.versionCatalogErrors = exports.fieldsLost = exports.fieldsLostBetween = exports.currentVersion = exports.versionSpec = exports.versionsOf = exports.isVersioned = exports.NODE_TYPE_TO_PREFIX = exports.PREFIX_TO_NODE_TYPE = exports.NODE_STATE_KEYS = exports.NODE_COLORS = exports.NODE_DETAIL_PATHS = exports.getNodeRegistryEntry = exports.NODE_REGISTRY = exports.getNodeDispatchConfig = exports.getAllNodeCollections = exports.NODE_DISPATCH = exports.resolveNodeId = exports.PREFIX_TO_TYPE = exports.NODE_UI = exports.ALL_NODE_TYPES = exports.isNodeType = exports.isTerminal = exports.canSendToNodes = exports.canReceiveFromNodes = exports.canReceiveFrom = exports.NODE_CONNECTIONS = exports.iterableMeta = exports.singleMeta = void 0;
4
4
  exports.SLACK_TOOLKIT_SPECS = exports.isSlackOperation = exports.camposDeSlackNoDisponibles = exports.slackPuedeEjecutar = exports.operacionesDeSlackPara = exports.SLACK_CAPACIDADES_POR_CREDENCIAL = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = exports.isJiraOperation = exports.JIRA_ITERABLE_OPERATIONS = exports.JIRA_DROPDOWN_OPERATIONS = exports.JIRA_OPERATION_SPECS = exports.JIRA_OPERATIONS = exports.isGithubOperation = exports.GITHUB_ITERABLE_OPERATIONS = exports.GITHUB_DROPDOWN_OPERATIONS = exports.GITHUB_OPERATION_SPECS = exports.GITHUB_OPERATIONS = exports.isShopifyOperation = exports.SHOPIFY_SEARCHABLE_RESOURCES = exports.SHOPIFY_TAGGABLE_RESOURCES = exports.SHOPIFY_OPERATION_SPECS = exports.SHOPIFY_OPERATIONS = exports.isMailchimpOperation = exports.MAILCHIMP_CONTACT_STATUSES = exports.MAILCHIMP_OPERATION_SPECS = exports.MAILCHIMP_OPERATIONS = exports.herramientasDeDiscordPara = exports.DISCORD_TOOLKIT_BY_TOOL_NAME = exports.DISCORD_TOOLKIT_SPECS = exports.isDiscordOperation = exports.camposDeDiscordNoDisponibles = exports.discordPuedeEjecutar = exports.operacionesDeDiscordPara = exports.DISCORD_CAPACIDADES_POR_CREDENCIAL = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = exports.camposNoDisponiblesPara = exports.puedeEjecutar = exports.operacionesPara = exports.isWhatsAppOperation = exports.WHATSAPP_OPERATIONS = exports.TELEGRAM_TOOLKIT_BY_TOOL_NAME = exports.TELEGRAM_TOOLKIT_SPECS = exports.isTelegramOperation = exports.TELEGRAM_OPERATION_SPECS = exports.TELEGRAM_OPERATIONS = exports.DRIVE_TOOLKIT_BY_TOOL_NAME = exports.DRIVE_TOOLKIT_SPECS = exports.isDriveOperation = void 0;
5
- exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = exports.GOOGLE_ANALYTICS_OPERATION_SPECS = exports.GOOGLE_ANALYTICS_OPERATIONS = exports.isGoogleContactsOperation = exports.GOOGLE_CONTACTS_DEFAULT_PERSON_FIELDS = exports.GOOGLE_CONTACTS_OPERATION_GROUPS = exports.GOOGLE_CONTACTS_OPERATION_SPECS = exports.GOOGLE_CONTACTS_OPERATIONS_V2 = exports.GOOGLE_CONTACTS_OPERATIONS_V1 = exports.GOOGLE_CONTACTS_OPERATIONS = exports.SHEETS_TOOLKIT_DEFAULTABLE = exports.SHEETS_TOOLKIT_BY_TOOL_NAME = exports.SHEETS_TOOLKIT_SPECS = exports.isSheetsOperation = exports.SHEETS_OPERATION_SPECS = exports.SHEETS_OPERATIONS = exports.herramientasDeSlackPara = exports.SLACK_TOOLKIT_BY_TOOL_NAME = void 0;
5
+ exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.ventanaDeContextoDeOpenRouter = exports.opcionesDeModelosDeOpenRouter = exports.URL_DE_MODELOS_DE_OPENROUTER = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = exports.GOOGLE_ANALYTICS_OPERATION_SPECS = exports.GOOGLE_ANALYTICS_OPERATIONS = exports.isGoogleContactsOperation = exports.GOOGLE_CONTACTS_DEFAULT_PERSON_FIELDS = exports.GOOGLE_CONTACTS_OPERATION_GROUPS = exports.GOOGLE_CONTACTS_OPERATION_SPECS = exports.GOOGLE_CONTACTS_OPERATIONS_V2 = exports.GOOGLE_CONTACTS_OPERATIONS_V1 = exports.GOOGLE_CONTACTS_OPERATIONS = exports.SHEETS_TOOLKIT_DEFAULTABLE = exports.SHEETS_TOOLKIT_BY_TOOL_NAME = exports.SHEETS_TOOLKIT_SPECS = exports.isSheetsOperation = exports.SHEETS_OPERATION_SPECS = exports.SHEETS_OPERATIONS = exports.herramientasDeSlackPara = exports.SLACK_TOOLKIT_BY_TOOL_NAME = void 0;
6
6
  var types_1 = require("./types");
7
7
  Object.defineProperty(exports, "singleMeta", { enumerable: true, get: function () { return types_1.singleMeta; } });
8
8
  Object.defineProperty(exports, "iterableMeta", { enumerable: true, get: function () { return types_1.iterableMeta; } });
@@ -193,6 +193,10 @@ Object.defineProperty(exports, "MODEL_CONTEXT_WINDOWS", { enumerable: true, get:
193
193
  Object.defineProperty(exports, "getModelsFor", { enumerable: true, get: function () { return llm_models_1.getModelsFor; } });
194
194
  Object.defineProperty(exports, "getDefaultModel", { enumerable: true, get: function () { return llm_models_1.getDefaultModel; } });
195
195
  Object.defineProperty(exports, "getModelLabel", { enumerable: true, get: function () { return llm_models_1.getModelLabel; } });
196
+ var openrouter_1 = require("./openrouter");
197
+ Object.defineProperty(exports, "URL_DE_MODELOS_DE_OPENROUTER", { enumerable: true, get: function () { return openrouter_1.URL_DE_MODELOS_DE_OPENROUTER; } });
198
+ Object.defineProperty(exports, "opcionesDeModelosDeOpenRouter", { enumerable: true, get: function () { return openrouter_1.opcionesDeModelosDeOpenRouter; } });
199
+ Object.defineProperty(exports, "ventanaDeContextoDeOpenRouter", { enumerable: true, get: function () { return openrouter_1.ventanaDeContextoDeOpenRouter; } });
196
200
  var credentials_1 = require("./credentials");
197
201
  Object.defineProperty(exports, "CREDENTIAL_TYPES", { enumerable: true, get: function () { return credentials_1.CREDENTIAL_TYPES; } });
198
202
  Object.defineProperty(exports, "CREDENTIAL_TYPE_VALUES", { enumerable: true, get: function () { return credentials_1.CREDENTIAL_TYPE_VALUES; } });
@@ -25,8 +25,18 @@
25
25
  * - OpenAI: https://developers.openai.com/api/docs/models
26
26
  * - Google: https://ai.google.dev/gemini-api/docs/models
27
27
  * - Groq: https://console.groq.com/docs/models
28
+ *
29
+ * NOT verified against docs, and on purpose: OpenRouter. Its catalog is not a
30
+ * list anybody types — see `LLM_MODELS.openrouter` and `openrouter.ts`.
31
+ */
32
+ export type LlmProvider = 'anthropic' | 'openai' | 'google' | 'groq'
33
+ /**
34
+ * A router, not a lab: OpenRouter resells ~425 models from everybody else
35
+ * behind one key. That is why it is the first provider here whose catalog
36
+ * is EMPTY below and fetched live instead — the shape of the entry is in
37
+ * `openrouter.ts`, and the price of each model travels with the model.
28
38
  */
29
- export type LlmProvider = 'anthropic' | 'openai' | 'google' | 'groq';
39
+ | 'openrouter';
30
40
  export interface LlmProviderOption {
31
41
  value: LlmProvider;
32
42
  label: string;
@@ -58,6 +68,13 @@ export declare const LLM_MODELS: Record<LlmProvider, ReadonlyArray<LlmModelOptio
58
68
  *
59
69
  * Includes ids the pickers no longer offer: the AI node's model field is
60
70
  * a free-text datalist, so a node may hold one by hand.
71
+ *
72
+ * ⚠️ NOT here: OpenRouter's ids. Same reason its models are not in
73
+ * `LLM_MODELS` — its API publishes `context_length` per model, so the number
74
+ * travels with the model instead of being copied 425 times into this table.
75
+ * Whoever chunks for an OpenRouter model reads it from the fetched entry
76
+ * (`ModeloDeOpenRouter.context_length`); a lookup here misses and falls back
77
+ * to the conservative 32k, which still summarizes, just in more calls.
61
78
  */
62
79
  export declare const MODEL_CONTEXT_WINDOWS: Record<string, number>;
63
80
  /**
@@ -71,6 +88,13 @@ export declare function getModelsFor(provider: string): ReadonlyArray<LlmModelOp
71
88
  * The suggested default model for a provider, or `''` for one we do not
72
89
  * catalog (Ollama runs whatever the user pulled, so it has no entry —
73
90
  * callers fall back to the node's own model there).
91
+ *
92
+ * ⚠️ `'openrouter'` is the second `''`, and unlike Ollama it IS a provider in
93
+ * `LLM_PROVIDERS`, so callers that assumed "every provider in the list has a
94
+ * default" now have a case to handle. It is deliberate: its catalog is live
95
+ * (see `LLM_MODELS.openrouter`) and no static pick would survive. Treat `''`
96
+ * as "the user must choose", not as "fall back to something" — a node born
97
+ * with an empty model must not be sent to the API as-is.
74
98
  */
75
99
  export declare function getDefaultModel(provider: string): string;
76
100
  /** The friendly label for a model value, or the value itself. */
@@ -26,6 +26,9 @@
26
26
  * - OpenAI: https://developers.openai.com/api/docs/models
27
27
  * - Google: https://ai.google.dev/gemini-api/docs/models
28
28
  * - Groq: https://console.groq.com/docs/models
29
+ *
30
+ * NOT verified against docs, and on purpose: OpenRouter. Its catalog is not a
31
+ * list anybody types — see `LLM_MODELS.openrouter` and `openrouter.ts`.
29
32
  */
30
33
  Object.defineProperty(exports, "__esModule", { value: true });
31
34
  exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = void 0;
@@ -37,6 +40,7 @@ exports.LLM_PROVIDERS = [
37
40
  { value: 'openai', label: 'OpenAI' },
38
41
  { value: 'google', label: 'Google' },
39
42
  { value: 'groq', label: 'Groq' },
43
+ { value: 'openrouter', label: 'OpenRouter' },
40
44
  ];
41
45
  exports.LLM_MODELS = {
42
46
  // ── Anthropic ──────────────────────────────────────────────────────────
@@ -106,6 +110,38 @@ exports.LLM_MODELS = {
106
110
  { value: 'groq/compound-mini', label: 'Compound Mini' },
107
111
  { value: 'qwen/qwen3.6-27b', label: 'Qwen 3.6 27B (preview)' },
108
112
  ],
113
+ // ── OpenRouter ─────────────────────────────────────────────────────────
114
+ // EMPTY ON PURPOSE. Not a gap, not a TODO — read this before filling it.
115
+ //
116
+ // Every other provider above is a lab with a dozen models, each with a
117
+ // price we look up in `MODEL_PRICING` over in platform-contracts. OpenRouter
118
+ // is a router: ~425 models from everybody else, and the set moves on its own
119
+ // — models appear, get deprecated and change price without anyone here
120
+ // touching a file. Typing them out has three separate costs:
121
+ //
122
+ // 1. It goes stale silently. That is the exact failure this whole file
123
+ // exists to kill (the picker offering a model the api had retired),
124
+ // only this time with 425 chances to happen instead of eight.
125
+ // 2. The price would have to be hand-copied into `MODEL_PRICING` too —
126
+ // 425 rows of a number the API already hands us with each model. A
127
+ // wrong row there does not fail loudly: it makes a dollar spending cap
128
+ // cut in the wrong place, or never.
129
+ // 3. Nobody wants a 425-entry dropdown anyway. The dashboard has to
130
+ // search/filter it live regardless of what we ship here.
131
+ //
132
+ // So the catalog is FETCHED: `GET https://openrouter.ai/api/v1/models` is
133
+ // open, needs no key, and each entry carries id, name, context_length AND
134
+ // its own pricing. `openrouter.ts` types that payload and maps it to the
135
+ // very same `LlmModelOption` the pickers already render; the pricing half
136
+ // is `tarifaDeOpenRouter` in `@hostwebhook/platform-contracts`, which turns
137
+ // it into a `ModelPricing` you hand to `calculateCost(..., tarifa)`.
138
+ //
139
+ // Consequence, stated out loud because it is the surprising part:
140
+ // `getDefaultModel('openrouter')` is `''`. There is no honest default among
141
+ // 425 models we do not curate — any pick here would be a guess that can be
142
+ // deprecated out from under us — so choosing a model is REQUIRED when the
143
+ // provider is OpenRouter. See `getDefaultModel` below.
144
+ openrouter: [],
109
145
  };
110
146
  /**
111
147
  * Effective context window per model, in TOKENS. The api sizes
@@ -116,6 +152,13 @@ exports.LLM_MODELS = {
116
152
  *
117
153
  * Includes ids the pickers no longer offer: the AI node's model field is
118
154
  * a free-text datalist, so a node may hold one by hand.
155
+ *
156
+ * ⚠️ NOT here: OpenRouter's ids. Same reason its models are not in
157
+ * `LLM_MODELS` — its API publishes `context_length` per model, so the number
158
+ * travels with the model instead of being copied 425 times into this table.
159
+ * Whoever chunks for an OpenRouter model reads it from the fetched entry
160
+ * (`ModeloDeOpenRouter.context_length`); a lookup here misses and falls back
161
+ * to the conservative 32k, which still summarizes, just in more calls.
119
162
  */
120
163
  exports.MODEL_CONTEXT_WINDOWS = {
121
164
  // Anthropic — the 4.6 generation onward is 1M; Haiku and the 4.5 line
@@ -169,6 +212,13 @@ function getModelsFor(provider) {
169
212
  * The suggested default model for a provider, or `''` for one we do not
170
213
  * catalog (Ollama runs whatever the user pulled, so it has no entry —
171
214
  * callers fall back to the node's own model there).
215
+ *
216
+ * ⚠️ `'openrouter'` is the second `''`, and unlike Ollama it IS a provider in
217
+ * `LLM_PROVIDERS`, so callers that assumed "every provider in the list has a
218
+ * default" now have a case to handle. It is deliberate: its catalog is live
219
+ * (see `LLM_MODELS.openrouter`) and no static pick would survive. Treat `''`
220
+ * as "the user must choose", not as "fall back to something" — a node born
221
+ * with an empty model must not be sent to the API as-is.
172
222
  */
173
223
  function getDefaultModel(provider) {
174
224
  const models = getModelsFor(provider);
@@ -0,0 +1,142 @@
1
+ /**
2
+ * El catálogo VIVO de OpenRouter: la forma de lo que devuelve su API y cómo se
3
+ * convierte en las mismas opciones que ya pintan los desplegables.
4
+ *
5
+ * ## Por qué este proveedor no tiene lista en `llm-models.ts`
6
+ *
7
+ * Porque no es un laboratorio, es un router: revende ~425 modelos de otros
8
+ * detrás de una sola clave, y el conjunto se mueve solo. La lista entera está
9
+ * en `GET https://openrouter.ai/api/v1/models`, que es **abierto y sin clave**
10
+ * —se puede pedir antes de que el usuario haya pegado ninguna— y cada entrada
11
+ * trae su `context_length` y **su propio precio**. Copiar eso a mano a
12
+ * `LLM_MODELS` y a `MODEL_PRICING` sería mantener a mano lo que la API ya da,
13
+ * con 425 oportunidades de que se quede viejo en silencio.
14
+ *
15
+ * ## El reparto, que es lo que hay que entender antes de tocar esto
16
+ *
17
+ * De la MISMA respuesta salen dos cosas, y viven en paquetes distintos porque
18
+ * son de dueños distintos:
19
+ *
20
+ * - **el catálogo** (id, nombre, ventana de contexto) → aquí, porque el
21
+ * catálogo de modelos es de este fichero desde siempre;
22
+ * - **la tarifa** (`pricing`) → `tarifaDeOpenRouter` en
23
+ * `@hostwebhook/platform-contracts`, porque el precio de un token es de
24
+ * ese paquete y no de éste.
25
+ *
26
+ * Y no al revés: este paquete NO puede importar `ModelPricing`
27
+ * —`platform-contracts` depende de éste, no al contrario—, así que el tipo que
28
+ * cruza es el crudo `ModeloDeOpenRouter`. Quien pinte el selector hace UN solo
29
+ * fetch y pasa cada entrada por las dos funciones.
30
+ *
31
+ * ## ⚠️ Esto es una respuesta de red, no un fichero nuestro
32
+ *
33
+ * Todo lo que no sea `id` va opcional a propósito. No porque OpenRouter lo
34
+ * omita hoy, sino porque nada nuestro impide que lo omita mañana, y un campo
35
+ * declarado obligatorio que llega `undefined` no falla al parsear —TypeScript
36
+ * no valida en runtime—: falla más tarde y más lejos, leyendo una propiedad de
37
+ * algo que no está. `id` es la única excepción porque sin él la entrada no
38
+ * identifica a ningún modelo y no hay nada que ofrecer.
39
+ *
40
+ * Verificado contra la respuesta real el 2026-08-31: 425 modelos, los 425 con
41
+ * `pricing.prompt` y `pricing.completion`, y algunos con un
42
+ * `top_provider.context_length` MENOR que su `context_length` anunciado.
43
+ */
44
+ import type { LlmModelOption } from './llm-models';
45
+ /** El endpoint del catálogo. Abierto: no lleva `Authorization`. */
46
+ export declare const URL_DE_MODELOS_DE_OPENROUTER = "https://openrouter.ai/api/v1/models";
47
+ /**
48
+ * El bloque `pricing` de un modelo.
49
+ *
50
+ * ⚠️ Dos trampas, las dos verificadas contra la respuesta real:
51
+ *
52
+ * 1. Los valores son **cadenas**, no números: `"0.0000001"`.
53
+ * 2. Son **por token**, no por millón. `"0.0000001"` = $0,10 por millón.
54
+ *
55
+ * O sea que ninguno de estos números se puede meter en un `ModelPricing` sin
56
+ * pasar por `tarifaDeOpenRouter`, que hace las dos conversiones.
57
+ *
58
+ * Se tipa como índice abierto porque OpenRouter publica más claves de las que
59
+ * usamos (`request`, `image`, `web_search`, `internal_reasoning`, y las de
60
+ * caché) y añade más sin avisar.
61
+ */
62
+ export interface PrecioDeOpenRouter {
63
+ /** USD por token de ENTRADA, como cadena. */
64
+ prompt?: string | null;
65
+ /** USD por token de SALIDA, como cadena. */
66
+ completion?: string | null;
67
+ /** USD por token leído de caché. Hoy no se usa: ver `tarifaDeOpenRouter`. */
68
+ input_cache_read?: string | null;
69
+ /** USD por token escrito en caché. Hoy no se usa. */
70
+ input_cache_write?: string | null;
71
+ /** Caché de una hora, cuando el modelo la ofrece. Hoy no se usa. */
72
+ input_cache_write_1h?: string | null;
73
+ [otras: string]: unknown;
74
+ }
75
+ /** Una entrada del catálogo de OpenRouter. */
76
+ export interface ModeloDeOpenRouter {
77
+ /**
78
+ * Lo que se le manda a la API como `model`, con el laboratorio delante:
79
+ * `"anthropic/claude-sonnet-5"`, `"openai/gpt-5.6-luna"`.
80
+ *
81
+ * ⚠️ Ese prefijo es parte del id y NO se quita. Un `"anthropic/..."`
82
+ * recortado a `"claude-sonnet-5"` cobraría la tarifa de la tabla estática
83
+ * —la de comprarle a Anthropic directamente— y OpenRouter no cobra eso.
84
+ */
85
+ id: string;
86
+ /** El id sin el sufijo de variante (`:free`, `:nitro`, `:floor`). */
87
+ canonical_slug?: string;
88
+ /** Nombre para humanos: `"Anthropic: Claude Sonnet 5"`. */
89
+ name?: string;
90
+ /** Ventana efectiva en TOKENS, tal y como la publica el proveedor. */
91
+ context_length?: number | null;
92
+ pricing?: PrecioDeOpenRouter | null;
93
+ architecture?: {
94
+ input_modalities?: string[];
95
+ output_modalities?: string[];
96
+ tokenizer?: string;
97
+ [otras: string]: unknown;
98
+ } | null;
99
+ top_provider?: {
100
+ context_length?: number | null;
101
+ max_completion_tokens?: number | null;
102
+ is_moderated?: boolean;
103
+ [otras: string]: unknown;
104
+ } | null;
105
+ /** Qué parámetros acepta: `"tools"`, `"reasoning"`, `"structured_outputs"`… */
106
+ supported_parameters?: string[] | null;
107
+ [otras: string]: unknown;
108
+ }
109
+ /** El sobre: `{ data: [...] }`. */
110
+ export interface RespuestaDeModelosDeOpenRouter {
111
+ data?: ModeloDeOpenRouter[] | null;
112
+ }
113
+ /**
114
+ * Las opciones del desplegable a partir de la respuesta cruda.
115
+ *
116
+ * Devuelve `LlmModelOption`, el MISMO tipo que `LLM_MODELS`, para que el
117
+ * selector no tenga dos formas de modelo según el proveedor: la única
118
+ * diferencia entre un catálogo y el otro es de dónde salió la lista.
119
+ *
120
+ * ⚠️ Ninguna opción sale `recommended`. En los catálogos estáticos esa marca
121
+ * la pone una persona que ha mirado los precios; aquí no hay nadie mirando, y
122
+ * una recomendación automática —«el primero», «el más barato»— sería una
123
+ * decisión de producto tomada por accidente. Sin `recommended`,
124
+ * `getDefaultModel('openrouter')` sigue siendo `''` y elegir es del usuario.
125
+ *
126
+ * Acepta el sobre entero o el array suelto porque quien llama a veces ya lo ha
127
+ * desenvuelto, y tolera basura (`null`, entradas sin `id`) devolviendo lo que
128
+ * sí sirve: un catálogo a medias es utilizable, una excepción a media pantalla
129
+ * no.
130
+ */
131
+ export declare function opcionesDeModelosDeOpenRouter(respuesta: RespuestaDeModelosDeOpenRouter | ModeloDeOpenRouter[] | null | undefined): LlmModelOption[];
132
+ /**
133
+ * La ventana de contexto de un modelo de OpenRouter, en tokens, o `undefined`.
134
+ *
135
+ * Aparte y con nombre propio porque hay DOS sitios donde viene y no siempre
136
+ * coinciden: `context_length` es lo que anuncia el modelo, y
137
+ * `top_provider.context_length` lo que sirve de verdad el proveedor que
138
+ * OpenRouter va a usar. Gana el más PEQUEÑO de los dos: quien trocea para
139
+ * resumir prefiere quedarse corto —más llamadas— a que el proveedor le
140
+ * rechace la petición por pasarse.
141
+ */
142
+ export declare function ventanaDeContextoDeOpenRouter(modelo: ModeloDeOpenRouter | null | undefined): number | undefined;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * El catálogo VIVO de OpenRouter: la forma de lo que devuelve su API y cómo se
4
+ * convierte en las mismas opciones que ya pintan los desplegables.
5
+ *
6
+ * ## Por qué este proveedor no tiene lista en `llm-models.ts`
7
+ *
8
+ * Porque no es un laboratorio, es un router: revende ~425 modelos de otros
9
+ * detrás de una sola clave, y el conjunto se mueve solo. La lista entera está
10
+ * en `GET https://openrouter.ai/api/v1/models`, que es **abierto y sin clave**
11
+ * —se puede pedir antes de que el usuario haya pegado ninguna— y cada entrada
12
+ * trae su `context_length` y **su propio precio**. Copiar eso a mano a
13
+ * `LLM_MODELS` y a `MODEL_PRICING` sería mantener a mano lo que la API ya da,
14
+ * con 425 oportunidades de que se quede viejo en silencio.
15
+ *
16
+ * ## El reparto, que es lo que hay que entender antes de tocar esto
17
+ *
18
+ * De la MISMA respuesta salen dos cosas, y viven en paquetes distintos porque
19
+ * son de dueños distintos:
20
+ *
21
+ * - **el catálogo** (id, nombre, ventana de contexto) → aquí, porque el
22
+ * catálogo de modelos es de este fichero desde siempre;
23
+ * - **la tarifa** (`pricing`) → `tarifaDeOpenRouter` en
24
+ * `@hostwebhook/platform-contracts`, porque el precio de un token es de
25
+ * ese paquete y no de éste.
26
+ *
27
+ * Y no al revés: este paquete NO puede importar `ModelPricing`
28
+ * —`platform-contracts` depende de éste, no al contrario—, así que el tipo que
29
+ * cruza es el crudo `ModeloDeOpenRouter`. Quien pinte el selector hace UN solo
30
+ * fetch y pasa cada entrada por las dos funciones.
31
+ *
32
+ * ## ⚠️ Esto es una respuesta de red, no un fichero nuestro
33
+ *
34
+ * Todo lo que no sea `id` va opcional a propósito. No porque OpenRouter lo
35
+ * omita hoy, sino porque nada nuestro impide que lo omita mañana, y un campo
36
+ * declarado obligatorio que llega `undefined` no falla al parsear —TypeScript
37
+ * no valida en runtime—: falla más tarde y más lejos, leyendo una propiedad de
38
+ * algo que no está. `id` es la única excepción porque sin él la entrada no
39
+ * identifica a ningún modelo y no hay nada que ofrecer.
40
+ *
41
+ * Verificado contra la respuesta real el 2026-08-31: 425 modelos, los 425 con
42
+ * `pricing.prompt` y `pricing.completion`, y algunos con un
43
+ * `top_provider.context_length` MENOR que su `context_length` anunciado.
44
+ */
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.URL_DE_MODELOS_DE_OPENROUTER = void 0;
47
+ exports.opcionesDeModelosDeOpenRouter = opcionesDeModelosDeOpenRouter;
48
+ exports.ventanaDeContextoDeOpenRouter = ventanaDeContextoDeOpenRouter;
49
+ /** El endpoint del catálogo. Abierto: no lleva `Authorization`. */
50
+ exports.URL_DE_MODELOS_DE_OPENROUTER = 'https://openrouter.ai/api/v1/models';
51
+ /**
52
+ * Las opciones del desplegable a partir de la respuesta cruda.
53
+ *
54
+ * Devuelve `LlmModelOption`, el MISMO tipo que `LLM_MODELS`, para que el
55
+ * selector no tenga dos formas de modelo según el proveedor: la única
56
+ * diferencia entre un catálogo y el otro es de dónde salió la lista.
57
+ *
58
+ * ⚠️ Ninguna opción sale `recommended`. En los catálogos estáticos esa marca
59
+ * la pone una persona que ha mirado los precios; aquí no hay nadie mirando, y
60
+ * una recomendación automática —«el primero», «el más barato»— sería una
61
+ * decisión de producto tomada por accidente. Sin `recommended`,
62
+ * `getDefaultModel('openrouter')` sigue siendo `''` y elegir es del usuario.
63
+ *
64
+ * Acepta el sobre entero o el array suelto porque quien llama a veces ya lo ha
65
+ * desenvuelto, y tolera basura (`null`, entradas sin `id`) devolviendo lo que
66
+ * sí sirve: un catálogo a medias es utilizable, una excepción a media pantalla
67
+ * no.
68
+ */
69
+ function opcionesDeModelosDeOpenRouter(respuesta) {
70
+ const modelos = Array.isArray(respuesta) ? respuesta : respuesta?.data;
71
+ if (!Array.isArray(modelos))
72
+ return [];
73
+ const opciones = [];
74
+ for (const modelo of modelos) {
75
+ if (!modelo || typeof modelo.id !== 'string' || modelo.id === '')
76
+ continue;
77
+ opciones.push({
78
+ value: modelo.id,
79
+ label: typeof modelo.name === 'string' && modelo.name !== ''
80
+ ? modelo.name
81
+ : modelo.id,
82
+ });
83
+ }
84
+ return opciones;
85
+ }
86
+ /**
87
+ * La ventana de contexto de un modelo de OpenRouter, en tokens, o `undefined`.
88
+ *
89
+ * Aparte y con nombre propio porque hay DOS sitios donde viene y no siempre
90
+ * coinciden: `context_length` es lo que anuncia el modelo, y
91
+ * `top_provider.context_length` lo que sirve de verdad el proveedor que
92
+ * OpenRouter va a usar. Gana el más PEQUEÑO de los dos: quien trocea para
93
+ * resumir prefiere quedarse corto —más llamadas— a que el proveedor le
94
+ * rechace la petición por pasarse.
95
+ */
96
+ function ventanaDeContextoDeOpenRouter(modelo) {
97
+ const candidatas = [
98
+ modelo?.context_length,
99
+ modelo?.top_provider?.context_length,
100
+ ].filter((n) => typeof n === 'number' && Number.isFinite(n) && n > 0);
101
+ if (candidatas.length === 0)
102
+ return undefined;
103
+ return Math.min(...candidatas);
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostwebhook/node-types",
3
- "version": "1.67.0",
3
+ "version": "1.69.0",
4
4
  "description": "Shared node type definitions, connection rules, and dispatch config for HostWebhook",
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": "tsc",
12
- "test": "vitest run",
12
+ "test": "tsc -p tsconfig.tests.json && vitest run",
13
13
  "prepublishOnly": "npm run build"
14
14
  },
15
15
  "keywords": [