n8n-nodes-openrouter-selector 0.3.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.
Files changed (70) hide show
  1. package/README.md +307 -0
  2. package/dist/credentials/OpenRouterApi.credentials.d.ts +10 -0
  3. package/dist/credentials/OpenRouterApi.credentials.d.ts.map +1 -0
  4. package/dist/credentials/OpenRouterApi.credentials.js +38 -0
  5. package/dist/credentials/OpenRouterApi.credentials.js.map +1 -0
  6. package/dist/credentials/SupabaseModelCatalogApi.credentials.d.ts +10 -0
  7. package/dist/credentials/SupabaseModelCatalogApi.credentials.d.ts.map +1 -0
  8. package/dist/credentials/SupabaseModelCatalogApi.credentials.js +51 -0
  9. package/dist/credentials/SupabaseModelCatalogApi.credentials.js.map +1 -0
  10. package/dist/nodes/OpenRouterChatCompletion/OpenRouterChatCompletion.node.d.ts +11 -0
  11. package/dist/nodes/OpenRouterChatCompletion/OpenRouterChatCompletion.node.d.ts.map +1 -0
  12. package/dist/nodes/OpenRouterChatCompletion/OpenRouterChatCompletion.node.js +300 -0
  13. package/dist/nodes/OpenRouterChatCompletion/OpenRouterChatCompletion.node.js.map +1 -0
  14. package/dist/nodes/OpenRouterChatCompletion/openrouter-chat.svg +13 -0
  15. package/dist/nodes/OpenRouterModelSelector/OpenRouterModelSelector.node.d.ts +14 -0
  16. package/dist/nodes/OpenRouterModelSelector/OpenRouterModelSelector.node.d.ts.map +1 -0
  17. package/dist/nodes/OpenRouterModelSelector/OpenRouterModelSelector.node.js +412 -0
  18. package/dist/nodes/OpenRouterModelSelector/OpenRouterModelSelector.node.js.map +1 -0
  19. package/dist/nodes/OpenRouterModelSelector/modelScoring.d.ts +28 -0
  20. package/dist/nodes/OpenRouterModelSelector/modelScoring.d.ts.map +1 -0
  21. package/dist/nodes/OpenRouterModelSelector/modelScoring.js +384 -0
  22. package/dist/nodes/OpenRouterModelSelector/modelScoring.js.map +1 -0
  23. package/dist/nodes/OpenRouterModelSelector/openrouter-selector.svg +37 -0
  24. package/dist/nodes/OpenRouterModelSelector/taskProfiles.d.ts +39 -0
  25. package/dist/nodes/OpenRouterModelSelector/taskProfiles.d.ts.map +1 -0
  26. package/dist/nodes/OpenRouterModelSelector/taskProfiles.js +271 -0
  27. package/dist/nodes/OpenRouterModelSelector/taskProfiles.js.map +1 -0
  28. package/dist/nodes/OpenRouterModelSelector/types.d.ts +217 -0
  29. package/dist/nodes/OpenRouterModelSelector/types.d.ts.map +1 -0
  30. package/dist/nodes/OpenRouterModelSelector/types.js +44 -0
  31. package/dist/nodes/OpenRouterModelSelector/types.js.map +1 -0
  32. package/dist/nodes/OpenRouterSmartChat/OpenRouterSmartChat.node.d.ts +11 -0
  33. package/dist/nodes/OpenRouterSmartChat/OpenRouterSmartChat.node.d.ts.map +1 -0
  34. package/dist/nodes/OpenRouterSmartChat/OpenRouterSmartChat.node.js +657 -0
  35. package/dist/nodes/OpenRouterSmartChat/OpenRouterSmartChat.node.js.map +1 -0
  36. package/dist/nodes/OpenRouterSmartChat/modelScoring.d.ts +28 -0
  37. package/dist/nodes/OpenRouterSmartChat/modelScoring.d.ts.map +1 -0
  38. package/dist/nodes/OpenRouterSmartChat/modelScoring.js +384 -0
  39. package/dist/nodes/OpenRouterSmartChat/modelScoring.js.map +1 -0
  40. package/dist/nodes/OpenRouterSmartChat/openrouter-smart.svg +11 -0
  41. package/dist/nodes/OpenRouterSmartChat/taskProfiles.d.ts +39 -0
  42. package/dist/nodes/OpenRouterSmartChat/taskProfiles.d.ts.map +1 -0
  43. package/dist/nodes/OpenRouterSmartChat/taskProfiles.js +271 -0
  44. package/dist/nodes/OpenRouterSmartChat/taskProfiles.js.map +1 -0
  45. package/dist/nodes/OpenRouterSmartChat/types.d.ts +217 -0
  46. package/dist/nodes/OpenRouterSmartChat/types.d.ts.map +1 -0
  47. package/dist/nodes/OpenRouterSmartChat/types.js +44 -0
  48. package/dist/nodes/OpenRouterSmartChat/types.js.map +1 -0
  49. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterEmbeddings.class.d.ts +48 -0
  50. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterEmbeddings.class.d.ts.map +1 -0
  51. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterEmbeddings.class.js +265 -0
  52. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterEmbeddings.class.js.map +1 -0
  53. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterSmartEmbedding.node.d.ts +16 -0
  54. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterSmartEmbedding.node.d.ts.map +1 -0
  55. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterSmartEmbedding.node.js +177 -0
  56. package/dist/nodes/OpenRouterSmartEmbedding/OpenRouterSmartEmbedding.node.js.map +1 -0
  57. package/dist/nodes/OpenRouterSmartEmbedding/openrouter-embed.svg +22 -0
  58. package/dist/nodes/shared/contentAnalyzer.d.ts +16 -0
  59. package/dist/nodes/shared/contentAnalyzer.d.ts.map +1 -0
  60. package/dist/nodes/shared/contentAnalyzer.js +256 -0
  61. package/dist/nodes/shared/contentAnalyzer.js.map +1 -0
  62. package/dist/nodes/shared/embeddingScoring.d.ts +12 -0
  63. package/dist/nodes/shared/embeddingScoring.d.ts.map +1 -0
  64. package/dist/nodes/shared/embeddingScoring.js +329 -0
  65. package/dist/nodes/shared/embeddingScoring.js.map +1 -0
  66. package/dist/nodes/shared/types.d.ts +131 -0
  67. package/dist/nodes/shared/types.d.ts.map +1 -0
  68. package/dist/nodes/shared/types.js +24 -0
  69. package/dist/nodes/shared/types.js.map +1 -0
  70. package/package.json +67 -0
@@ -0,0 +1,657 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenRouterSmartChat = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const types_1 = require("./types");
6
+ const taskProfiles_1 = require("./taskProfiles");
7
+ const modelScoring_1 = require("./modelScoring");
8
+ class OpenRouterSmartChat {
9
+ description = {
10
+ displayName: 'OpenRouter Smart Chat',
11
+ name: 'openRouterSmartChat',
12
+ icon: 'file:openrouter-smart.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ subtitle: '={{$parameter["operation"]}}',
16
+ description: 'Intelligent OpenRouter integration: auto-select models or direct chat completion',
17
+ defaults: {
18
+ name: 'OpenRouter Smart Chat',
19
+ },
20
+ inputs: ['main'],
21
+ outputs: ['main'],
22
+ credentials: [
23
+ {
24
+ name: 'openRouterApi',
25
+ required: true,
26
+ },
27
+ {
28
+ name: 'supabaseModelCatalogApi',
29
+ required: false,
30
+ displayOptions: {
31
+ show: {
32
+ operation: ['smartChat', 'selectOnly'],
33
+ },
34
+ },
35
+ },
36
+ ],
37
+ properties: [
38
+ // ===========================================================================
39
+ // Operation Selection
40
+ // ===========================================================================
41
+ {
42
+ displayName: 'Operation',
43
+ name: 'operation',
44
+ type: 'options',
45
+ noDataExpression: true,
46
+ default: 'chat',
47
+ options: [
48
+ {
49
+ name: 'Chat Completion',
50
+ value: 'chat',
51
+ description: 'Send messages to a specified model',
52
+ },
53
+ {
54
+ name: 'Smart Chat (Auto-Select Model)',
55
+ value: 'smartChat',
56
+ description: 'Automatically select the best model based on task and budget, then chat',
57
+ },
58
+ {
59
+ name: 'Select Model Only',
60
+ value: 'selectOnly',
61
+ description: 'Only select the best model, no API call',
62
+ },
63
+ ],
64
+ },
65
+ // ===========================================================================
66
+ // Model (for 'chat' operation)
67
+ // ===========================================================================
68
+ {
69
+ displayName: 'Model',
70
+ name: 'model',
71
+ type: 'string',
72
+ default: 'anthropic/claude-3.5-haiku',
73
+ required: true,
74
+ description: 'OpenRouter model ID (e.g., anthropic/claude-3.5-haiku, openai/gpt-4o)',
75
+ placeholder: 'anthropic/claude-3.5-haiku',
76
+ displayOptions: {
77
+ show: {
78
+ operation: ['chat'],
79
+ },
80
+ },
81
+ },
82
+ // ===========================================================================
83
+ // Task Category (for 'smartChat' and 'selectOnly')
84
+ // ===========================================================================
85
+ {
86
+ displayName: 'Task Category',
87
+ name: 'task',
88
+ type: 'options',
89
+ default: 'chat',
90
+ required: true,
91
+ description: 'What type of task will this model perform?',
92
+ options: (0, taskProfiles_1.getTaskCategories)().map((task) => ({
93
+ name: (0, taskProfiles_1.getTaskDescription)(task),
94
+ value: task,
95
+ })),
96
+ displayOptions: {
97
+ show: {
98
+ operation: ['smartChat', 'selectOnly'],
99
+ },
100
+ },
101
+ },
102
+ // ===========================================================================
103
+ // Budget (for 'smartChat' and 'selectOnly')
104
+ // ===========================================================================
105
+ {
106
+ displayName: 'Budget',
107
+ name: 'budget',
108
+ type: 'options',
109
+ default: 'balanced',
110
+ required: true,
111
+ description: 'How much are you willing to spend?',
112
+ options: [
113
+ {
114
+ name: 'Cheap - Lowest cost, quality secondary',
115
+ value: 'cheap',
116
+ },
117
+ {
118
+ name: 'Balanced - Good price-performance ratio',
119
+ value: 'balanced',
120
+ },
121
+ {
122
+ name: 'Premium - Best quality, cost no concern',
123
+ value: 'premium',
124
+ },
125
+ ],
126
+ displayOptions: {
127
+ show: {
128
+ operation: ['smartChat', 'selectOnly'],
129
+ },
130
+ },
131
+ },
132
+ // ===========================================================================
133
+ // Model Override (for 'smartChat' and 'selectOnly')
134
+ // ===========================================================================
135
+ {
136
+ displayName: 'Model Override',
137
+ name: 'modelOverride',
138
+ type: 'options',
139
+ default: 'default',
140
+ description: 'Override automatic selection with a specific model',
141
+ typeOptions: {
142
+ loadOptionsMethod: 'getTopModels',
143
+ },
144
+ displayOptions: {
145
+ show: {
146
+ operation: ['smartChat', 'selectOnly'],
147
+ },
148
+ },
149
+ },
150
+ // ===========================================================================
151
+ // Messages (for 'chat' and 'smartChat')
152
+ // ===========================================================================
153
+ {
154
+ displayName: 'Messages',
155
+ name: 'messages',
156
+ type: 'fixedCollection',
157
+ typeOptions: {
158
+ multipleValues: true,
159
+ sortable: true,
160
+ },
161
+ default: {},
162
+ placeholder: 'Add Message',
163
+ description: 'The messages to send to the model',
164
+ displayOptions: {
165
+ show: {
166
+ operation: ['chat', 'smartChat'],
167
+ },
168
+ },
169
+ options: [
170
+ {
171
+ name: 'messageValues',
172
+ displayName: 'Message',
173
+ values: [
174
+ {
175
+ displayName: 'Role',
176
+ name: 'role',
177
+ type: 'options',
178
+ default: 'user',
179
+ options: [
180
+ { name: 'System', value: 'system' },
181
+ { name: 'User', value: 'user' },
182
+ { name: 'Assistant', value: 'assistant' },
183
+ ],
184
+ description: 'The role of the message author',
185
+ },
186
+ {
187
+ displayName: 'Content',
188
+ name: 'content',
189
+ type: 'string',
190
+ typeOptions: {
191
+ rows: 4,
192
+ },
193
+ default: '',
194
+ description: 'The content of the message',
195
+ },
196
+ ],
197
+ },
198
+ ],
199
+ },
200
+ // ===========================================================================
201
+ // Filters (for 'smartChat' and 'selectOnly')
202
+ // ===========================================================================
203
+ {
204
+ displayName: 'Filters',
205
+ name: 'filters',
206
+ type: 'collection',
207
+ placeholder: 'Add Filter',
208
+ default: {},
209
+ displayOptions: {
210
+ show: {
211
+ operation: ['smartChat', 'selectOnly'],
212
+ },
213
+ },
214
+ options: [
215
+ {
216
+ displayName: 'Min Context Length',
217
+ name: 'minContextLength',
218
+ type: 'number',
219
+ default: 8000,
220
+ description: 'Minimum context window size (tokens)',
221
+ },
222
+ {
223
+ displayName: 'Require JSON Mode',
224
+ name: 'requireJsonMode',
225
+ type: 'boolean',
226
+ default: false,
227
+ description: 'Only include models that support JSON output mode',
228
+ },
229
+ {
230
+ displayName: 'Require Vision',
231
+ name: 'requireVision',
232
+ type: 'boolean',
233
+ default: false,
234
+ description: 'Only include multimodal models with image input support',
235
+ },
236
+ {
237
+ displayName: 'Max Cost per 1K Tokens',
238
+ name: 'maxCostPerK',
239
+ type: 'number',
240
+ default: 0,
241
+ description: 'Maximum cost in USD per 1K tokens (0 = no limit)',
242
+ },
243
+ {
244
+ displayName: 'Provider Whitelist',
245
+ name: 'providerWhitelist',
246
+ type: 'multiOptions',
247
+ default: [],
248
+ description: 'Only include models from these providers (empty = all)',
249
+ options: [
250
+ { name: 'Anthropic', value: 'anthropic' },
251
+ { name: 'OpenAI', value: 'openai' },
252
+ { name: 'Google', value: 'google' },
253
+ { name: 'DeepSeek', value: 'deepseek' },
254
+ { name: 'Meta Llama', value: 'meta-llama' },
255
+ { name: 'Mistral AI', value: 'mistralai' },
256
+ { name: 'Qwen', value: 'qwen' },
257
+ { name: 'Cohere', value: 'cohere' },
258
+ { name: 'Perplexity', value: 'perplexity' },
259
+ { name: 'xAI', value: 'x-ai' },
260
+ ],
261
+ },
262
+ {
263
+ displayName: 'Provider Blacklist',
264
+ name: 'providerBlacklist',
265
+ type: 'multiOptions',
266
+ default: [],
267
+ description: 'Exclude models from these providers',
268
+ options: [
269
+ { name: 'Anthropic', value: 'anthropic' },
270
+ { name: 'OpenAI', value: 'openai' },
271
+ { name: 'Google', value: 'google' },
272
+ { name: 'DeepSeek', value: 'deepseek' },
273
+ { name: 'Meta Llama', value: 'meta-llama' },
274
+ { name: 'Mistral AI', value: 'mistralai' },
275
+ { name: 'Qwen', value: 'qwen' },
276
+ { name: 'Cohere', value: 'cohere' },
277
+ { name: 'Perplexity', value: 'perplexity' },
278
+ { name: 'xAI', value: 'x-ai' },
279
+ ],
280
+ },
281
+ ],
282
+ },
283
+ // ===========================================================================
284
+ // Chat Options
285
+ // ===========================================================================
286
+ {
287
+ displayName: 'Options',
288
+ name: 'options',
289
+ type: 'collection',
290
+ placeholder: 'Add Option',
291
+ default: {},
292
+ options: [
293
+ {
294
+ displayName: 'Temperature',
295
+ name: 'temperature',
296
+ type: 'number',
297
+ default: 0.7,
298
+ typeOptions: {
299
+ minValue: 0,
300
+ maxValue: 2,
301
+ numberPrecision: 2,
302
+ },
303
+ description: 'Sampling temperature (0-2). Higher = more creative, lower = more focused.',
304
+ },
305
+ {
306
+ displayName: 'Max Tokens',
307
+ name: 'maxTokens',
308
+ type: 'number',
309
+ default: 1000,
310
+ typeOptions: {
311
+ minValue: 1,
312
+ maxValue: 128000,
313
+ },
314
+ description: 'Maximum number of tokens to generate',
315
+ },
316
+ {
317
+ displayName: 'JSON Mode',
318
+ name: 'jsonMode',
319
+ type: 'boolean',
320
+ default: false,
321
+ description: 'Whether to force JSON output format',
322
+ },
323
+ {
324
+ displayName: 'Top P',
325
+ name: 'topP',
326
+ type: 'number',
327
+ default: 1,
328
+ typeOptions: {
329
+ minValue: 0,
330
+ maxValue: 1,
331
+ numberPrecision: 2,
332
+ },
333
+ description: 'Nucleus sampling parameter (0-1)',
334
+ },
335
+ {
336
+ displayName: 'Frequency Penalty',
337
+ name: 'frequencyPenalty',
338
+ type: 'number',
339
+ default: 0,
340
+ typeOptions: {
341
+ minValue: -2,
342
+ maxValue: 2,
343
+ numberPrecision: 2,
344
+ },
345
+ description: 'Penalty for token frequency (-2 to 2)',
346
+ },
347
+ {
348
+ displayName: 'Presence Penalty',
349
+ name: 'presencePenalty',
350
+ type: 'number',
351
+ default: 0,
352
+ typeOptions: {
353
+ minValue: -2,
354
+ maxValue: 2,
355
+ numberPrecision: 2,
356
+ },
357
+ description: 'Penalty for token presence (-2 to 2)',
358
+ },
359
+ {
360
+ displayName: 'Alternatives Count',
361
+ name: 'alternativesCount',
362
+ type: 'number',
363
+ default: types_1.DEFAULT_ALTERNATIVES,
364
+ description: 'Number of alternative models to return (for selectOnly)',
365
+ typeOptions: {
366
+ minValue: 1,
367
+ maxValue: types_1.MAX_ALTERNATIVES,
368
+ },
369
+ displayOptions: {
370
+ show: {
371
+ '/operation': ['selectOnly'],
372
+ },
373
+ },
374
+ },
375
+ ],
376
+ },
377
+ ],
378
+ };
379
+ methods = {
380
+ loadOptions: {
381
+ async getTopModels() {
382
+ const task = this.getNodeParameter('task', 'chat');
383
+ const budget = this.getNodeParameter('budget', 'balanced');
384
+ try {
385
+ const credentials = await this.getCredentials('supabaseModelCatalogApi');
386
+ const models = await fetchModelsWithBenchmarks(credentials.url, credentials.apiKey);
387
+ const filters = {};
388
+ const scoredModels = (0, modelScoring_1.scoreAndRankModels)(models, task, budget, filters, 5);
389
+ const options = [
390
+ {
391
+ name: 'Default (Auto-Select Best)',
392
+ value: 'default',
393
+ },
394
+ ];
395
+ for (const model of scoredModels) {
396
+ options.push({
397
+ name: `${model.displayName} | Score: ${model.score} | $${model.pricing.promptPerMUsd.toFixed(2)} in / $${model.pricing.completionPerMUsd.toFixed(2)} out /M`,
398
+ value: model.modelId,
399
+ });
400
+ }
401
+ return options;
402
+ }
403
+ catch (error) {
404
+ return [
405
+ {
406
+ name: 'Default (Auto-Select Best)',
407
+ value: 'default',
408
+ },
409
+ ];
410
+ }
411
+ },
412
+ },
413
+ };
414
+ async execute() {
415
+ const items = this.getInputData();
416
+ const returnData = [];
417
+ // Get OpenRouter credentials (always required)
418
+ const openRouterCredentials = await this.getCredentials('openRouterApi');
419
+ const apiKey = openRouterCredentials.apiKey;
420
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
421
+ try {
422
+ const operation = this.getNodeParameter('operation', itemIndex);
423
+ const options = this.getNodeParameter('options', itemIndex, {});
424
+ let result;
425
+ if (operation === 'chat') {
426
+ // Simple chat completion with manual model
427
+ const model = this.getNodeParameter('model', itemIndex);
428
+ result = await executeChatCompletion(this, apiKey, model, itemIndex, options, items[itemIndex].json);
429
+ }
430
+ else if (operation === 'smartChat') {
431
+ // Smart chat: select model + chat completion
432
+ const { selectedModel, model } = await selectModel(this, itemIndex);
433
+ const chatResult = await executeChatCompletion(this, apiKey, model, itemIndex, options, items[itemIndex].json);
434
+ result = {
435
+ ...chatResult,
436
+ selectedModel: {
437
+ modelId: selectedModel.modelId,
438
+ displayName: selectedModel.displayName,
439
+ score: selectedModel.score,
440
+ reasoning: selectedModel.reasoning,
441
+ pricing: selectedModel.pricing,
442
+ },
443
+ };
444
+ }
445
+ else if (operation === 'selectOnly') {
446
+ // Only select model, no API call
447
+ const { selectedModel, alternatives } = await selectModelWithAlternatives(this, itemIndex, options.alternativesCount);
448
+ result = {
449
+ recommended: selectedModel,
450
+ alternatives,
451
+ };
452
+ }
453
+ else {
454
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, { itemIndex });
455
+ }
456
+ returnData.push({
457
+ json: result,
458
+ pairedItem: { item: itemIndex },
459
+ });
460
+ }
461
+ catch (error) {
462
+ if (this.continueOnFail()) {
463
+ returnData.push({
464
+ json: {
465
+ error: error.message,
466
+ _input: items[itemIndex].json,
467
+ },
468
+ pairedItem: { item: itemIndex },
469
+ });
470
+ continue;
471
+ }
472
+ throw error;
473
+ }
474
+ }
475
+ return [returnData];
476
+ }
477
+ }
478
+ exports.OpenRouterSmartChat = OpenRouterSmartChat;
479
+ // =============================================================================
480
+ // Helper Functions
481
+ // =============================================================================
482
+ async function executeChatCompletion(context, apiKey, model, itemIndex, options, inputJson) {
483
+ const messagesCollection = context.getNodeParameter('messages', itemIndex);
484
+ const messages = (messagesCollection.messageValues || []).map((msg) => ({
485
+ role: msg.role,
486
+ content: msg.content,
487
+ }));
488
+ if (messages.length === 0) {
489
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'At least one message is required', { itemIndex });
490
+ }
491
+ const body = {
492
+ model,
493
+ messages,
494
+ };
495
+ if (options.temperature !== undefined) {
496
+ body.temperature = options.temperature;
497
+ }
498
+ if (options.maxTokens !== undefined) {
499
+ body.max_tokens = options.maxTokens;
500
+ }
501
+ if (options.jsonMode) {
502
+ body.response_format = { type: 'json_object' };
503
+ }
504
+ if (options.topP !== undefined && options.topP !== 1) {
505
+ body.top_p = options.topP;
506
+ }
507
+ if (options.frequencyPenalty !== undefined && options.frequencyPenalty !== 0) {
508
+ body.frequency_penalty = options.frequencyPenalty;
509
+ }
510
+ if (options.presencePenalty !== undefined && options.presencePenalty !== 0) {
511
+ body.presence_penalty = options.presencePenalty;
512
+ }
513
+ const response = await context.helpers.httpRequest({
514
+ method: 'POST',
515
+ url: 'https://openrouter.ai/api/v1/chat/completions',
516
+ headers: {
517
+ 'Authorization': `Bearer ${apiKey}`,
518
+ 'Content-Type': 'application/json',
519
+ 'HTTP-Referer': 'https://n8n.io',
520
+ 'X-Title': 'n8n OpenRouter Smart Chat',
521
+ },
522
+ body,
523
+ json: true,
524
+ });
525
+ if (response.error) {
526
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `OpenRouter API Error: ${response.error.message}`, { itemIndex });
527
+ }
528
+ const content = response.choices?.[0]?.message?.content || '';
529
+ return {
530
+ content,
531
+ model: response.model,
532
+ finishReason: response.choices?.[0]?.finish_reason,
533
+ usage: response.usage,
534
+ id: response.id,
535
+ _input: inputJson,
536
+ };
537
+ }
538
+ async function selectModel(context, itemIndex) {
539
+ const credentials = await context.getCredentials('supabaseModelCatalogApi');
540
+ const supabaseUrl = credentials.url;
541
+ const supabaseKey = credentials.apiKey;
542
+ const task = context.getNodeParameter('task', itemIndex);
543
+ const budget = context.getNodeParameter('budget', itemIndex);
544
+ const modelOverride = context.getNodeParameter('modelOverride', itemIndex, 'default');
545
+ const filtersRaw = context.getNodeParameter('filters', itemIndex, {});
546
+ const filters = {
547
+ minContextLength: filtersRaw.minContextLength ?? 8000,
548
+ requireJsonMode: filtersRaw.requireJsonMode ?? false,
549
+ requireVision: filtersRaw.requireVision ?? false,
550
+ maxCostPerK: filtersRaw.maxCostPerK || undefined,
551
+ providerWhitelist: filtersRaw.providerWhitelist && filtersRaw.providerWhitelist.length > 0
552
+ ? filtersRaw.providerWhitelist
553
+ : undefined,
554
+ providerBlacklist: filtersRaw.providerBlacklist && filtersRaw.providerBlacklist.length > 0
555
+ ? filtersRaw.providerBlacklist
556
+ : undefined,
557
+ };
558
+ const models = await fetchModelsWithBenchmarks(supabaseUrl, supabaseKey);
559
+ let selectedModel;
560
+ if (modelOverride !== 'default') {
561
+ const selected = models.find((m) => m.model.model === modelOverride);
562
+ if (!selected) {
563
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Model not found: ${modelOverride}`, { itemIndex });
564
+ }
565
+ selectedModel = (0, modelScoring_1.createManualSelection)(selected);
566
+ }
567
+ else {
568
+ const scoredModels = (0, modelScoring_1.scoreAndRankModels)(models, task, budget, filters, 1);
569
+ if (scoredModels.length === 0) {
570
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'No models match the specified filters', { itemIndex });
571
+ }
572
+ selectedModel = scoredModels[0];
573
+ }
574
+ return {
575
+ selectedModel,
576
+ model: selectedModel.modelId,
577
+ };
578
+ }
579
+ async function selectModelWithAlternatives(context, itemIndex, alternativesCount) {
580
+ const credentials = await context.getCredentials('supabaseModelCatalogApi');
581
+ const supabaseUrl = credentials.url;
582
+ const supabaseKey = credentials.apiKey;
583
+ const task = context.getNodeParameter('task', itemIndex);
584
+ const budget = context.getNodeParameter('budget', itemIndex);
585
+ const modelOverride = context.getNodeParameter('modelOverride', itemIndex, 'default');
586
+ const filtersRaw = context.getNodeParameter('filters', itemIndex, {});
587
+ const filters = {
588
+ minContextLength: filtersRaw.minContextLength ?? 8000,
589
+ requireJsonMode: filtersRaw.requireJsonMode ?? false,
590
+ requireVision: filtersRaw.requireVision ?? false,
591
+ maxCostPerK: filtersRaw.maxCostPerK || undefined,
592
+ providerWhitelist: filtersRaw.providerWhitelist && filtersRaw.providerWhitelist.length > 0
593
+ ? filtersRaw.providerWhitelist
594
+ : undefined,
595
+ providerBlacklist: filtersRaw.providerBlacklist && filtersRaw.providerBlacklist.length > 0
596
+ ? filtersRaw.providerBlacklist
597
+ : undefined,
598
+ };
599
+ const models = await fetchModelsWithBenchmarks(supabaseUrl, supabaseKey);
600
+ const topN = 1 + (alternativesCount ?? types_1.DEFAULT_ALTERNATIVES);
601
+ if (modelOverride !== 'default') {
602
+ const selected = models.find((m) => m.model.model === modelOverride);
603
+ if (!selected) {
604
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Model not found: ${modelOverride}`, { itemIndex });
605
+ }
606
+ return {
607
+ selectedModel: (0, modelScoring_1.createManualSelection)(selected),
608
+ alternatives: [],
609
+ };
610
+ }
611
+ const scoredModels = (0, modelScoring_1.scoreAndRankModels)(models, task, budget, filters, topN);
612
+ if (scoredModels.length === 0) {
613
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'No models match the specified filters', { itemIndex });
614
+ }
615
+ const [selectedModel, ...alternatives] = scoredModels;
616
+ return { selectedModel, alternatives };
617
+ }
618
+ async function fetchModelsWithBenchmarks(supabaseUrl, supabaseKey) {
619
+ const modelsResponse = await fetch(`${supabaseUrl}/rest/v1/models_catalog?select=*`, {
620
+ headers: {
621
+ apikey: supabaseKey,
622
+ Authorization: `Bearer ${supabaseKey}`,
623
+ },
624
+ });
625
+ if (!modelsResponse.ok) {
626
+ throw new Error(`Failed to fetch models: ${modelsResponse.statusText}`);
627
+ }
628
+ const models = (await modelsResponse.json());
629
+ const benchmarksResponse = await fetch(`${supabaseUrl}/rest/v1/model_benchmarks?select=*`, {
630
+ headers: {
631
+ apikey: supabaseKey,
632
+ Authorization: `Bearer ${supabaseKey}`,
633
+ },
634
+ });
635
+ let benchmarks = [];
636
+ if (benchmarksResponse.ok) {
637
+ benchmarks = (await benchmarksResponse.json());
638
+ }
639
+ const mappingsResponse = await fetch(`${supabaseUrl}/rest/v1/model_name_mappings?select=*`, {
640
+ headers: {
641
+ apikey: supabaseKey,
642
+ Authorization: `Bearer ${supabaseKey}`,
643
+ },
644
+ });
645
+ let mappings = [];
646
+ if (mappingsResponse.ok) {
647
+ mappings = (await mappingsResponse.json());
648
+ }
649
+ const benchmarkMap = new Map(benchmarks.map((b) => [b.openrouter_id, b]));
650
+ const mappingMap = new Map(mappings.map((m) => [m.openrouter_id, m]));
651
+ return models.map((model) => ({
652
+ model,
653
+ benchmarks: benchmarkMap.get(model.model) ?? null,
654
+ mapping: mappingMap.get(model.model) ?? null,
655
+ }));
656
+ }
657
+ //# sourceMappingURL=OpenRouterSmartChat.node.js.map