n8n-nodes-vercel-ai-sdk-universal-temp 0.1.54 → 0.1.55
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.
|
@@ -41,42 +41,15 @@ const n8n_workflow_1 = require("n8n-workflow");
|
|
|
41
41
|
const ai_1 = require("ai");
|
|
42
42
|
const zod_1 = require("zod");
|
|
43
43
|
const ajv_1 = __importDefault(require("ajv"));
|
|
44
|
-
const crypto_1 = require("crypto");
|
|
45
44
|
const descriptions_1 = require("../shared/descriptions");
|
|
46
|
-
class UniversalAIError extends Error {
|
|
47
|
-
constructor(message, code, context) {
|
|
48
|
-
super(message);
|
|
49
|
-
this.code = code;
|
|
50
|
-
this.context = context;
|
|
51
|
-
this.name = 'UniversalAIError';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
class CacheError extends UniversalAIError {
|
|
55
|
-
constructor(message, context) {
|
|
56
|
-
super(message, 'CACHE_ERROR', context);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
class ProviderError extends UniversalAIError {
|
|
60
|
-
constructor(message, context) {
|
|
61
|
-
super(message, 'PROVIDER_ERROR', context);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
class ValidationError extends UniversalAIError {
|
|
65
|
-
constructor(message, context) {
|
|
66
|
-
super(message, 'VALIDATION_ERROR', context);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
45
|
class Cache {
|
|
70
|
-
constructor(
|
|
46
|
+
constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
|
|
71
47
|
this.cache = new Map();
|
|
72
48
|
this.totalHits = 0;
|
|
73
49
|
this.totalMisses = 0;
|
|
74
50
|
this.totalEvictions = 0;
|
|
75
51
|
this.maxSize = maxSize;
|
|
76
52
|
this.ttl = ttl;
|
|
77
|
-
if (ttl > 0) {
|
|
78
|
-
setInterval(() => this.cleanupExpired(), Math.min(ttl, 60000));
|
|
79
|
-
}
|
|
80
53
|
}
|
|
81
54
|
get(key) {
|
|
82
55
|
const item = this.cache.get(key);
|
|
@@ -92,7 +65,6 @@ class Cache {
|
|
|
92
65
|
return undefined;
|
|
93
66
|
}
|
|
94
67
|
item.hits++;
|
|
95
|
-
item.lastAccessed = now;
|
|
96
68
|
this.totalHits++;
|
|
97
69
|
return item.value;
|
|
98
70
|
}
|
|
@@ -100,42 +72,20 @@ class Cache {
|
|
|
100
72
|
const now = Date.now();
|
|
101
73
|
const expiresAt = customTTL ? now + customTTL : (this.ttl > 0 ? now + this.ttl : undefined);
|
|
102
74
|
if (this.cache.size >= this.maxSize) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
lastAccessed: now
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
evictLRU() {
|
|
114
|
-
let lruKey;
|
|
115
|
-
let oldestAccess = Date.now();
|
|
116
|
-
for (const [key, item] of this.cache.entries()) {
|
|
117
|
-
if (item.lastAccessed < oldestAccess) {
|
|
118
|
-
oldestAccess = item.lastAccessed;
|
|
119
|
-
lruKey = key;
|
|
75
|
+
let oldestKey;
|
|
76
|
+
let oldestTime = now;
|
|
77
|
+
for (const [k, v] of this.cache.entries()) {
|
|
78
|
+
if (v.timestamp < oldestTime) {
|
|
79
|
+
oldestTime = v.timestamp;
|
|
80
|
+
oldestKey = k;
|
|
81
|
+
}
|
|
120
82
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.totalEvictions++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
cleanupExpired() {
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
let cleaned = 0;
|
|
130
|
-
for (const [key, item] of this.cache.entries()) {
|
|
131
|
-
if (item.expiresAt && now > item.expiresAt) {
|
|
132
|
-
this.cache.delete(key);
|
|
133
|
-
cleaned++;
|
|
83
|
+
if (oldestKey) {
|
|
84
|
+
this.cache.delete(oldestKey);
|
|
85
|
+
this.totalEvictions++;
|
|
134
86
|
}
|
|
135
87
|
}
|
|
136
|
-
|
|
137
|
-
this.totalEvictions += cleaned;
|
|
138
|
-
}
|
|
88
|
+
this.cache.set(key, { value, timestamp: now, hits: 0, expiresAt });
|
|
139
89
|
}
|
|
140
90
|
delete(key) {
|
|
141
91
|
return this.cache.delete(key);
|
|
@@ -146,128 +96,93 @@ class Cache {
|
|
|
146
96
|
this.totalMisses = 0;
|
|
147
97
|
this.totalEvictions = 0;
|
|
148
98
|
}
|
|
149
|
-
*entries() {
|
|
150
|
-
for (const [key, value] of this.cache.entries()) {
|
|
151
|
-
yield [key, value];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
getMetadata(key) {
|
|
155
|
-
return this.cache.get(key);
|
|
156
|
-
}
|
|
157
99
|
getStats() {
|
|
158
|
-
const totalRequests = this.totalHits + this.totalMisses;
|
|
159
|
-
let totalSize = 0;
|
|
160
|
-
for (const item of this.cache.values()) {
|
|
161
|
-
totalSize += this.estimateSize(item.value);
|
|
162
|
-
}
|
|
163
100
|
return {
|
|
164
101
|
size: this.cache.size,
|
|
165
102
|
maxSize: this.maxSize,
|
|
166
|
-
hitRate:
|
|
103
|
+
hitRate: this.totalHits / (this.totalHits + this.totalMisses) || 0,
|
|
167
104
|
totalHits: this.totalHits,
|
|
168
105
|
totalMisses: this.totalMisses,
|
|
169
106
|
totalEvictions: this.totalEvictions,
|
|
170
107
|
ttl: this.ttl,
|
|
171
|
-
averageItemSize: this.cache.size > 0 ? totalSize / this.cache.size : 0,
|
|
172
108
|
};
|
|
173
109
|
}
|
|
174
|
-
estimateSize(value) {
|
|
175
|
-
try {
|
|
176
|
-
return JSON.stringify(value).length;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return 1024;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
110
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
111
|
+
const modelCache = new Cache(50);
|
|
112
|
+
const providerCache = new Cache(20);
|
|
113
|
+
const schemaCache = new Cache(30);
|
|
114
|
+
const googleCacheClients = new Cache(10, 60 * 60 * 1000);
|
|
115
|
+
const googleCachedContexts = new Cache(50, 55 * 60 * 1000);
|
|
116
|
+
async function getGoogleCacheManager(apiKey) {
|
|
117
|
+
let client = googleCacheClients.get(apiKey);
|
|
118
|
+
if (!client) {
|
|
119
|
+
const { GoogleGenAI } = await Promise.resolve().then(() => __importStar(require('@google/genai')));
|
|
120
|
+
client = new GoogleGenAI({ apiKey });
|
|
121
|
+
googleCacheClients.set(apiKey, client);
|
|
122
|
+
}
|
|
123
|
+
return client;
|
|
187
124
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const now = Date.now();
|
|
195
|
-
const entriesToDelete = [];
|
|
196
|
-
for (const [key, cacheItem] of googleCachedContexts.entries()) {
|
|
197
|
-
if (cacheItem.expiresAt && now > cacheItem.expiresAt) {
|
|
198
|
-
entriesToDelete.push({
|
|
199
|
-
key,
|
|
200
|
-
cacheName: cacheItem.value.name,
|
|
201
|
-
apiKey: cacheItem.value.apiKey
|
|
202
|
-
});
|
|
125
|
+
async function createGoogleCache(exec, index, apiKey, cacheContent, tools) {
|
|
126
|
+
var _a;
|
|
127
|
+
try {
|
|
128
|
+
const useGoogleCache = exec.getNodeParameter('useGoogleCache', index, false);
|
|
129
|
+
if (!useGoogleCache) {
|
|
130
|
+
return null;
|
|
203
131
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
await client.caches.delete(cacheName);
|
|
132
|
+
const googleCacheManager = await getGoogleCacheManager(apiKey);
|
|
133
|
+
const normalizedCacheContent = (_a = cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.trim()) !== null && _a !== void 0 ? _a : '';
|
|
134
|
+
if (!normalizedCacheContent) {
|
|
135
|
+
return null;
|
|
209
136
|
}
|
|
210
|
-
|
|
211
|
-
|
|
137
|
+
const cacheKeyData = {
|
|
138
|
+
content: normalizedCacheContent,
|
|
139
|
+
tools: tools ? Object.keys(tools).sort() : [],
|
|
140
|
+
model: 'gemini-2.0-flash-001',
|
|
141
|
+
};
|
|
142
|
+
const cacheKey = JSON.stringify(cacheKeyData);
|
|
143
|
+
const existingCache = googleCachedContexts.get(cacheKey);
|
|
144
|
+
if (existingCache) {
|
|
145
|
+
return existingCache.name;
|
|
212
146
|
}
|
|
213
|
-
|
|
214
|
-
|
|
147
|
+
const ttlSeconds = 3600;
|
|
148
|
+
const displayName = `universal_ai_cache_${Date.now()}`;
|
|
149
|
+
const cacheConfig = {
|
|
150
|
+
model: 'gemini-2.0-flash-001',
|
|
151
|
+
config: {
|
|
152
|
+
displayName,
|
|
153
|
+
ttl: `${ttlSeconds}s`,
|
|
154
|
+
contents: [{
|
|
155
|
+
role: 'user',
|
|
156
|
+
parts: [{ text: normalizedCacheContent }],
|
|
157
|
+
}],
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
if (tools && Object.keys(tools).length > 0) {
|
|
161
|
+
cacheConfig.config.tools = Object.values(tools);
|
|
215
162
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
cleanupInterval = setInterval(() => {
|
|
224
|
-
cleanupExpiredGoogleCaches().catch(console.error);
|
|
225
|
-
}, 5 * 60 * 1000);
|
|
226
|
-
}
|
|
227
|
-
startCacheCleanup();
|
|
228
|
-
async function getGoogleCacheManager(apiKey) {
|
|
229
|
-
const cacheKey = generateCacheKey(apiKey, 'google_client:');
|
|
230
|
-
const cachedClient = googleCacheClients.get(cacheKey);
|
|
231
|
-
if (cachedClient) {
|
|
232
|
-
return cachedClient;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
const { GoogleGenAI } = await Promise.resolve().then(() => __importStar(require('@google/genai')));
|
|
236
|
-
const client = new GoogleGenAI({ apiKey });
|
|
237
|
-
googleCacheClients.set(cacheKey, client);
|
|
238
|
-
return client;
|
|
163
|
+
const result = await googleCacheManager.caches.create(cacheConfig);
|
|
164
|
+
const cachedContentName = result === null || result === void 0 ? void 0 : result.name;
|
|
165
|
+
if (!cachedContentName) {
|
|
166
|
+
throw new Error('Failed to get cached content name from creation response');
|
|
167
|
+
}
|
|
168
|
+
googleCachedContexts.set(cacheKey, { name: cachedContentName }, ttlSeconds * 1000);
|
|
169
|
+
return cachedContentName;
|
|
239
170
|
}
|
|
240
171
|
catch (error) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
function isUrl(str) {
|
|
245
|
-
if (typeof str !== 'string')
|
|
246
|
-
return false;
|
|
247
|
-
try {
|
|
248
|
-
const url = new URL(str);
|
|
249
|
-
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
return str.startsWith('data:');
|
|
172
|
+
console.error('UniversalAI: Failed to create Google cache. Falling back to non-cached execution:', error);
|
|
173
|
+
return null;
|
|
253
174
|
}
|
|
254
175
|
}
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
return false;
|
|
258
|
-
if (str.length % 4 !== 0)
|
|
259
|
-
return false;
|
|
260
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(str))
|
|
261
|
-
return false;
|
|
262
|
-
if (str.length > 10000)
|
|
263
|
-
return true;
|
|
264
|
-
return true;
|
|
176
|
+
function canUseCache(cacheContent) {
|
|
177
|
+
return Boolean(cacheContent && cacheContent.trim().length > 0);
|
|
265
178
|
}
|
|
266
179
|
function extractTextFromMessageContent(content) {
|
|
267
|
-
if (!content)
|
|
180
|
+
if (!content) {
|
|
268
181
|
return '';
|
|
269
|
-
|
|
182
|
+
}
|
|
183
|
+
if (typeof content === 'string') {
|
|
270
184
|
return content;
|
|
185
|
+
}
|
|
271
186
|
if (Array.isArray(content)) {
|
|
272
187
|
return content
|
|
273
188
|
.map((part) => {
|
|
@@ -287,20 +202,16 @@ function extractTextFromMessageContent(content) {
|
|
|
287
202
|
}
|
|
288
203
|
return '';
|
|
289
204
|
}
|
|
290
|
-
function canUseCache(cacheContent) {
|
|
291
|
-
return Boolean(cacheContent && cacheContent.trim().length > 0);
|
|
292
|
-
}
|
|
293
205
|
function resolveCacheContent(input) {
|
|
294
|
-
var _a, _b, _c;
|
|
295
206
|
const sections = [];
|
|
296
207
|
let hasSystem = false;
|
|
297
208
|
let hasMessages = false;
|
|
298
209
|
let hasPrompt = false;
|
|
299
|
-
if (
|
|
210
|
+
if (input.system && input.system.trim()) {
|
|
300
211
|
sections.push(`System Instruction:\n${input.system.trim()}`);
|
|
301
212
|
hasSystem = true;
|
|
302
213
|
}
|
|
303
|
-
if (
|
|
214
|
+
if (input.messages && input.messages.length > 0) {
|
|
304
215
|
const messageSections = [];
|
|
305
216
|
for (const message of input.messages) {
|
|
306
217
|
const text = extractTextFromMessageContent(message.content);
|
|
@@ -311,31 +222,34 @@ function resolveCacheContent(input) {
|
|
|
311
222
|
sections.push(`System Instruction (from messages):\n${text.trim()}`);
|
|
312
223
|
hasSystem = true;
|
|
313
224
|
}
|
|
225
|
+
continue;
|
|
314
226
|
}
|
|
315
|
-
|
|
316
|
-
messageSections.push(`${message.role.toUpperCase()}:\n${text.trim()}`);
|
|
317
|
-
}
|
|
227
|
+
messageSections.push(`${message.role.toUpperCase()}:\n${text.trim()}`);
|
|
318
228
|
}
|
|
319
229
|
if (messageSections.length > 0) {
|
|
320
230
|
sections.push(`Messages:\n${messageSections.join('\n\n')}`);
|
|
321
231
|
hasMessages = true;
|
|
322
232
|
}
|
|
323
233
|
}
|
|
324
|
-
if (
|
|
234
|
+
if (input.prompt && input.prompt.trim()) {
|
|
325
235
|
sections.push(`Prompt Template:\n${input.prompt.trim()}`);
|
|
326
236
|
hasPrompt = true;
|
|
327
237
|
}
|
|
328
238
|
const content = sections.join('\n\n').trim();
|
|
329
239
|
let source;
|
|
330
240
|
const sourceCount = [hasSystem, hasMessages, hasPrompt].filter(Boolean).length;
|
|
331
|
-
if (sourceCount > 1)
|
|
241
|
+
if (sourceCount > 1) {
|
|
332
242
|
source = 'combined';
|
|
333
|
-
|
|
243
|
+
}
|
|
244
|
+
else if (hasSystem) {
|
|
334
245
|
source = 'system';
|
|
335
|
-
|
|
246
|
+
}
|
|
247
|
+
else if (hasMessages) {
|
|
336
248
|
source = 'messages';
|
|
337
|
-
|
|
249
|
+
}
|
|
250
|
+
else if (hasPrompt) {
|
|
338
251
|
source = 'prompt';
|
|
252
|
+
}
|
|
339
253
|
return {
|
|
340
254
|
content: content || undefined,
|
|
341
255
|
hasSystem,
|
|
@@ -344,90 +258,8 @@ function resolveCacheContent(input) {
|
|
|
344
258
|
source,
|
|
345
259
|
};
|
|
346
260
|
}
|
|
347
|
-
async function createGoogleCache(exec, index, apiKey, cacheContent, tools) {
|
|
348
|
-
try {
|
|
349
|
-
const useGoogleCache = exec.getNodeParameter('useGoogleCache', index, false);
|
|
350
|
-
if (!useGoogleCache || !(cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.trim())) {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
const googleCacheManager = await getGoogleCacheManager(apiKey);
|
|
354
|
-
const normalizedCacheContent = cacheContent.trim();
|
|
355
|
-
const cacheKeyData = {
|
|
356
|
-
content: normalizedCacheContent,
|
|
357
|
-
tools: tools ? Object.keys(tools).sort() : [],
|
|
358
|
-
model: 'gemini-2.0-flash-001',
|
|
359
|
-
};
|
|
360
|
-
const cacheKey = generateCacheKey(cacheKeyData, 'google_cache:');
|
|
361
|
-
const existingCache = googleCachedContexts.get(cacheKey);
|
|
362
|
-
if (existingCache) {
|
|
363
|
-
return existingCache.name;
|
|
364
|
-
}
|
|
365
|
-
const ttlSeconds = 3600;
|
|
366
|
-
const displayName = `n8n_cache_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
367
|
-
const cacheConfig = {
|
|
368
|
-
model: 'gemini-2.0-flash-001',
|
|
369
|
-
config: {
|
|
370
|
-
displayName,
|
|
371
|
-
ttl: `${ttlSeconds}s`,
|
|
372
|
-
contents: [{
|
|
373
|
-
role: 'user',
|
|
374
|
-
parts: [{ text: normalizedCacheContent }],
|
|
375
|
-
}],
|
|
376
|
-
},
|
|
377
|
-
};
|
|
378
|
-
if (tools && Object.keys(tools).length > 0) {
|
|
379
|
-
cacheConfig.config.tools = Object.values(tools);
|
|
380
|
-
}
|
|
381
|
-
const result = await googleCacheManager.caches.create(cacheConfig);
|
|
382
|
-
const cachedContentName = result === null || result === void 0 ? void 0 : result.name;
|
|
383
|
-
if (!cachedContentName) {
|
|
384
|
-
throw new CacheError('Failed to create cache: No name in response', { displayName });
|
|
385
|
-
}
|
|
386
|
-
googleCachedContexts.set(cacheKey, {
|
|
387
|
-
name: cachedContentName,
|
|
388
|
-
apiKey
|
|
389
|
-
}, ttlSeconds * 1000);
|
|
390
|
-
return cachedContentName;
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
if (error instanceof CacheError)
|
|
394
|
-
throw error;
|
|
395
|
-
throw new CacheError(`Google cache creation failed: ${error.message}`, {
|
|
396
|
-
cacheContentLength: cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.length,
|
|
397
|
-
hasTools: !!tools && Object.keys(tools).length > 0
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
async function buildInput(exec, itemIndex) {
|
|
402
|
-
const inputType = exec.getNodeParameter('inputType', itemIndex);
|
|
403
|
-
if (inputType === 'prompt') {
|
|
404
|
-
return buildPromptInput(exec, itemIndex);
|
|
405
|
-
}
|
|
406
|
-
const messageAsJson = exec.getNodeParameter('messageAsJson', itemIndex, false);
|
|
407
|
-
return messageAsJson
|
|
408
|
-
? buildMessagesFromJson(exec, itemIndex)
|
|
409
|
-
: buildMessagesFromUI(exec, itemIndex);
|
|
410
|
-
}
|
|
411
|
-
function buildPromptInput(exec, itemIndex) {
|
|
412
|
-
const result = {};
|
|
413
|
-
const promptValue = exec.getNodeParameter('prompt', itemIndex, '').trim();
|
|
414
|
-
if (promptValue) {
|
|
415
|
-
if (promptValue.length > 100000) {
|
|
416
|
-
throw new ValidationError('Prompt is too long (max 100,000 characters)');
|
|
417
|
-
}
|
|
418
|
-
result.prompt = promptValue;
|
|
419
|
-
}
|
|
420
|
-
const systemValue = exec.getNodeParameter('system', itemIndex, '').trim();
|
|
421
|
-
if (systemValue) {
|
|
422
|
-
if (systemValue.length > 50000) {
|
|
423
|
-
throw new ValidationError('System instruction is too long (max 50,000 characters)');
|
|
424
|
-
}
|
|
425
|
-
result.system = systemValue;
|
|
426
|
-
}
|
|
427
|
-
return result;
|
|
428
|
-
}
|
|
429
261
|
const messageSchema = zod_1.z.object({
|
|
430
|
-
role: zod_1.z.enum(['system', 'user', 'assistant'
|
|
262
|
+
role: zod_1.z.enum(['system', 'user', 'assistant']),
|
|
431
263
|
content: zod_1.z.any(),
|
|
432
264
|
});
|
|
433
265
|
const messagesArraySchema = zod_1.z.array(messageSchema);
|
|
@@ -435,143 +267,111 @@ const ajv = new ajv_1.default({
|
|
|
435
267
|
allErrors: true,
|
|
436
268
|
verbose: true,
|
|
437
269
|
strict: false,
|
|
438
|
-
useDefaults: true,
|
|
439
|
-
removeAdditional: true,
|
|
440
270
|
});
|
|
271
|
+
const isUrl = (str) => {
|
|
272
|
+
if (typeof str !== 'string')
|
|
273
|
+
return false;
|
|
274
|
+
return str.startsWith('http://') ||
|
|
275
|
+
str.startsWith('https://') ||
|
|
276
|
+
str.startsWith('data:');
|
|
277
|
+
};
|
|
278
|
+
const isLikelyBase64 = (str) => {
|
|
279
|
+
if (str.length % 4 !== 0)
|
|
280
|
+
return false;
|
|
281
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(str))
|
|
282
|
+
return false;
|
|
283
|
+
if (str.length > 10000)
|
|
284
|
+
return true;
|
|
285
|
+
return true;
|
|
286
|
+
};
|
|
287
|
+
async function buildInput(exec, itemIndex) {
|
|
288
|
+
const inputType = exec.getNodeParameter('inputType', itemIndex);
|
|
289
|
+
if (inputType === 'prompt') {
|
|
290
|
+
const promptValue = exec.getNodeParameter('prompt', itemIndex, '');
|
|
291
|
+
const systemValue = exec.getNodeParameter('system', itemIndex, '');
|
|
292
|
+
const result = {};
|
|
293
|
+
const trimmedPrompt = typeof promptValue === 'string' ? promptValue.trim() : '';
|
|
294
|
+
if (trimmedPrompt) {
|
|
295
|
+
result.prompt = trimmedPrompt;
|
|
296
|
+
}
|
|
297
|
+
const trimmedSystem = typeof systemValue === 'string' ? systemValue.trim() : '';
|
|
298
|
+
if (trimmedSystem) {
|
|
299
|
+
result.system = trimmedSystem;
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
const messageAsJson = exec.getNodeParameter('messageAsJson', itemIndex, false);
|
|
304
|
+
return messageAsJson
|
|
305
|
+
? buildMessagesFromJson(exec, itemIndex)
|
|
306
|
+
: buildMessagesFromUI(exec, itemIndex);
|
|
307
|
+
}
|
|
441
308
|
async function buildMessagesFromJson(exec, itemIndex) {
|
|
442
309
|
const rawJson = exec.getNodeParameter('messagesJson', itemIndex);
|
|
443
|
-
if (!rawJson.trim()) {
|
|
444
|
-
throw new ValidationError('Messages JSON field is empty');
|
|
445
|
-
}
|
|
446
|
-
if (rawJson.length > 200000) {
|
|
447
|
-
throw new ValidationError('Messages JSON is too large (max 200,000 characters)');
|
|
448
|
-
}
|
|
449
310
|
try {
|
|
450
311
|
const parsed = JSON.parse(rawJson);
|
|
451
312
|
const result = messagesArraySchema.safeParse(parsed);
|
|
452
313
|
if (!result.success) {
|
|
453
|
-
|
|
454
|
-
.map((issue) => {
|
|
455
|
-
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
456
|
-
return `${path}: ${issue.message}`;
|
|
457
|
-
})
|
|
458
|
-
.join('; ');
|
|
459
|
-
throw new ValidationError(`Invalid messages format: ${errorDetails}`);
|
|
460
|
-
}
|
|
461
|
-
if (result.data.length > 100) {
|
|
462
|
-
throw new ValidationError('Too many messages (max 100)');
|
|
314
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), 'Messages must be an array of objects with role and content.');
|
|
463
315
|
}
|
|
464
316
|
return { messages: result.data };
|
|
465
317
|
}
|
|
466
318
|
catch (error) {
|
|
467
|
-
|
|
468
|
-
throw error;
|
|
469
|
-
throw new ValidationError(`Invalid JSON in messages field: ${error.message}`);
|
|
319
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), `Invalid JSON in "Messages (JSON)" field: ${error.message}`);
|
|
470
320
|
}
|
|
471
321
|
}
|
|
472
322
|
async function buildMessagesFromUI(exec, itemIndex) {
|
|
473
|
-
var _a
|
|
323
|
+
var _a;
|
|
474
324
|
const items = exec.getInputData();
|
|
475
325
|
const messagesUi = exec.getNodeParameter('messages.messagesUi', itemIndex, []);
|
|
476
|
-
if (messagesUi.length > 100) {
|
|
477
|
-
throw new ValidationError('Too many messages (max 100)');
|
|
478
|
-
}
|
|
479
326
|
const builtMessages = [];
|
|
480
327
|
const itemBinary = items[itemIndex].binary;
|
|
481
328
|
for (const msg of messagesUi) {
|
|
482
329
|
const role = msg.role;
|
|
483
330
|
if (role === 'system') {
|
|
484
|
-
|
|
485
|
-
builtMessages.push({ role, content: msg.systemContent.trim() });
|
|
486
|
-
}
|
|
331
|
+
builtMessages.push({ role, content: msg.systemContent || '' });
|
|
487
332
|
continue;
|
|
488
333
|
}
|
|
489
|
-
const attachments = ((
|
|
490
|
-
const content = ((_c = msg.content) === null || _c === void 0 ? void 0 : _c.trim()) || '';
|
|
334
|
+
const attachments = ((_a = msg.attachments) === null || _a === void 0 ? void 0 : _a.attachment) || [];
|
|
491
335
|
if (attachments.length === 0) {
|
|
492
|
-
|
|
493
|
-
builtMessages.push({ role, content });
|
|
494
|
-
}
|
|
336
|
+
builtMessages.push({ role, content: msg.content || '' });
|
|
495
337
|
}
|
|
496
338
|
else {
|
|
497
|
-
const messageWithAttachments = await buildMessageWithAttachments(role, content, attachments, itemBinary, exec, itemIndex);
|
|
339
|
+
const messageWithAttachments = await buildMessageWithAttachments(role, msg.content, attachments, itemBinary, exec, itemIndex);
|
|
498
340
|
if (messageWithAttachments) {
|
|
499
341
|
builtMessages.push(messageWithAttachments);
|
|
500
342
|
}
|
|
501
343
|
}
|
|
502
344
|
}
|
|
503
345
|
const convertMessagesToModel = exec.getNodeParameter('convertMessagesToModel', itemIndex, false);
|
|
504
|
-
|
|
505
|
-
messages:
|
|
506
|
-
}
|
|
346
|
+
if (convertMessagesToModel) {
|
|
347
|
+
return { messages: (0, ai_1.convertToModelMessages)(builtMessages) };
|
|
348
|
+
}
|
|
349
|
+
return { messages: builtMessages };
|
|
507
350
|
}
|
|
508
|
-
const MAX_ATTACHMENT_SIZE = 50 * 1024 * 1024;
|
|
509
|
-
const MAX_TOTAL_ATTACHMENTS_SIZE = 100 * 1024 * 1024;
|
|
510
351
|
async function buildMessageWithAttachments(role, content, attachments, itemBinary, exec, itemIndex) {
|
|
511
352
|
const parts = [];
|
|
512
353
|
if (content) {
|
|
513
354
|
parts.push({ type: 'text', text: content });
|
|
514
355
|
}
|
|
515
|
-
let totalSize = 0;
|
|
516
356
|
const MAX_CONCURRENT_ATTACHMENTS = 3;
|
|
357
|
+
const processedAttachments = [];
|
|
517
358
|
for (let i = 0; i < attachments.length; i += MAX_CONCURRENT_ATTACHMENTS) {
|
|
518
359
|
const batch = attachments.slice(i, i + MAX_CONCURRENT_ATTACHMENTS);
|
|
519
360
|
const batchPromises = batch.map(attachment => processAttachment(attachment, itemBinary, exec, itemIndex));
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
if (attachment) {
|
|
523
|
-
if (attachment.data instanceof Buffer) {
|
|
524
|
-
totalSize += attachment.data.length;
|
|
525
|
-
if (totalSize > MAX_TOTAL_ATTACHMENTS_SIZE) {
|
|
526
|
-
throw new ValidationError(`Total attachments size exceeds limit of ${MAX_TOTAL_ATTACHMENTS_SIZE / 1024 / 1024}MB`);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
parts.push(attachment);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return parts.length > 0 ? { role, content: parts } : null;
|
|
534
|
-
}
|
|
535
|
-
function getMimeType(attachment) {
|
|
536
|
-
return attachment.mimeType === 'other' ? attachment.mimeTypeOther : attachment.mimeType;
|
|
537
|
-
}
|
|
538
|
-
async function getBinaryData(fileContentInput, itemBinary, exec, itemIndex) {
|
|
539
|
-
if (itemBinary === null || itemBinary === void 0 ? void 0 : itemBinary[fileContentInput]) {
|
|
540
|
-
const binaryData = itemBinary[fileContentInput];
|
|
541
|
-
if (binaryData.data) {
|
|
542
|
-
const buffer = Buffer.from(binaryData.data, 'base64');
|
|
543
|
-
if (buffer.length > MAX_ATTACHMENT_SIZE) {
|
|
544
|
-
throw new ValidationError(`Attachment too large: ${buffer.length / 1024 / 1024}MB (max ${MAX_ATTACHMENT_SIZE / 1024 / 1024}MB)`);
|
|
545
|
-
}
|
|
546
|
-
return {
|
|
547
|
-
data: buffer,
|
|
548
|
-
mimeType: binaryData.mimeType
|
|
549
|
-
};
|
|
550
|
-
}
|
|
361
|
+
const batchResults = await Promise.all(batchPromises);
|
|
362
|
+
processedAttachments.push(...batchResults);
|
|
551
363
|
}
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
if (buffer.length > MAX_ATTACHMENT_SIZE) {
|
|
556
|
-
throw new ValidationError(`Attachment too large: ${buffer.length / 1024 / 1024}MB (max ${MAX_ATTACHMENT_SIZE / 1024 / 1024}MB)`);
|
|
557
|
-
}
|
|
558
|
-
if (buffer.length > 0) {
|
|
559
|
-
return { data: buffer, mimeType: undefined };
|
|
560
|
-
}
|
|
364
|
+
for (const attachment of processedAttachments) {
|
|
365
|
+
if (attachment) {
|
|
366
|
+
parts.push(attachment);
|
|
561
367
|
}
|
|
562
368
|
}
|
|
563
|
-
|
|
564
|
-
if (error instanceof ValidationError)
|
|
565
|
-
throw error;
|
|
566
|
-
throw new ValidationError(`Invalid file content for attachment: ${error.message}`);
|
|
567
|
-
}
|
|
568
|
-
return { data: null, mimeType: undefined };
|
|
369
|
+
return parts.length > 0 ? { role, content: parts } : null;
|
|
569
370
|
}
|
|
570
371
|
async function processAttachment(attachment, itemBinary, exec, itemIndex) {
|
|
571
372
|
const fileContentInput = attachment.fileContent;
|
|
572
|
-
if (!fileContentInput || typeof fileContentInput !== 'string')
|
|
373
|
+
if (!fileContentInput || typeof fileContentInput !== 'string')
|
|
573
374
|
return null;
|
|
574
|
-
}
|
|
575
375
|
let mimeType = getMimeType(attachment);
|
|
576
376
|
let fileData;
|
|
577
377
|
if (isUrl(fileContentInput)) {
|
|
@@ -579,19 +379,46 @@ async function processAttachment(attachment, itemBinary, exec, itemIndex) {
|
|
|
579
379
|
}
|
|
580
380
|
else {
|
|
581
381
|
const result = await getBinaryData(fileContentInput, itemBinary, exec, itemIndex);
|
|
582
|
-
if (!result.data)
|
|
583
|
-
return null;
|
|
584
382
|
fileData = result.data;
|
|
585
383
|
if (!mimeType && result.mimeType) {
|
|
586
384
|
mimeType = result.mimeType;
|
|
587
385
|
}
|
|
588
386
|
}
|
|
387
|
+
if (!fileData || (Buffer.isBuffer(fileData) && fileData.length === 0)) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
589
390
|
return {
|
|
590
391
|
type: 'file',
|
|
591
392
|
data: fileData,
|
|
592
|
-
mediaType: mimeType || 'application/octet-stream'
|
|
393
|
+
mediaType: mimeType || 'application/octet-stream',
|
|
593
394
|
};
|
|
594
395
|
}
|
|
396
|
+
function getMimeType(attachment) {
|
|
397
|
+
return attachment.mimeType === 'other'
|
|
398
|
+
? attachment.mimeTypeOther
|
|
399
|
+
: attachment.mimeType;
|
|
400
|
+
}
|
|
401
|
+
async function getBinaryData(fileContentInput, itemBinary, exec, itemIndex) {
|
|
402
|
+
if (itemBinary === null || itemBinary === void 0 ? void 0 : itemBinary[fileContentInput]) {
|
|
403
|
+
const binaryData = itemBinary[fileContentInput];
|
|
404
|
+
return {
|
|
405
|
+
data: Buffer.from(binaryData.data, 'base64'),
|
|
406
|
+
mimeType: binaryData.mimeType,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
if (isLikelyBase64(fileContentInput)) {
|
|
411
|
+
const buffer = Buffer.from(fileContentInput, 'base64');
|
|
412
|
+
if (buffer.length > 0 && buffer.length < 50 * 1024 * 1024) {
|
|
413
|
+
return { data: buffer, mimeType: undefined };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), `Invalid file content for attachment: ${error.message}`);
|
|
419
|
+
}
|
|
420
|
+
return { data: null, mimeType: undefined };
|
|
421
|
+
}
|
|
595
422
|
function formatTextResult(result, includeRequestBody, provider) {
|
|
596
423
|
var _a, _b, _c, _d, _e;
|
|
597
424
|
let { text, reasoning } = result;
|
|
@@ -705,159 +532,163 @@ function getCacheMetrics(result, provider, metadata) {
|
|
|
705
532
|
}
|
|
706
533
|
function formatResponse(result) {
|
|
707
534
|
var _a, _b, _c, _d;
|
|
708
|
-
|
|
535
|
+
const response = {
|
|
709
536
|
id: (_a = result.response) === null || _a === void 0 ? void 0 : _a.id,
|
|
710
537
|
modelId: (_b = result.response) === null || _b === void 0 ? void 0 : _b.modelId,
|
|
711
538
|
timestamp: (_c = result.response) === null || _c === void 0 ? void 0 : _c.timestamp,
|
|
712
539
|
headers: (_d = result.response) === null || _d === void 0 ? void 0 : _d.headers,
|
|
713
540
|
};
|
|
541
|
+
return response;
|
|
714
542
|
}
|
|
715
|
-
async function getProvider(provider,
|
|
716
|
-
const headersKey =
|
|
717
|
-
?
|
|
543
|
+
async function getProvider(provider, apiKey, baseURL, customHeaders) {
|
|
544
|
+
const headersKey = customHeaders
|
|
545
|
+
? JSON.stringify(Object.keys(customHeaders)
|
|
718
546
|
.sort()
|
|
719
|
-
.map((key) => [key,
|
|
547
|
+
.map((key) => [key, customHeaders[key]]))
|
|
720
548
|
: '';
|
|
721
|
-
const cacheKey =
|
|
549
|
+
const cacheKey = `${provider}:${apiKey}:${baseURL || ''}:${headersKey}`;
|
|
722
550
|
const cached = providerCache.get(cacheKey);
|
|
723
551
|
if (cached)
|
|
724
552
|
return cached;
|
|
553
|
+
let providerInstance;
|
|
725
554
|
try {
|
|
726
|
-
let providerInstance;
|
|
727
555
|
switch (provider) {
|
|
728
556
|
case 'google':
|
|
729
557
|
const { createGoogleGenerativeAI } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/google')));
|
|
730
558
|
providerInstance = createGoogleGenerativeAI({
|
|
731
|
-
apiKey
|
|
732
|
-
...(
|
|
733
|
-
...(
|
|
734
|
-
headers: config.customHeaders
|
|
735
|
-
}),
|
|
559
|
+
apiKey,
|
|
560
|
+
...(baseURL && { baseURL }),
|
|
561
|
+
...(customHeaders && Object.keys(customHeaders).length > 0 && { headers: customHeaders }),
|
|
736
562
|
});
|
|
737
563
|
break;
|
|
738
564
|
case 'deepseek':
|
|
739
565
|
const { createDeepSeek } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/deepseek')));
|
|
740
|
-
providerInstance = createDeepSeek({
|
|
741
|
-
apiKey: config.apiKey,
|
|
742
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
743
|
-
});
|
|
566
|
+
providerInstance = createDeepSeek({ apiKey, ...(baseURL && { baseURL }) });
|
|
744
567
|
break;
|
|
745
568
|
case 'groq':
|
|
746
569
|
const { createGroq } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/groq')));
|
|
747
|
-
providerInstance = createGroq({
|
|
748
|
-
apiKey: config.apiKey,
|
|
749
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
750
|
-
});
|
|
570
|
+
providerInstance = createGroq({ apiKey, ...(baseURL && { baseURL }) });
|
|
751
571
|
break;
|
|
752
572
|
case 'openrouter':
|
|
753
573
|
const { createOpenRouter } = await Promise.resolve().then(() => __importStar(require('@openrouter/ai-sdk-provider')));
|
|
754
|
-
providerInstance = createOpenRouter({
|
|
755
|
-
apiKey: config.apiKey,
|
|
756
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
757
|
-
});
|
|
574
|
+
providerInstance = createOpenRouter({ apiKey, ...(baseURL && { baseURL }) });
|
|
758
575
|
break;
|
|
759
576
|
default:
|
|
760
|
-
throw new
|
|
577
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
761
578
|
}
|
|
762
579
|
providerCache.set(cacheKey, providerInstance);
|
|
763
580
|
return providerInstance;
|
|
764
581
|
}
|
|
765
582
|
catch (error) {
|
|
766
|
-
throw new
|
|
583
|
+
throw new Error(`Failed to initialize ${provider} provider: ${error.message}`);
|
|
767
584
|
}
|
|
768
585
|
}
|
|
769
586
|
function parseAndValidateSchema(rawSchema, exec) {
|
|
770
|
-
|
|
771
|
-
throw new ValidationError('Schema field is empty');
|
|
772
|
-
}
|
|
773
|
-
if (rawSchema.length > 100000) {
|
|
774
|
-
throw new ValidationError('Schema is too large (max 100,000 characters)');
|
|
775
|
-
}
|
|
776
|
-
const cacheKey = generateCacheKey(rawSchema, 'schema:');
|
|
587
|
+
const cacheKey = `schema:${Buffer.from(rawSchema).toString('base64').substring(0, 50)}`;
|
|
777
588
|
const cached = schemaCache.get(cacheKey);
|
|
778
589
|
if (cached)
|
|
779
590
|
return cached;
|
|
591
|
+
let parsedSchema;
|
|
780
592
|
try {
|
|
781
|
-
|
|
782
|
-
if (!ajv.validateSchema(parsedSchema)) {
|
|
783
|
-
throw new ValidationError(`Invalid JSON Schema: ${ajv.errorsText(ajv.errors)}`);
|
|
784
|
-
}
|
|
785
|
-
schemaCache.set(cacheKey, parsedSchema);
|
|
786
|
-
return parsedSchema;
|
|
593
|
+
parsedSchema = JSON.parse(rawSchema);
|
|
787
594
|
}
|
|
788
595
|
catch (err) {
|
|
789
|
-
throw new
|
|
596
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), 'Schema is not valid JSON: ' + err.message);
|
|
597
|
+
}
|
|
598
|
+
if (!ajv.validateSchema(parsedSchema)) {
|
|
599
|
+
throw new n8n_workflow_1.NodeOperationError(exec.getNode(), `Invalid JSON Schema: ${ajv.errorsText(ajv.errors)}`);
|
|
790
600
|
}
|
|
601
|
+
schemaCache.set(cacheKey, parsedSchema);
|
|
602
|
+
return parsedSchema;
|
|
791
603
|
}
|
|
792
604
|
function parseStopSequences(stopSequencesStr) {
|
|
793
605
|
if (!stopSequencesStr)
|
|
794
606
|
return undefined;
|
|
795
|
-
|
|
796
|
-
.split(',')
|
|
797
|
-
.map(s => s.trim())
|
|
798
|
-
.filter(Boolean);
|
|
799
|
-
return sequences.length > 0 ? sequences : undefined;
|
|
607
|
+
return stopSequencesStr.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
800
608
|
}
|
|
801
609
|
function applyNumericOptions(params, options, keys) {
|
|
802
610
|
for (const key of keys) {
|
|
803
611
|
const value = options[key];
|
|
804
612
|
if (value !== undefined && value !== null && value !== '') {
|
|
805
|
-
|
|
806
|
-
if (!isNaN(numValue)) {
|
|
807
|
-
params[key] = numValue;
|
|
808
|
-
}
|
|
613
|
+
params[key] = value;
|
|
809
614
|
}
|
|
810
615
|
}
|
|
811
616
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
617
|
+
class UniversalAI {
|
|
618
|
+
constructor() {
|
|
619
|
+
this.description = descriptions_1.UNIVERSAL_AI_DESCRIPTION;
|
|
620
|
+
this.methods = {
|
|
621
|
+
loadOptions: {
|
|
622
|
+
async getModels() {
|
|
623
|
+
const provider = this.getCurrentNodeParameter('provider');
|
|
624
|
+
const cacheKey = `models:${provider}`;
|
|
625
|
+
const cached = modelCache.get(cacheKey);
|
|
626
|
+
if (cached)
|
|
627
|
+
return cached;
|
|
628
|
+
const { OPENROUTER_MODELS, GOOGLE_GEMINI_MODELS, DEEPSEEK_MODELS, GROQ_MODELS } = await Promise.resolve().then(() => __importStar(require('./model-lists')));
|
|
629
|
+
const models = {
|
|
630
|
+
google: GOOGLE_GEMINI_MODELS,
|
|
631
|
+
deepseek: DEEPSEEK_MODELS,
|
|
632
|
+
groq: GROQ_MODELS,
|
|
633
|
+
openrouter: OPENROUTER_MODELS,
|
|
634
|
+
}[provider] || [];
|
|
635
|
+
modelCache.set(cacheKey, models);
|
|
636
|
+
return models;
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
};
|
|
825
640
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
641
|
+
async execute() {
|
|
642
|
+
const items = this.getInputData();
|
|
643
|
+
const returnData = [];
|
|
644
|
+
const provider = this.getNodeParameter('provider', 0);
|
|
645
|
+
const credentialType = {
|
|
646
|
+
google: 'googleGenerativeAIApi',
|
|
647
|
+
deepseek: 'deepSeekApi',
|
|
648
|
+
groq: 'groqApi',
|
|
649
|
+
openrouter: 'openRouterApi',
|
|
650
|
+
}[provider];
|
|
651
|
+
if (!credentialType) {
|
|
652
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported provider: ${provider}`);
|
|
832
653
|
}
|
|
833
|
-
|
|
834
|
-
|
|
654
|
+
const credentials = await this.getCredentials(credentialType);
|
|
655
|
+
if (!(credentials === null || credentials === void 0 ? void 0 : credentials.apiKey)) {
|
|
656
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No API key provided in credentials');
|
|
835
657
|
}
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
success: false
|
|
658
|
+
const customHeaders = provider === 'google' ? getGoogleCustomHeaders(this, 0) : undefined;
|
|
659
|
+
const aiProvider = await getProvider(provider, credentials.apiKey, credentials.baseUrl, customHeaders);
|
|
660
|
+
for (let i = 0; i < items.length; i++) {
|
|
661
|
+
if (this.continueOnFail()) {
|
|
662
|
+
try {
|
|
663
|
+
const result = await processItem(this, i, provider, aiProvider, credentials.apiKey);
|
|
664
|
+
returnData.push(...result);
|
|
844
665
|
}
|
|
845
|
-
|
|
666
|
+
catch (error) {
|
|
667
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
668
|
+
returnData.push({
|
|
669
|
+
json: { error: errorMessage },
|
|
670
|
+
pairedItem: { item: i },
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
const result = await processItem(this, i, provider, aiProvider, credentials.apiKey);
|
|
676
|
+
returnData.push(...result);
|
|
677
|
+
}
|
|
846
678
|
}
|
|
847
|
-
|
|
848
|
-
}
|
|
679
|
+
return [returnData];
|
|
680
|
+
}
|
|
849
681
|
}
|
|
682
|
+
exports.UniversalAI = UniversalAI;
|
|
850
683
|
async function processItem(exec, index, provider, aiProvider, apiKey) {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
: await generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey);
|
|
860
|
-
}, 'processItem', exec, index);
|
|
684
|
+
const operation = exec.getNodeParameter('operation', index);
|
|
685
|
+
const model = exec.getNodeParameter('model', index);
|
|
686
|
+
const options = exec.getNodeParameter('options', index, {});
|
|
687
|
+
const input = await buildInput(exec, index);
|
|
688
|
+
const modelSettings = getModelSettings(exec, index, provider, operation, options);
|
|
689
|
+
return operation === 'generateText'
|
|
690
|
+
? await generateTextOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey)
|
|
691
|
+
: await generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey);
|
|
861
692
|
}
|
|
862
693
|
function getModelSettings(exec, index, provider, operation, options) {
|
|
863
694
|
const settings = {};
|
|
@@ -868,14 +699,12 @@ function getModelSettings(exec, index, provider, operation, options) {
|
|
|
868
699
|
if (provider === 'google') {
|
|
869
700
|
const safetySettingsRaw = exec.getNodeParameter('safetySettings.settings', index, []);
|
|
870
701
|
if (safetySettingsRaw.length > 0) {
|
|
871
|
-
settings.safetySettings = safetySettingsRaw.map(s => ({
|
|
702
|
+
settings.safetySettings = safetySettingsRaw.map((s) => ({
|
|
872
703
|
category: s.category,
|
|
873
704
|
threshold: s.threshold,
|
|
874
705
|
}));
|
|
875
706
|
}
|
|
876
|
-
|
|
877
|
-
settings.structuredOutputs = true;
|
|
878
|
-
}
|
|
707
|
+
settings.structuredOutputs = operation === 'generateObject';
|
|
879
708
|
const responseModalities = exec.getNodeParameter('responseModalities', index, []);
|
|
880
709
|
if (responseModalities.length > 0) {
|
|
881
710
|
settings.responseModalities = responseModalities;
|
|
@@ -890,7 +719,7 @@ function buildGoogleProviderOptions(exec, index, cachedContentName) {
|
|
|
890
719
|
if (!Number.isNaN(thinkingBudgetValue) && thinkingBudgetValue > -1) {
|
|
891
720
|
options.thinkingConfig = {
|
|
892
721
|
thinkingBudget: Math.max(0, thinkingBudgetValue),
|
|
893
|
-
includeThoughts
|
|
722
|
+
includeThoughts,
|
|
894
723
|
};
|
|
895
724
|
}
|
|
896
725
|
if (cachedContentName) {
|
|
@@ -899,16 +728,20 @@ function buildGoogleProviderOptions(exec, index, cachedContentName) {
|
|
|
899
728
|
return Object.keys(options).length > 0 ? options : undefined;
|
|
900
729
|
}
|
|
901
730
|
function getGoogleCustomHeaders(exec, index) {
|
|
902
|
-
var _a, _b
|
|
731
|
+
var _a, _b;
|
|
903
732
|
const headersCollection = exec.getNodeParameter('customHeaders', index, {});
|
|
904
733
|
const entries = (_a = headersCollection === null || headersCollection === void 0 ? void 0 : headersCollection.headers) !== null && _a !== void 0 ? _a : [];
|
|
905
|
-
if (!entries.length)
|
|
734
|
+
if (!entries || entries.length === 0) {
|
|
906
735
|
return undefined;
|
|
736
|
+
}
|
|
907
737
|
const headers = {};
|
|
908
738
|
for (const entry of entries) {
|
|
909
|
-
if (
|
|
910
|
-
|
|
911
|
-
|
|
739
|
+
if (!entry)
|
|
740
|
+
continue;
|
|
741
|
+
const name = (entry.name || '').trim();
|
|
742
|
+
if (!name)
|
|
743
|
+
continue;
|
|
744
|
+
headers[name] = (_b = entry.value) !== null && _b !== void 0 ? _b : '';
|
|
912
745
|
}
|
|
913
746
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
914
747
|
}
|
|
@@ -921,101 +754,82 @@ async function prepareGoogleCache(exec, index, apiKey, input, tools, context) {
|
|
|
921
754
|
cachedContentName = await createGoogleCache(exec, index, apiKey, cacheContentInfo.content, tools);
|
|
922
755
|
}
|
|
923
756
|
catch (error) {
|
|
924
|
-
|
|
925
|
-
console.warn(`Cache creation failed for ${context}:`, error.message);
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
console.warn(`Unexpected cache error for ${context}:`, error);
|
|
929
|
-
}
|
|
757
|
+
console.warn(`UniversalAI: Cache creation for ${context} generation failed, continuing without cache:`, error);
|
|
930
758
|
}
|
|
931
759
|
}
|
|
932
760
|
const googleProviderOptions = buildGoogleProviderOptions(exec, index, cachedContentName || undefined);
|
|
933
761
|
return {
|
|
934
762
|
cachedContentName,
|
|
935
763
|
cacheContentInfo,
|
|
936
|
-
googleProviderOptions
|
|
764
|
+
googleProviderOptions,
|
|
937
765
|
};
|
|
938
766
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const finalContext = resolveFinalContext(input, cachedContentName, cacheContentInfo);
|
|
954
|
-
const params = {
|
|
955
|
-
model: aiProvider(model, modelSettings),
|
|
956
|
-
...finalContext,
|
|
957
|
-
};
|
|
958
|
-
if (tools && !cachedContentName) {
|
|
959
|
-
params.tools = tools;
|
|
960
|
-
}
|
|
961
|
-
if (provider === 'google' && googleProviderOptions) {
|
|
962
|
-
params.providerOptions = {
|
|
963
|
-
google: googleProviderOptions,
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
applyNumericOptions(params, options, [
|
|
967
|
-
'maxTokens', 'temperature', 'topP', 'topK',
|
|
968
|
-
'frequencyPenalty', 'presencePenalty', 'seed'
|
|
969
|
-
]);
|
|
970
|
-
if (enableStreaming) {
|
|
971
|
-
return await handleStreaming(params, provider, includeRequestBody);
|
|
767
|
+
function resolveFinalContext(input, cachedContentName, cacheContentInfo) {
|
|
768
|
+
const finalContext = {};
|
|
769
|
+
if (!cachedContentName) {
|
|
770
|
+
return input;
|
|
771
|
+
}
|
|
772
|
+
if (input.system) {
|
|
773
|
+
}
|
|
774
|
+
if (input.prompt) {
|
|
775
|
+
finalContext.prompt = input.prompt;
|
|
776
|
+
}
|
|
777
|
+
else if (input.messages) {
|
|
778
|
+
const filteredMessages = input.messages.filter(msg => msg.role !== 'system');
|
|
779
|
+
if (filteredMessages.length > 0) {
|
|
780
|
+
finalContext.messages = filteredMessages;
|
|
972
781
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return [{ json: formattedResult }];
|
|
976
|
-
}, 'generateTextOperation', exec, index);
|
|
782
|
+
}
|
|
783
|
+
return finalContext;
|
|
977
784
|
}
|
|
978
|
-
async function
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
785
|
+
async function generateTextOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey) {
|
|
786
|
+
const enableStreaming = exec.getNodeParameter('enableStreaming', index, false);
|
|
787
|
+
const includeRequestBody = options.includeRequestBody;
|
|
788
|
+
const tools = provider === 'google' ? await buildGoogleTools(exec, index) : undefined;
|
|
789
|
+
let cachedContentName = null;
|
|
790
|
+
let googleProviderOptions;
|
|
791
|
+
let cacheContentInfo;
|
|
792
|
+
if (provider === 'google') {
|
|
793
|
+
const cacheSetup = await prepareGoogleCache(exec, index, apiKey, input, tools, 'text');
|
|
794
|
+
cachedContentName = cacheSetup.cachedContentName;
|
|
795
|
+
cacheContentInfo = cacheSetup.cacheContentInfo;
|
|
796
|
+
googleProviderOptions = cacheSetup.googleProviderOptions;
|
|
797
|
+
}
|
|
798
|
+
const finalContext = resolveFinalContext(input, cachedContentName, cacheContentInfo);
|
|
799
|
+
const params = {
|
|
800
|
+
model: aiProvider(model, modelSettings),
|
|
801
|
+
...finalContext,
|
|
802
|
+
};
|
|
803
|
+
if (tools && !cachedContentName) {
|
|
804
|
+
params.tools = tools;
|
|
805
|
+
}
|
|
806
|
+
if (provider === 'google' && googleProviderOptions) {
|
|
807
|
+
params.providerOptions = {
|
|
808
|
+
google: googleProviderOptions,
|
|
1000
809
|
};
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
810
|
+
}
|
|
811
|
+
const textNumericKeys = [
|
|
812
|
+
'maxTokens',
|
|
813
|
+
'temperature',
|
|
814
|
+
'topP',
|
|
815
|
+
'topK',
|
|
816
|
+
'frequencyPenalty',
|
|
817
|
+
'presencePenalty',
|
|
818
|
+
'seed',
|
|
819
|
+
];
|
|
820
|
+
applyNumericOptions(params, options, textNumericKeys);
|
|
821
|
+
if (enableStreaming) {
|
|
822
|
+
return await handleStreaming(params, provider, includeRequestBody);
|
|
823
|
+
}
|
|
824
|
+
const result = await (0, ai_1.generateText)(params);
|
|
825
|
+
const formattedResult = formatTextResult(result, includeRequestBody, provider);
|
|
826
|
+
return [{ json: formattedResult }];
|
|
1014
827
|
}
|
|
1015
828
|
async function buildGoogleTools(exec, index) {
|
|
1016
829
|
const googleTools = exec.getNodeParameter('googleTools', index, []);
|
|
1017
|
-
if (!
|
|
830
|
+
if (!googleTools || googleTools.length === 0) {
|
|
1018
831
|
return undefined;
|
|
832
|
+
}
|
|
1019
833
|
const tools = {};
|
|
1020
834
|
const { google } = await Promise.resolve().then(() => __importStar(require('@ai-sdk/google')));
|
|
1021
835
|
const toolSet = new Set(googleTools);
|
|
@@ -1031,129 +845,82 @@ async function buildGoogleTools(exec, index) {
|
|
|
1031
845
|
return tools;
|
|
1032
846
|
}
|
|
1033
847
|
async function handleStreaming(params, provider, includeRequestBody) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
848
|
+
const stream = await (0, ai_1.streamText)(params);
|
|
849
|
+
const chunks = [];
|
|
850
|
+
let fullText = '';
|
|
851
|
+
for await (const textPart of stream.textStream) {
|
|
852
|
+
fullText += textPart;
|
|
853
|
+
chunks.push({ json: { chunk: textPart, isStreaming: true } });
|
|
854
|
+
}
|
|
855
|
+
let finalUsage;
|
|
856
|
+
try {
|
|
857
|
+
finalUsage = await stream.usage;
|
|
858
|
+
}
|
|
859
|
+
catch (error) {
|
|
860
|
+
console.warn('UniversalAI: Failed to get usage from stream:', error);
|
|
861
|
+
finalUsage = undefined;
|
|
862
|
+
}
|
|
863
|
+
const finalJson = {
|
|
864
|
+
text: fullText,
|
|
865
|
+
toolCalls: stream.toolCalls || [],
|
|
866
|
+
toolResults: stream.toolResults || [],
|
|
867
|
+
finishReason: stream.finishReason,
|
|
868
|
+
usage: finalUsage ? formatUsage({ usage: finalUsage }, provider) : undefined,
|
|
869
|
+
isStreaming: false,
|
|
870
|
+
isFinal: true,
|
|
871
|
+
};
|
|
872
|
+
if (includeRequestBody) {
|
|
1038
873
|
try {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
json: {
|
|
1043
|
-
chunk: textPart,
|
|
1044
|
-
isStreaming: true
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
874
|
+
const requestMetadata = stream.request ? await stream.request : undefined;
|
|
875
|
+
if ((requestMetadata === null || requestMetadata === void 0 ? void 0 : requestMetadata.body) !== undefined) {
|
|
876
|
+
finalJson.request = { body: requestMetadata.body };
|
|
1047
877
|
}
|
|
1048
878
|
}
|
|
1049
879
|
catch (error) {
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
let finalUsage;
|
|
1053
|
-
let requestMetadata;
|
|
1054
|
-
try {
|
|
1055
|
-
finalUsage = await stream.usage;
|
|
1056
|
-
}
|
|
1057
|
-
catch (error) {
|
|
1058
|
-
console.warn('Could not get usage from stream:', error);
|
|
880
|
+
console.warn('UniversalAI: Failed to get request metadata from stream:', error);
|
|
1059
881
|
}
|
|
1060
|
-
try {
|
|
1061
|
-
requestMetadata = stream.request ? await stream.request : undefined;
|
|
1062
|
-
}
|
|
1063
|
-
catch (error) {
|
|
1064
|
-
console.warn('Could not get request metadata from stream:', error);
|
|
1065
|
-
}
|
|
1066
|
-
const finalJson = {
|
|
1067
|
-
text: fullText,
|
|
1068
|
-
toolCalls: stream.toolCalls || [],
|
|
1069
|
-
toolResults: stream.toolResults || [],
|
|
1070
|
-
finishReason: stream.finishReason,
|
|
1071
|
-
usage: finalUsage ? formatUsage({ usage: finalUsage }, provider) : undefined,
|
|
1072
|
-
isStreaming: false,
|
|
1073
|
-
isFinal: true,
|
|
1074
|
-
};
|
|
1075
|
-
if (includeRequestBody && (requestMetadata === null || requestMetadata === void 0 ? void 0 : requestMetadata.body) !== undefined) {
|
|
1076
|
-
finalJson.request = { body: requestMetadata.body };
|
|
1077
|
-
}
|
|
1078
|
-
chunks.push({ json: finalJson });
|
|
1079
|
-
return chunks;
|
|
1080
|
-
}, 'handleStreaming');
|
|
1081
|
-
}
|
|
1082
|
-
class UniversalAI {
|
|
1083
|
-
constructor() {
|
|
1084
|
-
this.description = descriptions_1.UNIVERSAL_AI_DESCRIPTION;
|
|
1085
|
-
this.methods = {
|
|
1086
|
-
loadOptions: {
|
|
1087
|
-
async getModels() {
|
|
1088
|
-
return withErrorHandling(async () => {
|
|
1089
|
-
const provider = this.getCurrentNodeParameter('provider');
|
|
1090
|
-
const cacheKey = generateCacheKey(provider, 'models:');
|
|
1091
|
-
const cached = modelCache.get(cacheKey);
|
|
1092
|
-
if (cached)
|
|
1093
|
-
return cached;
|
|
1094
|
-
const { OPENROUTER_MODELS, GOOGLE_GEMINI_MODELS, DEEPSEEK_MODELS, GROQ_MODELS } = await Promise.resolve().then(() => __importStar(require('./model-lists')));
|
|
1095
|
-
const models = {
|
|
1096
|
-
google: GOOGLE_GEMINI_MODELS,
|
|
1097
|
-
deepseek: DEEPSEEK_MODELS,
|
|
1098
|
-
groq: GROQ_MODELS,
|
|
1099
|
-
openrouter: OPENROUTER_MODELS
|
|
1100
|
-
}[provider] || [];
|
|
1101
|
-
modelCache.set(cacheKey, models);
|
|
1102
|
-
return models;
|
|
1103
|
-
}, 'getModels');
|
|
1104
|
-
},
|
|
1105
|
-
},
|
|
1106
|
-
};
|
|
1107
882
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
883
|
+
chunks.push({ json: finalJson });
|
|
884
|
+
return chunks;
|
|
885
|
+
}
|
|
886
|
+
async function generateObjectOperation(exec, index, provider, aiProvider, model, modelSettings, input, options, apiKey) {
|
|
887
|
+
const schemaName = exec.getNodeParameter('schemaName', index, '');
|
|
888
|
+
const schemaDescription = exec.getNodeParameter('schemaDescription', index, '');
|
|
889
|
+
const rawSchema = exec.getNodeParameter('schema', index);
|
|
890
|
+
const parsedSchema = parseAndValidateSchema(rawSchema, exec);
|
|
891
|
+
let cachedContentName = null;
|
|
892
|
+
let googleProviderOptions;
|
|
893
|
+
let cacheContentInfo;
|
|
894
|
+
if (provider === 'google') {
|
|
895
|
+
const cacheSetup = await prepareGoogleCache(exec, index, apiKey, input, undefined, 'object');
|
|
896
|
+
cachedContentName = cacheSetup.cachedContentName;
|
|
897
|
+
cacheContentInfo = cacheSetup.cacheContentInfo;
|
|
898
|
+
googleProviderOptions = cacheSetup.googleProviderOptions;
|
|
899
|
+
}
|
|
900
|
+
const finalContext = resolveFinalContext(input, cachedContentName, cacheContentInfo);
|
|
901
|
+
const params = {
|
|
902
|
+
model: aiProvider(model, modelSettings),
|
|
903
|
+
schema: (0, ai_1.jsonSchema)(parsedSchema),
|
|
904
|
+
schemaName,
|
|
905
|
+
schemaDescription,
|
|
906
|
+
...finalContext,
|
|
907
|
+
};
|
|
908
|
+
if (provider === 'google' && googleProviderOptions) {
|
|
909
|
+
params.providerOptions = {
|
|
910
|
+
google: googleProviderOptions,
|
|
1129
911
|
};
|
|
1130
|
-
if (credentials.baseUrl && String(credentials.baseUrl).trim()) {
|
|
1131
|
-
providerConfig.baseURL = credentials.baseUrl;
|
|
1132
|
-
}
|
|
1133
|
-
const aiProvider = await getProvider(provider, providerConfig);
|
|
1134
|
-
for (let i = 0; i < items.length; i++) {
|
|
1135
|
-
try {
|
|
1136
|
-
const result = await processItem(this, i, provider, aiProvider, credentials.apiKey);
|
|
1137
|
-
returnData.push(...result);
|
|
1138
|
-
}
|
|
1139
|
-
catch (error) {
|
|
1140
|
-
if (this.continueOnFail()) {
|
|
1141
|
-
returnData.push({
|
|
1142
|
-
json: {
|
|
1143
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1144
|
-
errorCode: error instanceof UniversalAIError ? error.code : 'UNKNOWN_ERROR',
|
|
1145
|
-
itemIndex: i,
|
|
1146
|
-
success: false
|
|
1147
|
-
},
|
|
1148
|
-
pairedItem: { item: i }
|
|
1149
|
-
});
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
throw error;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
return [returnData];
|
|
1156
912
|
}
|
|
913
|
+
const objectNumericKeys = [
|
|
914
|
+
'temperature',
|
|
915
|
+
'topP',
|
|
916
|
+
'topK',
|
|
917
|
+
'frequencyPenalty',
|
|
918
|
+
'presencePenalty',
|
|
919
|
+
'seed',
|
|
920
|
+
];
|
|
921
|
+
applyNumericOptions(params, options, objectNumericKeys);
|
|
922
|
+
const result = await (0, ai_1.generateObject)(params);
|
|
923
|
+
const formattedResult = formatObjectResult(result, options.includeRequestBody, provider);
|
|
924
|
+
return [{ json: formattedResult }];
|
|
1157
925
|
}
|
|
1158
|
-
exports.UniversalAI = UniversalAI;
|
|
1159
926
|
//# sourceMappingURL=UniversalAI.node.js.map
|